neovintage by Rimas Silkaitis

Configuring Your Rails 3 Plugin

With Railties, I've found writing plugins for Rails 3 to be much easier than before. Instead of writing a post about how easy it is to create plugins, I figured I would share what I learned about configuring your recently created plugin. After poking through the Rails code, I came across some interesting things that can really clean up and tighten the code in your Rails projects.

If you're looking for more of an introduction on Railties and Rails 3 plugins, I recommend that you read a couple of posts before you continue with mine. These posts should be enough to get you started.

Environment-specific Configuration

In the past, if you needed any type of configuration to be done in your app you'd more than likely create a yaml file and stick what you needed in there. With Rails 3 and Railties, you can move all of your plugin configuration into the environment specific files (application.rb / development.rb / test.rb / production.rb) within your Rails project. I like this so much more because all of the configuration stays in the same place and you don't have to litter your project with a bunch of yaml files.

Throughout this post, I'll refer to a library I created to use MongoDB as a FIFO cache store. Let's start with a snippet of development.rb in one of my current projects.

The important line in the above code is config.mongo_fifo. Did you know that you can create any variable that you want within the config object? The cool thing is that the config object is actually an instance of Rails::Railtie::Configuration and when you specify a method that doesn't exist, that class will actually go through method_missing and add the new method to a hash and assign whatever you wanted to it. So in the example above, mongo_fifo was added to the application's config hash with a new instance of MongoFifo. If you need a little more configurability with your methods, like action_mailer above, you can do that too. So let's say I wanted mongo_fifo to have a slew of config options, I could do this in my Railties class to add that ability:

Coincidentally, that's how ActiveRecord, ActionMailer and ActiveSupport allow for "namespaced" configuration in your Rails app.


Now that we moved the configuration into the specific environment files, we'll be able to access it via our initializers. I really wish I had more to write here but it's just as easy as accessing a hash. So in my MongoFifo code, I've got one initializer that will create a global variable within the Rails module so that anywhere within my app, I can read and write to my MongoDB cache. Here's the code from MongoFifo:

Get going!

The beauty of Railties is that you don't necessarily need to develop a whole gem to get the benefits of it's modularity. In my case, I wrote some code that's a slimmed down version of an ActiveSupport cache store that works with MongoDb's capped collections. I plopped this file in my /lib directory. I'm able to put the MongoDB connection configuration in the specific environment (development / test / production) file and keep my overall Rails project that much cleaner. Give it a go!

Using Allocate to Create ActiveRecord Objects When Stubbing find_by_sql

find_by_sql. What a method. I love the flexibility it gives me when I need to get information from my database with as little fuss as possible. Yet when I want to stub find_by_sql and return real objects in my rspec tests, a little part of me gets frustrated. The real pain comes in when you want to have an object with attributes that aren't part of the original class and typically you're going to run into that when you use find_by_sql.

In my particular use case, I had two different tables, the first was a Shop table and the second was a Job table. In terms of the association, a Shop could have many jobs. The find_by_sql method was aggregating data from the two tables and creating each instance of the result as Job object. So it looked something like this:

I'm sure that you're thinking to yourself that I could have just created some factories or fixtures and then had my tests just query the database for the records. That probably would have been the easy route, but I was wondering if I could pull this off without the database call. So how do we do this?

First, we need to understand how ActiveRecord actually creates new objects. The post from Peter Bui is a great first step in explaining what goes on when ActiveRecord queries a database and creates a new object. The important thing to note in his article is Ruby's ability to create an object without calling the initialize method via allocate.

From there it was time to look at the core ActiveRecord code to see what is going on. Just to note, since Peter's article, ActiveRecord::Base has changed a little bit. First I started with the code for find_by_sql:

You can see in this piece of code that ActiveRecord is hitting the database with your custom SQL and then for each result creating a new instance. Now let's see how that instance is created:

The first line of code is looking for the appropriate class to use to create the object via find_sti_class. Then it allocates an object based upon the class name. The next line is really where the meat is. init_with will actually then create the object with the attributes that you specify. Sweet! I think we struck gold.

Now that we know about the init_with and allocate methods we can now create real objects to use in our tests if we need attributes outside of what the object provides. So in the example that I provided above, I could do something like this:

Excellent! Now I can stub the find_by_sql call and I can create objects that will be in the correct class that I need.

Do note that what I did above is for edge rails. If you're on 3.0.1 or below you're going to have to do this to create your objects:

Also, pay attention to the way the hash was created. You'll need to use strings for the keys otherwise you'll get a bunch of errors.

The Rails Way Means Disruption

A little less than a month ago, Obie Fernandez posed the question "What is the Rails Way (To You)?" For me, it took a little while to ponder that question and generate a response. This is what I came up with:

I see the Rails way encompassing two different stakeholders, the first being the individual, myself in this case, and the second being the community. I can't explain what 'the way' is without first identifying these two groups.

When talking about the community, I see the Rails way as embracing change. The Rails community seems to embrace a constant state of mild disruption. Some of the best corporations can't even deal with change as well as the Rails community. While some might argue that the precipitation of Rails 3 was a huge disruption, I would say that a Rails 3 type of disruption is quite rare. Mild disruption allows the community to vet ideas and practices and really reinforce what really works within the realm of web development. Something like BDD is an example of the reinforcing practices that have grown with the Rails community.

Personally, what the Rails way means for me is that it can mean anything. If I need a monolithic ERP system using Rails as my backend, I can do it. If I need to create a bunch of RESTful web services that are all interconnected, I can. 'The way' means that when I work with Rails, especially Rails 3, I can mold the framework around my needs knowing that Rails has quality baked in from the start.

Mongodb Stored Javascript Functions - Ruby Edition

I was writing a couple of different analytics applications recently. My applications did two different things but accessed the same database. I was wondering if there were a way to store javascript functions so that I could share code between two different applications. That's when I stumbled across the server side code execution and Mike Dirolf's post on stored javascript and pymongo. I was hoping for the same type of functionality in the ruby driver as in pymongo, but alas it wasn't.

In the patch that I submitted to the mongo ruby driver, I added a couple of functions that will allow you to add stored Javascript functions to system.js in your database. Let's walk through a use case:

# Assume we're using the mongo driver and db = test
db ="localhost").db("test")

# Setup the stored function in MongoDB
db.add_stored_function("sum", "function (x, y) { return x + y; }")

sites = db.collection("sites")
sites.insert({:name => "Site 1", :admin_count => 2, :moderator_count => 3})
sites.find("$where" => "sum(this.admin_count, this.moderator_count) == 5")

# When we don't need the stored function any more

This is just one example of how to use the stored functions. Don't forget that you can use them in map/reduce, eval, and $where clauses.

Unlike the pymongo version, I did not create an instance of the system.js collection in the db instance. This would have allowed us to use introspection to actually make method calls instead of having to write out an eval method. (See how it works for pymongo at the end of Mike's post.) In my use cases, I used the stored functions within where and map_reduce calls and didn't really need to use eval. If other people need it, I could definitely change it up.

Mongoid Versioning - A Run Down

In past Rails apps that I've built, I've used plugins such as vestal version or acts_as_versioned to version my ActiveRecord models in MySQL. Using Mongoid, you get that functionality built-in without the need for extra gems / plugins.

For the purposes of this discussion, let's use the following code as a reference point:

So.... what really goes on when you put that innocent include Mongoid::Versioning in your model? I'm glad you asked:

  • A field named version of type Integer is added.
  • An embeds_many association named versions is created with the :class_name option set as your current class. Using our example above the generated code would look like this: embeds_many :versions, :class => Post.
  • A before_save callback is created that takes care of versioning the document.
Now this stuff came straight from the documentation.

Now that we've got the basics established lets go through some use cases that will better illustrate how versioning works.

  • Let's say I have an idea for a new post and I create one with just a title. Then at some point in the future I finally get around to writing the content and save it to MongoDB.

    Since we've completed 2 save operations, there will be two versions in the database and the document look something like this:
    Notice in the versions array that the content field is actually not there. If a default is not defined on your field and you do not create anything the field will not be created in the document.
  • Now we've got our newly finished post in MongoDB and it's available for the world to see. At some point, a user decides to comment on our post and we need to save that to our document.

    The document in the database will now look like this:
    Notice that in this instance that a new version was not created when we saved an embedded document. This is great when you've got a setup like this and you don't want to create multiple versions.

Gotchas & Workarounds

  • Take into consideration how you're going to create your embedded documents - By this I mean if you were to do something like this:
    Mongoid will not only create a new comment but will also create a new version of the entire document
  • The entire contents of the document is saved in the version - When embedding something like comments on a post, depending on how many comments your had, that array of comments could get pretty big. Now if you were to update a field on the post and it had a large amount of comments, all of the comments would get saved in the old and new versions. Again depending on the size of the comments, this could quickly start taking up large amounts of disk space
  • You can skip creating a version if need be - I wouldn't recommend this if you're trying to keep track of documents, but it can be done.

    Note that :revise is the name of the method in Mongoid::Versioning and that I did this on the class Post and not an instance. I'm not sure if you can skip a callback on an instance. I'll have to look into that.

That's all I've got for now. As I continue to learn more about mongoid, I'll continue to post on this blog. Maybe I'll even turn this into a larger series on the ins and outs of using mongoid.