Named Scope: It's Not Just for Conditions, Ya Know? 10

Posted by ryan
at 10:47 AM on Wednesday, August 20, 2008



Named scopes in Rails are great, everybody knows that. They’re usually used to create granular, chainable sets of SQL conditions that nicely encapsulate your domain query logic. Here’s a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
class Article < ActiveRecord::Base
  
  # Get all articles that have been published
  named_scope :published, :conditions => ['published = ?', true]

  # Get all articles that were created recently
  named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }

end

# Get all recently created articles that have been published
Article.published.recent #=> [<Article id: ...>, <..>]

However, as much as I use named_scope for this purpose, I also use it for some smaller and still useful functions. For instance, I find that I often need to just fetch the first X number of results for any particular query. Instead of having to call find with the :limit option you could create the following named_scope:

1
2
3
4
5
6
7
8
9
class Article < ActiveRecord::Base
  
  # Only get the first X results
  named_scope :limited, lambda { |num| { :limit => num } }

end

# Get the first 5 articles - instead of Article.find(:all, :limit => 5)
Article.limited(5) #=> [<Article id: ...>, <..>]

Hey, any less typing I’ll take, and I find myself using this limited named_scope a lot. But let’s pimp it a little so that you don’t always have to supply the number, and make it default to the per_page value that exists on the class if you’re using will_paginate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Article < ActiveRecord::Base
  
  # Only get the first X results.  If no arg is given then try to
  # use the per_page value that will_paginate uses.  If that
  # doesn't exist then use 10
  named_scope :limited, lambda { |*num|
    { :limit => num.flatten.first || (defined?(per_page) ? per_page : 10) }
  }

  def per_page; 15; end

end

# Get the first 15 articles
Article.limited #=> [<Article id: ...>, <..>]

# Get the first 5 articles
Article.limited(5) #=> [<Article id: ...>, <..>]

Note that we have to use the variable length *num argument in the lambda to allow for no arguments.

Cool, so we’ve got a handy little tool for our toolbox now. Here’s another one I find myself using that isn’t strictly a conditional scope – ordered:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Article < ActiveRecord::Base
  
  # Order the results by the given argument, or 'created_at DESC'
  # if no arg is given
  named_scope :ordered, lambda { |*order|
    { :order => order.flatten.first || 'created_at DESC' }
  }

end

# Get all articles ordered by 'created_at DESC'
Article.ordered #=> [<Article id: ...>, <..>]

# Get all articles ordered by 'updated_at DESC'
Article.ordered('updated_at DESC') #=> [<Article id: ...>, <..>]

Be careful with this one, however, as with_scope (which is really what is powering named_scope) doesn’t know how to handle multiple order clauses. So, you can only used ordered once per call chain.

I’ve bundled these scopes up into a “utility scopes:http://github.com/yfactorial/utility_scopes” plugin/gem if you think they look useful to you. I’ve also added some class-level convenience initializers to let you override the default values (like the default limit and default order clause):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Article < ActiveRecord::Base

  # This class's default ordering (if not specified
  # defaults to 'created_at DESC'
  ordered_by 'published_at DESC'
  
  # By default, return 15 results (if not specified
  # defaults to 10
  default_limit 15

end

# Get the first 15 articles ordered by 'published_at DESC'
Article.ordered.limited #=> [<Article id: ...>, <..>]

# Get the first 15 articles ordered by 'popularity ASC'
Article.ordered('popularity ASC').limited #=> [<Article id: ...>, <..>]

# Get the first 20 articles ordered by 'popularity ASC'
Article.ordered('popularity ASC').limited(20) #=> [<Article id: ...>, <..>]

Need a little something else? How about this with scope I’ve included which will eager load the specified associations:

1
2
3
4
5
6
7
8
9
class Article < ActiveRecord::Base
  has_many :comments
  has_many :contributors, :class_name => 'User'
end

# Get the first 10 articles along with their comments, comment authors and article contributors
# This is equivalent to
# Article.limit(10).find(:all, :include => [{ :comments => :author }, :contributors])
Article.limit(10).with({ :comments => :author }, :contributors)

You can get all these goodies yourself by doing the following in your Rails 2.1 app. In config/environment.rb specify the gem dependency:

1
2
3
4
5
Rails::Initializer.run do |config|
  # ...
  config.gem "yfactorial-utility_scopes", :lib => 'utility_scopes', 
    :source => 'http://gems.github.com/'
end

And then to get the utility_scopes gem actually installed on your system:


rake gems:install GEM=yfactorial-utility_scopes

Or you can just install the gem as you normally would:


sudo gem install yfactorial-utility_scopes -s http://gems.github.com

Independent of whether or not you find these scopes useful, remember that named_scope is all up in your queries’ bidness – not just your queries’ conditions

Have some utility scopes you find to be indispensable? Let me know here or send me a request on github (user is yfactorial).

tags: ruby, rubyonrails

What's New in Edge Rails: Simpler Conditional Get Support (ETags) 13

Posted by ryan
at 8:32 PM on Wednesday, August 13, 2008



Conditional-gets are a facility of the HTTP spec that provide a way for web servers to tell browsers that the response to a GET request hasn’t changed since the last request and can be safely pulled from the browser cache.

They work by using the HTTP_IF_NONE_MATCH and HTTP_IF_MODIFIED_SINCE headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server’s version then the server only needs to send back an empty response with a not modified status.

It is the server’s (i.e. our) responsibility to look for a last modified timestamp and the if-none-match header and determine whether or not to send back the full response. With this new conditional-get support in rails this is a pretty easy task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ArticlesController < ApplicationController

  def show
    @article = Article.find(params[:id])

    # Set the response headers to accurately reflect the state of the
    # requested object(s)
    response.last_modified = @article.published_at.utc
    response.etag = @article

    # If the request's state is the same as the server's state then we know
    # we don't have to send back the whole body
    if request.fresh?(response)
      head :not_modified
    else
      respond_to do |wants|
        # normal response processing
      end
    end
end

The etag value is calculated for you with the etag= setter method. All you have to do is provide a single object or array of objects that uniquely identify this request. In this example the article itself contains all the information that uniquely identifies the state of this request. However, you may need to use more than one key in your app. For instance, if the request is user specific:


response.etag = [@article, current_user]

The request.fresh?(response) method is what will then tell you if the incoming request matches either the last-modified-since or if-none-match values of the outgoing response. If it does you can avoid passing the full body of the response back and save some bandwidth.

It’s also possible that you can avoid hitting the database all together if your application deals with completely static resources (though this is rare):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ArticlesController < ApplicationController

  def show

    # If articles don't change, the etag can be based solely
    # on items we have in the request
    response.etag = [:article, params[:id]]

    # If the request's state is the same as the server's state then we can
    # avoid the db call all together
    if request.fresh?(response)
      head :not_modified
    else
      @article = Article.find(params[:id])
      respond_to do |wants|
        ...
      end
    end
end

So be a good citizen and make your requests conditional-get compatible. It’s the right thing to do – and can make your apps more performant.

tags: ruby, rubyonrails

What's New in Edge Rails: Standard Internationalization Framework 5

Posted by ryan
at 12:41 PM on Tuesday, July 22, 2008



Internationalization (i18n) is a tough nut to crack and has long been handled in Rails by a variety of plugins. Thanks to a concerted effort by the Rails i18n team we now have a standardized framework for providing internationalization.

Rather than rehash what’s already out there, head over to Sven’s writeup of the new Rails Internationalization framework for the skinny.

It’s important to note that this does not provide actual language translations but merely a way for you to plug in other translations and internationalization implementations into your app.

tags: ruby, rubyonrails

What's New in Edge Rails: Get Your Metaclass 5

Posted by ryan
at 7:48 PM on Saturday, July 19, 2008



The ActiveSupport library now has a little nugget worth mentioning – a metaclass accessor available everywhere as metaclass.

Not sure what a metaclass (or singleton class, or eigen class) is? Let Ola explain it to you.

tags: ruby, rubyonrails

What's New in Edge Rails: Nested Model Mass Assignment 23

Posted by ryan
at 7:30 PM on Saturday, July 19, 2008



Nested models (nested forms by another name) describe the scenario when you want to create and modify values of nested attributes through a containing model. For instance, if you have an user model with many phone numbers:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base
  validates_presence_of :login
  has_many :phone_numbers
end

class PhoneNumber < ActiveRecord::Base
  validates_presence_of :area_code, :number
  belongs_to :user
end

you may want to be able to create the user and a group of phone numbers at the same time. This is what this looks like with the new mass assignment functionality of Rails keyed off of the :accessible option of the association declaration (:phone_numbers, in this case).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User < ActiveRecord::Base
  validates_presence_of :login
  has_many :phone_numbers, :accessible => true
end

ryan = User.create( {
  :login => 'ryan',
  :phone_numbers => [
    { :area_code => '919', :number => '123-4567' },
    { :area_code => '920', :number => '123-8901' }
  ]
})

ryan.phone_numbers.count #=> 2

A single hash of values being sent to User.create results in both a new user object and new associated phone numbers. Previously, you would have had to manually create your own phone_numbers= setter method on user to get this same functionality:

1
2
3
4
5
6
7
8
9
10
11
class User < ActiveRecord::Base

  ...

  def phone_numbers=(attrs_array)
    attrs_array.each do |attrs|
      phone_numbers.create(attrs)
    end
  end

end

Mass assignment now gives you this functionality for free.

This may not look like much, but it is a step in the direction of letting you use nested forms. Consider a user registration form where a user can enter their login name and their phone numbers in the same form (through the use of fields_for which will bundle nested model attributes into a single form):

1
2
3
4
5
6
7
8
<% form_for @user do |f| %>
  <%= f.text_field :login %>
  <% fields_for :phone_numbers do |pn_f| %>
    <%= pn_f.text_field :area_code %>
    <%= pn_f.text_field :number %>
  <% end %>
  <%= submit_tag %>
<% end %>

This form, when submitted to the following standard RESTful UserController, will correctly create the user and its associated phone numbers through the beauty of mass assignment.

1
2
3
4
5
6
7
8
9
10
class UserController < ApplicationController

  # Create a new user and their phone numbers with mass assignment
  def new
    @user = User.create(params[:user])
    respond_to do |wants|
      ...
    end
  end
end

Mass assignment can be used on all association types – :belongs_to, :has_one, :has_many and :has_and_belongs_to_many as long as the :accessible => true option is specified.

This is a very convenient addition to ActiveRecord, but the real zinger will come with full nested form support when you can create, update and delete these nested models directly from what is pushed down in the parameter hash of a form submission. This would allow for the functionality in this complex forms screencast with minimal hassle. What a fine day that would be.

tags: ruby, rubyonrails

What's New in Edge Rails: Easy Memoization

Posted by ryan
at 8:32 PM on Tuesday, July 15, 2008



Most people will recognize the pattern of memoization to provide a basic caching mechanism (that’s not a misspelling, it really doesn’t have an ‘r’) :

1
2
3
4
5
6
class Person < ActiveRecord::Base
  def social_security
    @social_security ||= decrypt_social_security
  end
  ...
end

The big problem with this common type of memoization is that you’ve littered your method implementation with caching logic. Caching is best applied in a transparent manner – and ActiveSupport now lets you easily insert memoization into your classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person < ActiveRecord::Base

  def social_security
    decrypt_social_security
  end

  # Memoize the result of the social_security method after
  # its first evaluation (must be placed after the target
  # method definition).
  #
  # Can pass in multiple symbols:
  #  memoize :social_security, :credit_card
  memoize :social_security
  ...
end

@person = Person.new
@person.social_security  # decrypt_social_security is invoked
@person.social_security  # decrypt_social_security is NOT invoked

memoize transparently aliases the method and stores the value of your method’s first evaluation in an instance variable – giving you the same functionality of the unrefined var ||= ... implementation with much less clutter.

This implementation also handles the case where you freeze your User object before ever calling the social_security method. Using conventional memoization, calls to social_security would give you a Can’t modify instance variable error instead of happily evaluating as you would want.

This implementation also will not execute the target method more than once if the original execution resulted in nil or false – which is a flaw of the conventional pattern. You can, however, force the target method to be invoked with the optional reload parameter:

1
2
3
# Force invocation of target method, i.e. "decrypt_social_security"
# is invoked independent of there being a cached value
@person.social_security(true)

So start giving memoize some play – it’s just the right thing to do.

Update: You can now use unmemoize_all and memoize_all to undo and redo your memoized properties.

tags: ruby, rubyonrails

What's New in Edge Rails: Easy Join Table Conditions

Posted by ryan
at 4:38 PM on Monday, July 07, 2008



For an application with anything above a moderate level of domain complexity it’s quite likely that you’ve had to perform a query utilizing a join table:

1
2
3
4
5
6
7
8
9
10
11
class Article < ActiveRecord::Base
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :articles
end

# Get all the users that have published articles
User.find(:all, :joins => :article,
  :conditions => ["articles.published = ?", true])

It always makes me feel slightly embarrassed to have to resort to using String snippets to specify my query logic, and this little bit is no exception. Well, now we can specify conditions on a join table in a more concise manner:

1
2
3
# Get all the users that have published articles
User.find(:all, :joins => :article,
  :conditions => { :articles => { :published => true } })

Note how you’re able to specify the join-table conditions as a hash whose key corresponds to the table or association name of the join table? You can now let Rails worry about forming the correct SQL condition even across complex joins.

However, don’t let the ease of this feature make you use it over a properly associated domain-model. For instance, this join query:

1
2
3
# Get all articles for a given user
Article.find(:all, :joins => :user,
  :conditions => { :users => { :id => @user.id } })

is more appropriately represented as:


@user.articles

tags: ruby, rubyonrails

What's New in Edge Rails: Custom Length Validation Tokenizer

Posted by ryan
at 8:41 PM on Sunday, July 06, 2008



The various ActiveRecord validation methods are some of the hardest working bits of ActiveRecord and yet they get so little love. In what may be a little-noticed change, you can now specify how validates_length_of parses the attribute value. While the default behavior is to just count the number of characters in the attribute value, you can now also do something like this to make sure the value has a minimum number of words:

1
2
3
validates_length_of :article, :minimum => 10,
  :too_short => "Your article must be at least %d words in length.",
  :tokenizer => lambda {|str| str.scan(/\w+/) }

Just pass a proc to the :tokenizer option that chops the attribute value into its relevant parts for measure by the length validation routine. You can do most anything, like ensure a minimum or maximum number of vowels, digits etc…

tags: ruby, rubyonrails

What's New in Edge Rails: Collection Partial Variable Naming

Posted by ryan
at 8:12 PM on Sunday, July 06, 2008



From the little-but-useful department comes a new addition to Rails that lets you explicitly name the local variable exposed to a partial template when using a collection partial. So, for instance, in this statement:


render :partial => 'employees', :collection => @workers, :as => :person

each element of the workers collection will be exposed as person within the employees template. No longer are you hostage to your template name.

tags: ruby, rubyonrails

Rails 2 Peepcode Updated w/ 2.1 Features 11

Posted by ryan
at 7:43 AM on Thursday, June 12, 2008



Though I’m a little late to the party – the Rails2 Peepcode has just been re-released with write-ups of the major new features in Rails 2.1. If you’ve already purchased the Rails2 Peepcode go on over and download your free update. If you have yet to experience the goodness of this high-quality, professionally produced and detailed publication – isn’t it time you did?

This release also features a section on staying on edge Rails with the git SCM and github.com. Lots of relevant goodies!

tags: ruby, rubyonrails