dcsimg

Living on the Edge of Rails, Part 2

Edge Rails has more treats in store for early adopters and lazy developers.

This week, I am walking along the Rails, Edge Rails, to reveal some of the conveniences you can expect to see in Rails 3, the next major release of the popular framework. Last time, I looked at independent validation classes and the new state machine class. Here, let’s focus on some new coding shorthand that saves time and typing, but also reduces errors and redundant repetition.

Database Seeding

Rails’s migrations are a godsend. At any given time, your collection of migrations binds the current version of the schema to the current version of your code and decouples the schema from the specifics of the database engine. Combined, deploying a Rails application to a new environment is usually quite simple. A typical Rails application can bootstrap on SQLite, MySQL, PostgreSQL, and others with a small change to a configuration file.

Ideally, a migration would only define structure, leaving initial population of the database to the application. However, that’s not been the case to date, as Rails lacked a convention to bridge the gap between creating the database (rake db:migrate) and launching the application. Hence, it’s quite common to see create calls in otherwise agnostic migrations.

As of May 2009, Edge Rails now offers a formal convention for population. Create you schema with migrations, and use seeding to add initial data. To seed, create db/seeds.rb and sow to your heart’s content. For example, if you have this code…

%w( apple banana mango watermelon ).each { |f| Fruit.create( :name => f ) }

… in a migration, move it to seeds.rb. There are two new rake tasks to facilitate seeding, too. rake db:seed performs the actual seeding; rake db:setup creates the database, runs db/schema.rb to install the schema, and seeds the database. So, to reset your database, you might run rake db:drop db:setup. If you prefer to run your migrations and then seed, you could run rake db:drop db:create db:migrate db:seed.

As with many Rails enhancements, seeding is not earth-shattering, but it furthers convention over configuration and assigns individual roles to actors.

Don’t Repeat Yourself

Tell me if this sounds familiar: You create a model and a number of the fields require identical transformation. For instance, consider an address book where each entry contains a primary and secondary email address. To avoid repetition, you might create a private function to perform manipulation on both, something a la…

class Entry < ActiveRecord::Base
  ...
  if ( in_example( self.primary_address ) or in_example( self.secondary_address ) )
    # Processing
  end

  private

  def in_example( address )
    address =~ /example\.org$/i
  end
end

That works, but it feels awkward, especially when you can call a dynamic finder method such as Person.find_by_first_name_and_last_name(). Once again, a patch for Edge Rails provides do-it-yourself dynamic attribute methods without the muss and fuss of method_missing. Use attribute_method_suffix (and attribute_method_prefix and attribute_method_affix). Here’s a recast of the latter example.

class Entry < ActiveRecord::Base
  attribute_method_suffix '_is_in_example?'

  if self.primary_address_is_in_example? or self.secondary_address_is_in_example?
    # Processing
  end

  private

  def attribute_is_in_example?( attr )
    send(attr) =~ /example\.org$/
  end
end

Much better. A related patch also provides a dynamic method to reset your attributes.

entry = Entry.find_by_first_name_and_last_name(
  :first_name => 'Groucho', :last_name => 'Marx')

entry.last_name = 'Flywheel'
entry.reset_last_name!

reset_ is dynamic and purges all changes from the instance above. In other words, after the reset_last_name, entry.changes is empty and entry.last_name_changed? is false.

Other Results from BugMash

The previous two enhancements are brand new and are just two of approximately 130 improvements made to core Rails during this month’s BugMash. The event squashed bugs and granted wishes, and many of the participants never committed to Rails core before. Here are some other notable changes:

  • Matt Duncan changed the application template facility to accept a :branch option for plugins in Git repositories and :revision for plugins in Subversion. For instance, to install revision 100 of conquer_world, you would write plugin 'conquer_world', :svn => 'svn://svn.rubyforge.org/conquer_world/trunk', :revision => 10.
  • Another nicety, validates_length_of :name, :within => (1…128) restricts name to any length between 1 and 127. This change was made by Adam Keys.
  • Mike Gunderloy addresses a longstanding bug with associations. The original bug states: If User has_many Posts, and each Post has_one Photo, and you say User has_many :photos, :through :posts, user.photos causes an exception. This is now fixed.
  • This code, which is a nice example of scopes and eager loading in has_many now works.

    class Company < ActiveRecord::Base
      has_many :employments, :class_name => 'Employment',
          :conditions => { :end_date => nil }, :include => :person
    end
    
    class Employment < ActiveRecord::Base
      belongs_to :person
      belongs_to :company
    end
    
    class Person < ActiveRecord::Base
    end
    
    Company.find(:all, :include => {:employments => :person})
    Company.find(:all, :include => :employments)
    

    Previously, the last two statements failed because the SQL generated was incorrect.

  • Mike Breen scratched an itch. You can now use the command rake routes CONTROLLER=users to see the routes for the UserController.

And finally, Dan Cheail produced this ditty, which I snipped from his commit. Given the three classes…

class Continent < ActiveRecord::Base
  has_many :countries
  # attribs: id, name
  end

class Country < ActiveRecord::Base
  belongs_to :continent
  # attribs: id, name, continent_id
end

class City < ActiveRecord::Base
  belongs_to :country
  # attribs: id, name, country_id
end

… this helper…

grouped_collection_select(:city, :country_id, @continents, :countries, 
  :name, :id, :name)

… produces:

<select name="city[country_id]">
  <optgroup label="Africa">
    <option value="1">South Africa</option>
    <option value="3">Somalia</option>
  </optgroup>
  <optgroup label="Europe">
    <option value="7" selected="selected">Denmark</option>
    <option value="2">Ireland</option>
  </optgroup>
</select>

To decode all those parameters: :city is the class and :country_id is the attribute to use in the select tag. @continents is the array of objects representing the optgroups. :countries is the method of the collection that yields the options. :name is the method to call to get the labels for the optgroups. And the last two, :id and :name, yield the value and contents of each option. Whew!

More tomorrow. Yes, there is even more!

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62