From Param: How URL-Based Fetching Ought to Be

The addition of RESTful routing and the #to_param method in Rails has undoubtedly improved both the ease and usefulness of URL generation in Rails apps. However, there has come to be a rather insidious tendency to have an auto-generated id as the true reference for a given item. This may make sense for some or even many apps, but by and large records should be referred to by their content, not their ids, in URLs if at all possible. With this goal in mind, I have released from_param, a simple addition to ActiveRecord that makes it dead simple to use better URL finders.

Example

First of all, from_param is meant to simply be the complement of to_param, that is, you should be able to pass Model.from_param(some_parameter) the same way you would pass Model.from_xml(some_xml). Let’s examine how this works in practice:

class User < ActiveRecord::Base
  def to_param
    "#{id}-#{login.downcase}" 
  end
end

class UsersController < ApplicationController
  # GET /users/1-mbleigh
  def show
    @user = User.from_param(params[:id]) # => <User id=1 login="mbleigh">
  end
end

Simple enough, and it should look very familiar except that instead of a User.find or User.find_by_id, we have User.from_param. In fact, that’s exactly what this code does: from_param by default will call find_by_id since that is how the default to_param is configured.

Now let’s take something a little more complicated: a blog post title. I definitely don’t want an id in the URL if it’s a permalink, so how can I make an arbitrary parameter found as easily as the id? Simple, just add a param column to your table! Take a look:

class Post < ActiveRecord::Base
  def to_param
    "#{created_at.strftime("%Y-%m-%d")}-#{title.gsub(" ","-").downcase.gsub(/[^a-z0-9-]/,"")}" 
  end
end

class PostsController < ApplicationController
  # GET /posts/2008-04-26-from-param-plugin-released
  @post = Post.from_param(params[:id]) # => <Post title="From Param: Plugin Released" created_at="2008-04-26">
end

From Param will auto-magically save the to_param of your model to the specified parameter column (defaults to “param” but you can set it by calling set_param_column) and then automatically know to find by that column if it exists when from_param is called. This way, all you have to do is define a to_param that will be unique to your record and everything else is handled for you!

This plugin is really quite simple, but it establishes a convention that I feel has been missing from Rails for some time: a standard method to call to retrieve a record based on its URL parameter.

Installation

To install the plugin on Edge Rails or Rails 2.1 and greater:

script/plugin install git://github.com/mbleigh/from_param.git

On earlier versions of Rails:

git clone git://github.com/mbleigh/from_param.git vendor/plugins/from_param

Resources

If you have any suggestions for expanding the usefulness of the plugin or run into any problems, please report them on the From Param Lighthouse Project.

Share:

Comment on this post (1 comment)


Codebite: Generic "New Today" for Rails Records

Often times in an application you might want to know how many of a given model were created today. Rather than writing a custom method for each model, let’s keep it DRY and extend ActiveRecord:

class ActiveRecord::Base
  def self.new_today
    if self.new.respond_to?(:created_at)
      self.count(:all, :conditions => "created_at > (NOW() - 60*60*24)")
    else
      nil
    end
  end
end

This will return the number of records created in the last 24 hours by calling the model with new_today (e.x. User.new_today). It will return nil if the model does not respond to the created_at Rails timestamp attribute.

Share:

Comment on this post (0 comments)


ActiveRecord::Base.create_or_update on Steroids

I’ve been working a lot with seed data in my applications lately, and it’s obviously a problem that can be pretty aggravating to deal with. I ultimately liked the db-populate approach the best with the addition of the ActiveRecord::Base.create_or_update method. My one problem with it is that it’s based entirely on fixed ids for the fixtures, which is a pain to deal with when loading up your attributes from arrays. Here’s an example of what I was doing:

i = 1

{ "admin"     => ["Administrator", 1000], 
  "member"    => ["Member", 1], 
  "moderator" => ["Moderator", 100],
  "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|
  Role.create_or_update(:id => i, :key => key, :name => val[0], :value => val[1])
  i += 1
end

I really don’t like having to put the i in there to increment up. Not only is it messier code, but it would be dangerous if I wanted to move around the roles. I also don’t want to have to have an id explicitly in each entry since I really don’t care what the id is. So I thought I would hack up a better solution for this create_or_update scenario and this is what I came up with:

class << ActiveRecord::Base
  def create_or_update(options = {})
    self.create_or_update_by(:id, options)
  end

  def create_or_update_by(field, options = {})
    find_value = options.delete(field)
    record = find(:first, :conditions => {field => find_value}) || self.new
    record.send field.to_s + "=", find_value
    record.attributes = options
    record.save!
    record
  end

  def method_missing_with_create_or_update(method_name, *args)
    if match = method_name.to_s.match(/create_or_update_by_([a-z0-9_]+)/)
      field = match[1].to_sym
      create_or_update_by(field,*args)
    else
      method_missing_without_create_or_update(method_name, *args)
    end
  end

  alias_method_chain :method_missing, :create_or_update
end

Basically, this allows me to call create_or_update with an arbitrary attribute as my “finder” by calling Model.create_or_update_by(:field, ...). To give it a little taste of syntactic sugar, I threw in a method missing to allow you to name a field in the method call itself. So now the code I wrote before can become this:

{ "admin"     => ["Administrator", 1000], 
  "member"    => ["Member", 1], 
  "moderator" => ["Moderator", 100],
  "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|
  Role.create_or_update_by_key(:key => key, :name => val[0], :value => val[1])
end

This is much cleaner and prettier to look at, and also makes sure that it is keying off of the value that I really care about. This new create_or_update combined with db-populate creates the most powerful seed data solution I’ve yet come across.

Share:

Comment on this post (0 comments)