Fork me on GitHub

Updates from January, 2012 Toggle Comment Threads | Keyboard Shortcuts

  • britg 9:27 am on January 7, 2012 Permalink | Log in to leave a Comment
    Tags: , , , mongodb, postgres   

    Forging Forgecraft: A Hybrid SQL MongoDB Data Solution 

    Forgecraft is a game currently in development using Ruby on Rails, Backbone.js, and all sorts of HTML5 buzzwords. Read an introduction here, and play the demo here.

    One of my primary goals while building Forgecraft is to learn as many new technologies as possible. I’ve learned the hard way that it’s better to leave this kind of exploration in your hobby projects and out of your production “for real” stuff. And, seeing as how there are so many emergent technologies right now in web development there’s a lot to explore!

    Whole Hog is Too Much Hog

    MongoDB has quite a bit of steam behind it in the Rails/Ruby community (and plenty of other places too), and with projects like Mongoid and MongoMapper (I went with Mongoid) it’s an easy drop-in replacement for Active Record that maintains all those ORM conventions you know and love. I decided against the replacement approach for one primary reason: I wanted to use existing, robust, and actively developed libraries that rely on Active Record.

    Example: There’s no reason to roll your own authentication/user system when there are insanely popular and feature-full libraries already in place for them like Devise and authlogic (I went with Devise).

    Fortunately combining Active Record with Mongoid and getting the best of both worlds proved to be easy and painless.

    The Set Up

    The instructions for Mongoid include a step to remove the Active Record libraries from being loaded and delete your database.yml. Simply skip that step and both systems will run in conjunction.

    One small operational change you’ll have to make during development is explicitly defining when your rails generators should use Active Record. If you want to make an AR-driven model, your generators will look like:

    rails g active_record:model Player … produces a model the extends from ActiveRecord::Base.

    And by default, the data generators will use Mongoid:

    rails g model Skill … produces a model that includes Mongoid::Document.

    The Implementation

    Here’s how this combo system really shines in my opinion: get all the great features of gems built on AR with all the schema less features of Mongo. I’ll walk through an example:

    In Forgecraft, the authenticating object is the Player and players have many Skills which, right now, are Accuracy, Craftsmanship, and Perception. But, the skill list will likely grow and change over time as the game evolves. A typical AR/SQL based approach would be to create a skills table and a join table between players and skills resulting in a complex multi-table query to get a player’s complete skills.

    It’s clear here that the Player and Skills would fit well together as a single document in Mongo, queried all at once in one tidy object. But, since the Player is also our authentication model, using Mongo would prevent us from using Devise to handle all of that boring and complex auth stuff for us.

    Enter Hybridization. Forgecraft’s implementation uses the typical Devise set up around the Player object (again all on top of AR). All of a player’s skills go into a single document with a reference to the player, like so:


    class Skill
      include Mongoid::Document
      field :player_id, :type => Integer
    
      field :accuracy, :type => Integer, :default => 0
      field :craftsmanship, :type => Integer, :default => 0
      field :perception, :type => Integer, :default => 0
    
      index :player_id, :unique => true
    
      def player
        Player.find_by_id(player_id)
      end
    end

    To set up the relationships like one would expect with Active Record is a simple method on your Player object:


    class Player < ActiveRecord::Base
    
      # ...
    
      def skills
        @skills ||= (Skill.where(:player_id => self.id).first || \
                     Skill.create(:player_id => self.id))
      end
    
    end

    Bam! Now we can retrieve our skills as a single document (fast), and add new skills with ease (easy). Since Mongo is schema less, you could even have a different set of skills per player. Again, all of this is achievable through SQL — it’s been done for decades — but it just makes more conceptual sense to treat these as a single document.

    Querying and working with these objects and their relationships looks and feels just like active record:

    player.skills
    player.skills.accuracy
    player.skills.update_attributes(:accuracy => 5)
    player.skills.inc(:accuracy, 1)

    Benefits Outweight the Consequences

    I know, I know — any time you introduce an additional system, especially a datastore, you introduce a lot of complexity, failure modes, testing, etc. But, I honestly think the benefits are worth this extra complexity. Speed, ease of development, and the potential for less schema-driven growth make this an ideal environment for me. I highly recommend it, and will be using it in the future!


    More Forging Forgecraft:

     
  • britg 11:03 am on October 27, 2011 Permalink | Log in to leave a Comment
    Tags: ,   

    A SublimeText2 Project File for Rails Projects 

    The fuzzy finder in SublimeText2 is great, but if you’ve added a rails folder to your project you may see a bunch of cruft in the result list most likely due to cache files. Here’s a generic rails project file I use that keeps the fuzzy finder pretty clean.

    Note: this is a Rails 3.0 project with jammit and compass installed. The exclusion of the assets folder is probably not ideal in a Rails 3.1 project.

    {
    	"folders":
    	[
    		{
    			"path": "/Users/britg/Projects/ponycorn-fanclub.com",
                "folder_exclude_patterns": ["*.sass-cache", "tmp", "log", "assets", "cache"]
    		}
    	]
    }
    

     
  • britg 10:03 pm on June 2, 2011 Permalink | Log in to leave a Comment
    Tags:   

    Using Foreman for Logging and Watchr 

    Foreman has quickly turned into a must-have gem for local development for me.

    From @ddollar’s description of Foreman:

    Using foreman you can declare the various processes that are needed to run your application using a Procfile.

    Besides the normal processes (rails, workers, etc.) I’ve found great success running log tailing and watchr through Foreman. Here’s what my current Procfile looks like:


    (View original to see this Gist.)

    If your development pattern is anything like mine, it looks like:

    • Edit a test, model or controller: check watchr output

    • Edit a view, js or scss file: refresh the browser

    Here’s what that looks like through Foreman:

    This works well with watchr and log tailing output, as they rarely overlap. Even if they do, finding the appropriate output is easy with Foreman’s prepending of the process name and timestamp.

    Have any other tips/tricks with Foreman? Let me know!

     
  • britg 3:56 pm on January 1, 2010 Permalink | Log in to leave a Comment  

    Sinatra Authentication with MongoMapper Support 

    Added support for MongoMapper in the sinatra-authentication gem. Grab it from http://github.com/britg/sinatra-authentication.

    Be sure to require mongo_mapper before sinatra-authentication and everything should work peachy keen.

    require 'sinatra/base'
    require 'mongo_mapper'
    require 'sinatra-authentication'
    
     
  • britg 10:46 am on December 29, 2009 Permalink | Log in to leave a Comment
    Tags: , , , , sinatra   

    Cross Origin Resource Sharing with Sinatra 

    It’s no lie that I think highly of the potential of Cross Origin Resource Sharing. One of the great things about it is that it doesn’t take much re-wiring of existing server (or client-side) apps to start working cross domain.

    Enabling your server app is as simple as including a few extra headers when you detect a cross origin request. How do you know it’s a cross origin request? You’ll see the Origin: header — all CORS requests will have it. From there, response headers depend on the specifics of the request, but I won’t go over those here — check out the Mozilla Developer Center treatment for in-depth information.

    I’ve been working with Sinatra a lot lately, so I put together an extension for Sinatra that makes enabling Cross Origin requests even easier.

    sudo gem install sinatra-cross_origin

    There are two ways to use the extension: globally or per-route.

    Global

    For when you want to share all your endpoints cross-domain.

    
    require 'sinatra/base'
    require 'sinatra/cross_origin'
    
    class MyApp &lt; Sinatra::Base
      register Sinatra::CrossOrigin
    
      enable cross_origin
    
      get '/' do
        &quot;This is available to cross domain javascript requests automatically&quot;
      end
    end
    

    Per Route

    For when you want to share only some of your routes cross-domain.

    
    require 'sinatra/base'
    require 'sinatra/cross_origin'
    
    class MyApp &lt; Sinatra::Base
      register Sinatra::CrossOrigin
    
      get '/' do
        cross_origin
        &quot;This is available to cross domain javascript requests&quot;
      end
    end
    

    Configuration

    You can mix and match app-wide config and request specific config.

    
    require 'sinatra/base'
    require 'sinatra/cross_origin'
    
    class MyApp &lt; Sinatra::Base
      register Sinatra::CrossOrigin
    
      configure do
        # Comma separate list of remote hosts that are allowed.
        # :any will allow any host
        set :allow_origin, :any
    
        # HTTP methods allowed
        set :allow_methods, [:get, :post]
    
        # Allow cookies to be sent with the requests
        set :allow_credentials, true
      end
    
      get '/' do
        # Only available to GET requests originating from
        # http://example.com.  No cookies allowed.
        cross_origin :allow_origin =&gt; 'http://example.com',
          :allow_methods =&gt; [:get],
          :allow_credentials =&gt; false
        &quot;This is available to cross domain javascripts&quot;
      end
    end
    

    Grab the source at Github: britg/sinatra-cross_origin.

     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
esc
cancel