December 28, 2010

Posted by John

Tagged gems

Older: Improving Your Methods

Newer: Year In Review

A Scam I Say!

Today, I repeated myself in a particular way for the last time. In at least four places in Harmony, I had faux classes that responded to all, find, create, etc. and initialized attributes from a hash. As I went to write another, I realized I had a problem and it was time for a new open source project.

A Little History

Harmony has a Site model, which has a site mode. The site mode is either ‘development’ or ‘live’. Back in the day, I would have created a SiteMode model that was backed by the database and hooked up all the relationships between Site and SiteMode.

Over the years, I have realized that is a waste. The information in that database table rarely changes and if it does, it is usually accompanied by other code changes and a deploy. This type of information is perfect for just storing in memory. If you need to change it, do so, commit, and deploy.

When I was originally working on site mode’s a few years back, I created a fake model that looked similar to this:

class SiteMode
  cattr_accessor :modes
  @@modes = [
    {:id => 1, :name => 'live'},
    {:id => 2, :name => 'development'},
  ]
  
  def self.[](id)
    SiteMode.new(id)
  end
  
  def self.all
    @@modes.map { |m| SiteMode.new(m[:id]) }
  end

  attr_accessor :id, :name
  
  def initialize(id)
    self.class.modes.detect { |m| m[:id] == id }.each_pair do |attr, value|
      self.send("#{attr}=", value)
    end
  end
  
  def password_required?
    id == 2
  end
  
  def display_name
    name.titleize
  end
end

With just that code, Site could belong_to :site_mode and everything just worked. I got my same relationships and instead of querying a database (and having to keep that data in sync), everything was just stored in memory.

The Scam

Like I said, rather than create another fake model with all the same code and tests, I pulled out the shared pieces into a gem named scam. With scam in the mix, the SiteMode model now looks like this:

class SiteMode
  include Scam

  attr_accessor :name

  def password_required?
    id == 2
  end

  def item_cache?
    id == 1
  end

  def display_name
    name.titleize
  end
end

SiteMode.create({
  :id   => 1,
  :name => 'live'
})

SiteMode.create({
  :id   => 2,
  :name => 'development'
})

By just including Scam, we get all the enumerable functionality and such (see the specs for more). Now the SiteMode class deals specifically with site mode related code instead of how to initialize, create, and enumerate site modes. I switched the other classes that used the same idiom and was left with less code on the other side.

A Few Notes

This is nothing earth shattering, but it saves queries and wraps up an idiom I was using into a separate, well-tested piece of functionality that I can share not only across Harmony, but other applications as well.

One other thing worth noting is my choice of using id’s and having them be integers. The first advantage to using integers is size. The integer 2 is smaller to store than the string ‘development’. That might not seem like a big deal, but after working on the projects I have recently, every byte still counts.

The second reason is that integers are more flexible. Right now, 1 is live and 2 is development. If I decide I want another site mode to be the default instead of development, I can simply change development to a different id and create my new one as 2. The new one instantly becomes the new site mode for all sites with site_mode_id of 2.

If, on the other hand, I had used strings, I would have to map my new site mode to development and map development to something different. This would certainly lead to confusion in the code down the road. Strings have meaning because they are often words, whereas integers do not. Hope that makes sense.

At any rate, you can gem install scam if you have a need. If not, I hope some of the ideas in this post inspire you in some way.

10 Comments

  1. Was there a specific reason you chose not to inherit from ActiveModel ?

  2. @Steven Soroka: Yep, Harmony is Rails 2 still. Scam as it is will work with both 2 and 3. Also, since I am creating the data, I do not need validations, callbacks, etc.

  3. Christian W Christian W

    Dec 28, 2010

    I’m rather new to Rails still… But I don’t quite understand why you have these models in the first place?

  4. Are you persisting only mode_id to the Site model? In this case, what happen if you want to do stats reporting on your MongoDB Site collection and can’t use Ruby for example.

  5. I do this sort of thing a lot, too. It’s nice to see someone else share the same thoughts I do for situations where the data rarely changes. I’m using this approach in a roles/permissions plugin I wrote.

    Also, have you considered using OpenStruct for things like the @modes stores? @modes = [OpenStruct.new(:id => 1, :name => ‘live’), …] and then you could access it with SiteMode.modes.first.name and so on. Nearly every time I take this approach, ostruct finds a way in the mix :-)

  6. I released something similar 6 weeks ago: https://github.com/niko/is_a_collection.

    It’s more limited in what it does: no attribute initialization, no #create. It just keeps collection index.

  7. This looks pretty neat, thanks for releasing it!

    To Ryan’s point, this may be somewhat cargo-culted knowledge, but we found that OpenStruct had some serious memory leak issues (at least back with ruby 1.8).

  8. @Christian W: Mostly to move responsibilities around. You can either have a ton of if/else or case statements in your site model or have a separate site mode class and delegate functionality to it.

    @mech: Yep, only the mode gets persisted in site. If I can’t use Ruby I can just code the integers in a js function as well for mongo to use. I really do not see it as an issue.

    @Ryan: I like defining accessors as then there is never confusion on what is available. To me, open structs can be like having a schema-less database with no application schema either. It works, but it is not always fun to maintain.

  9. Vladimir Andrijevik Vladimir Andrijevik

    Dec 30, 2010

    Any reason why you didn’t just use https://github.com/zilkey/active_hash instead of writing what seems to be the same functionality?

  10. Yeah, I use ActiveHash, but it aims to be closer to a real ActiveRecord class, including a #save method, find_by_* magic, and associations (optionally). Scam looks like it’s about 10 times simpler, so I guess if you didn’t need all that stuff then Scam would be a perfect fit.

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a web developer and programmer who has fallen deeply in love with Ruby. More about John.

Syndication

Feed IconRailsTips Articles - An assortment of howto's and thoughts on Ruby and Rails.

Feed IconRails Quick Tips - Ruby and Rails related links.