zargony.com

#![desc = "Struggling through software development and server administration"]

My favorite features in upcoming Rails 2.1

Ruby on Rails 2.1 is almost here and there are several interesting features coming with it. Among many little details, performance optimizations and some bug fixes, my favorites are:

  • ActiveRecord named scopes
  • ActiveRecord dirty field checking and partial updates
  • Built-in gem dependencies

ActiveRecord named scopes

Half a year ago when I was struggling with pagination and special queries in Rails, at some time I found out about the HasFinder Plugin which I was using happily since then. So I'm pleased to hear that the features of this plugin were added to the Rails core under the name named_scope.

Namedscope does what its name implies: it can create named scopes for ActiveRecord models. Almost like the ActiveRecord method with_scope, you set criteria to define a subset of your records (a scope). With namedscope, such a scope gets a name and becomes usable (and even nestable) from other classes (like other models or controllers).

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :active => true }
  named_scope :male, :conditions => { :gender => 'm' }
end

User.active will return a list of active users (same as User.find(:all, :conditions => {:active => true})) and User.male will return a list of male users (same as User.find(:all, :conditions => {:gender => 'm'})).

Named scopes can also be nested. So User.active.male will return a list of active, male users -- and they can, of course, be used through associations. Consider another model, called Group, that has_many :users: @somegroup.users.active will return all active users of @somegroup.

With named scopes, you can keep the definition of a scope within the model where the scope applies, which is very DRY. Why should a controller or another model know that the attribute 'gender' needs to be 'm' to find male users? What if you change the definition of an 'active user', e.g. a user is only considered active, if he/she has logged in during the last 4 weeks. Previously you'd probably create custom finder methods -- named_scope makes this easier (and nestable).

But there's more about named scopes. Check out Ryan's post about HasFinder functionality to see how you can pass arguments to named scopes, create anonymous scopes and extend a named scope.

ActiveRecord dirty field checking and partial updates

Dirty field checking for ActiveRecord models can become really handy, e.g. if you want to write a log of changes to a particular table. While writing an application last year, I needed to find a way to create an audit log of all changes that were done to records in a table. That time, I ended up with something similar to what will be known as dirty field checking in Rails 2.1.

Consider an ActiveRecord model called User that has an attribute called :name. As usual, you can access the value of the attribute :name by using the getter: @user.name and set a new value by using the setter: @user.name = 'Joe'.

With dirty field checking, there are additional methods for each attribute. E.g. @user.name_changed? returns true, if the attribute :name was changed and not saved yet. And @user.name_was returns the value of the attribute :name before it was changed. An array with both, old and new value, is returned by @user.name_change, which is very useful to call in a before_save callback to write a log of changes like I mentioned above.

The above methods work on the corresponding attribute. There are also methods that query the dirty state of the whole model, e.g. @user.changed? returns true, if any attribute was changed and the model wasn't saved yet. @user.changed returns an array of attribute names that have changed and @user.changes returns a hash that contains old and new values for each changed attribute.

With dirty field checking, it's only a small step further to partial SQL updates. Currently, ActiveRecord always updates all attributes when changing and saving a record. Now that ActiveRecord knows, which attributes have changed values, it can optimize SQL UPDATE statements to only update the changed values, leading to smaller update queries and therefore better performance.

Read more about this smart feature in Ryan's posts about Dirty Objects and Partial Updates.

Built-in gem dependencies

Do you use Ruby gems in your Rails application? If so, you probably know the trouble of updating gems when deploying an application. Rails 2.1 can help to automatically install or update required gems. It's now possible to specify gem dependencies in the Rails initializer:

Rails::Initializer.run do |config|
  config.gem "haml"
  config.gem "chronic", :version => '0.2.3'
  config.gem "hpricot", :source => "http://code.whytheluckystiff.net"
  config.gem "aws-s3", :lib => "aws/s3"
end

When your application starts, it'll automatically locate and load the specified gems. On deployment, rake gems:install will try to download and install all required but missing gems. Gems are not only searched in the system-wide gem path, but also in the vendor/gems path in your Rails application. rake gems:unpack GEM=gemname can be used to unpack a gem into this local gem path, so that the gems can be either checked into the source repository or they can be installed locally on deployment.

As usual, there's more about this in Ryan's blog in his post about Gem Dependencies.

More...

Those are only the major features, I'm looking forward to most. Of course, there's lot of other stuff, like ActiveRecord find(:last), Ruby 1.9 compatibility, better timezone support and migrations that are ordered by timestamps instead of numbers. The latter will make it easier to work on a Rails project in a distributed environment, like with GIT, a distributed version control system that seems to get more and more attention in the Rails scene recently. Speaking of GIT, some support already has been included in Rails, like script/generate and script/plugin being able to handle GIT.

To sum it up, it's like Courtenay said: Exciting times in Rails land. Rails 2.1 will be certainly an interesting one for us all.