Parallel to the addition of :except and :only options to Rails’ routing comes the removal of all those formatted_xxx named routes. Turns out these routes ate up a lot of memory and served minimal purpose.
So with this routes.rb definition:
| 1 2 3 | ActionController::Routing::Routes.draw do |map| map.resources :articles end |
you’ll no longer have any of the formatted_xxx url helpers available. However, you will be able to get the same functionality by passing in a :format option to the base view helper methods:
| 1 2 3 4 | # Old => New formatted_article_path(article, :xml) => article_path(article, :format => :xml) formatted_new_article_path(:json) => new_article_path(:format => :json) # etc... |
A minimal change that will have a big impact on the memory consumption of each of your Rails processes – especially if you’ve got a complex app with lots of routes.
If you’re on Edge Rails you’re going to start seeing deprecation warnings where you reference these formatted url helpers, so you’ve got some warning before you have to make the switch.
tags: ruby, rubyonrails
Parallel to the addition of :except and :only options to Rails’ routing comes the removal of all those formatted_xxx named routes. Turns out these routes ate up a lot of memory and served minimal purpose.
So with this routes.rb definition:
| 1 2 3 | ActionController::Routing::Routes.draw do |map| map.resources :articles end |
you’ll no longer have any of the formatted_xxx url helpers available. However, you will be able to get the same functionality by passing in a :format option to the base view helper methods:
| 1 2 3 4 | # Old => New formatted_article_path(article, :xml) => article_path(article, :format => :xml) formatted_new_article_path(:json) => new_article_path(:format => :json) # etc... |
A minimal change that will have a big impact on the memory consumption of each of your Rails processes – especially if you’ve got a complex app with lots of routes.
If you’re on Edge Rails you’re going to start seeing deprecation warnings where you reference these formatted url helpers, so you’ve got some warning before you have to make the switch.
tags: ruby, rubyonrails
Rails 2.2 (RC1) was just released, let’s take a peek at some of the major new features (as determined by yours truly – feel free to pipe up with features that I’ve missed).
Rails 2.2.2 requires rubygems v 1.3.1, so before you upgrade make sure to do a sudo gem update --system.
Rails 2.2 FeaturesNote: The nested model mass assignment feature previously discussed has been delayed until after this 2.2 release. Just needs a little more time in the oven, apparently.
Enjoy, folks.
tags: ruby, rubyonrails
Just added to Edge Rails is the ability to exclude and include the default generated routes in your mapping configuration. Previously, map.resources :articles would generate routes to all seven default actions on the ArticlesController (index, create, new, edit, show, update, destroy). You can now tell your routes configuration to only generate a subset of those actions, or to exclude a subset of those actions:
| 1 2 3 4 5 6 7 8 | # Only generate the :index route of articles map.resources :articles, :only => :index # Generate all but the destroy route of articles map.resources :articles, :except => :destroy # Only generate the non-modifying routes of articles map.resources :articles, :only => [:index, :show] |
Note that you can use the :all and :none values to denote all or none of the default routes.
| 1 2 3 4 5 | # Don't generate any default article routes, just the approve route map.resources :articles, :except => :all, :member => { :approve => :put } # Same map.resources :articles, :only => :none, :member => { :approve => :put } |
You should also note that these options will be inherited by nested resources that don’t override them. For instance, in this example, comments would only have the :index and :show routes exposed:
| 1 2 3 4 5 | # Because comments are nested within articles, they too will only # have the index and show routes generated. map.resources :articles, :only => [:index, :show] do |article| article.resources :comments end |
Specifying either an :except or :only option in a nested resource will override its parent resource’s options. E.g. this routing will result in comments having all actions but :show routed:
| 1 2 3 4 5 | map.resources :articles, :only => [:index, :show] do |article| # My :except option overrides my parent resources :only article.resources :comments, :except => :show end |
The motivation behind this feature is that complex routing consumes a lot of memory. So eliminating unnecessary and unused routes can significantly reduce your memory consumption. The holy grail of this, however, will be when you can specify which routes should be formatted (json, xml etc…) as this is essentially doubling the number of default routes when very rarely does every call need to be API-accessible.
tags: ruby, rubyonrails
Just added to Edge Rails is the ability to exclude and include the default generated routes in your mapping configuration. Previously, map.resources :articles would generate routes to all seven default actions on the ArticlesController (index, create, new, edit, show, update, destroy). You can now tell your routes configuration to only generate a subset of those actions, or to exclude a subset of those actions:
| 1 2 3 4 5 6 7 8 | # Only generate the :index route of articles map.resources :articles, :only => :index # Generate all but the destroy route of articles map.resources :articles, :except => :destroy # Only generate the non-modifying routes of articles map.resources :articles, :only => [:index, :show] |
Note that you can use the :all and :none values to denote all or none of the default routes.
| 1 2 3 4 5 | # Don't generate any default article routes, just the approve route map.resources :articles, :except => :all, :member => { :approve => :put } # Same map.resources :articles, :only => :none, :member => { :approve => :put } |
You should also note that these options will be inherited by nested resources that don’t override them. For instance, in this example, comments would only have the :index and :show routes exposed:
| 1 2 3 4 5 | # Because comments are nested within articles, they too will only # have the index and show routes generated. map.resources :articles, :only => [:index, :show] do |article| article.resources :comments end |
Specifying either an :except or :only option in a nested resource will override its parent resource’s options. E.g. this routing will result in comments having all actions but :show routed:
| 1 2 3 4 5 | map.resources :articles, :only => [:index, :show] do |article| # My :except option overrides my parent resources :only article.resources :comments, :except => :show end |
The motivation behind this feature is that complex routing consumes a lot of memory. So eliminating unnecessary and unused routes can significantly reduce your memory consumption. The holy grail of this, however, will be when you can specify which routes should be formatted (json, xml etc…) as this is essentially doubling the number of default routes when very rarely does every call need to be API-accessible.
tags: ruby, rubyonrails
We talked about the new conditional GET support in rails a couple months ago. As some of the comments alluded, the feature was somewhat cumbersome to use – especially by Ruby standards. Well, the feature has since been refined. So, read the original post to get the gist and come back here for the sugar.
Instead of manually setting properties directly on the response and querying the request to see if it’s fresh we have some higher-level accessors we can use. Observe (extending our previous example):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class ArticlesController < ApplicationController def show @article = Article.find(params[:id]) # If the request is stale according to the given timestamp and etag value # (i.e. it needs to be processed again) then execute this block if stale?(:last_modified => @article.published_at.utc, :etag => @article) respond_to do |wants| # ... normal response processing end end # If the request is fresh (i.e. it's not modified) then you don't need to do # anything. The default render checks for this using the parameters # used in the previous call to stale? and will automatically send a # :not_modified. So that's it, you're done. end |
If you don’t have any special response processing and are using the default rendering mechanism (i.e. you’re not using respond_to or calling render yourself) then you’ve got an easy helper in fresh_when:
| 1 2 3 4 5 6 7 8 9 | class ArticlesController < ApplicationController # This will automatically send back a :not_modified if the request is fresh, and # will render the default template (article.*) if it's stale. def show @article = Article.find(params[:id]) fresh_when :last_modified => @article.published_at.utc, :etag => @article end end |
There you have it, the new and improved conditional GET support in Rails 2.2.
tags: ruby, rubyonrails
Those of you using Chris Wanstrath’s slick little try trick will now have access to that functionality in Rails with this ActiveSupport update.
Basically, try lets you attempt to invoke a method on an object without worrying about a NoMethodError being raised. If the method doesn’t exist, or if the target object nil, then nil will be returned without exceptions:
| 1 2 3 4 5 | # No exceptions when receiver is nil nil.try(:destroy) #=> nil # Useful when chaining potential nil items User.admins.first.try(:address).try(:reset) |
Just a small little bit of syntactical candy pulled in from the community.
Update: You can now also use this trick for methods with arguments
tags: ruby, rubyonrails
It’s pretty common to want SQL queries against a particular table to always be sorted the same way, and is one of the reasons why I added the ordered scope to the utility scopes gem. For instance, when dealing with collections of articles it is reasonable to expect that the default ordering be most recent first, i.e. created_at DESC. Well now you can specify default ordering, and other scopes, in edge rails directly in your ActiveRecord model.
Taking our Article example let’s specify the aforementioned default ordering:
| 1 2 3 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end |
Now, when any find method or named_scope is executed the default ordering comes along for the ride:
| Article.find(:all) #=> "SELECT * FROM `articles` ORDER BY created_at DESC" |
The same holds true for any named scopes you might have:
| 1 2 3 4 5 6 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true } end Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC" |
There are some things to keep in mind, however. First is that scopes like :join, :offset, :limit and :order will get clobbered by the innermost rule. For example, here the default scope ordering loses out to the named_scope ordering.
| 1 2 3 4 5 6 7 8 9 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true }, :order => 'published_at DESC' end # published_at DESC clobbers default scope Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY published_at DESC" |
Also keep in mind that the default scoping is inherited, so child-classes of Article will have the same default scoping.
And for those occasions when you want to override or remove your default scope, just use with_exclusive_scope:
| 1 2 3 4 5 6 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end # Ignore other scoping within this block Article.with_exclusive_scope { find(:all) } #=> "SELECT * FROM `articles` |
default_scope is a great way to specify reasonable query defaults and relieve yourself of having to create your own named_scopes for that purpose or specify such conditions at every invocation. Yes, I just said ‘relieve yourself’... giggity giggity
tags: ruby, rubyonrails
Ever wonder why your ArticlesController has to live in a file called articles_controller.rb but that special ApplicationController gets to hang out in application.rb? Well no more.. That snooty little controller will now live in application_controller.rb and abide by the same naming restrictions that the rest of us do.
By the time Rails 2.3/3.0 rolls around this rename will happen for you as part of rake rails:update. However, for those of you on Edge Rails, you’ll need to make this change yourself.
tags: ruby, rubyonrails
render is one of those oft-used view-helper methods that always seems to be a little cumbersome. The most common use is to render a partial within another view:
| render :partial => 'articles/article', :locals => { :article => @article } |
But this is a lot of work for a simple operation, and now it becomes much simpler. Now the default is to assume that a partial is requested (in the past render a :file was the default) and that the final hash argument is the locals hash. Here is the above functionality using the new syntax:
| 1 2 3 4 5 6 7 8 9 | # Render the 'article' partial with an article local variable render 'articles/article', :article => @article # Or even better (same as above) render @article # And for collections, same as: # render :partial => 'articles/article', :collection => @articles render @articles |
If you’ve got some old render calls hanging around that aren’t using partials you’ll have to specify the :file option now:
| render :file => 'original' |
Hassle free partial rendering. Yay.
tags: ruby, rubyonrails
Those of you using Chris Wanstrath’s slick little try trick will now have access to that functionality in Rails with this ActiveSupport update.
Basically, try lets you attempt to invoke a method on an object without worrying about a NoMethodError being raised. If the method doesn’t exist, or if the target object nil, then nil will be returned without exceptions:
| 1 2 3 4 5 | # No exceptions when receiver is nil nil.try(:destroy) #=> nil # Useful when chaining potential nil items User.admins.first.try(:address).try(:reset) |
Just a small little bit of syntactical candy pulled in from the community.
Update: You can now also use this trick for methods with arguments
tags: ruby, rubyonrails
render is one of those oft-used view-helper methods that always seems to be a little cumbersome. The most common use is to render a partial within another view:
| render :partial => 'articles/article', :locals => { :article => @article } |
But this is a lot of work for a simple operation, and now it becomes much simpler. Now the default is to assume that a partial is requested (in the past render a :file was the default) and that the final hash argument is the locals hash. Here is the above functionality using the new syntax:
| 1 2 3 4 5 6 7 8 9 | # Render the 'article' partial with an article local variable render 'articles/article', :article => @article # Or even better (same as above) render @article # And for collections, same as: # render :partial => 'articles/article', :collection => @articles render @articles |
If you’ve got some old render calls hanging around that aren’t using partials you’ll have to specify the :file option now:
| render :file => 'original' |
Hassle free partial rendering. Yay.
tags: ruby, rubyonrails
Ever wonder why your ArticlesController has to live in a file called articles_controller.rb but that special ApplicationController gets to hang out in application.rb? Well no more.. That snooty little controller will now live in application_controller.rb and abide by the same naming restrictions that the rest of us do.
By the time Rails 2.3/3.0 rolls around this rename will happen for you as part of rake rails:update. However, for those of you on Edge Rails, you’ll need to make this change yourself.
tags: ruby, rubyonrails
It’s pretty common to want SQL queries against a particular table to always be sorted the same way, and is one of the reasons why I added the ordered scope to the utility scopes gem. For instance, when dealing with collections of articles it is reasonable to expect that the default ordering be most recent first, i.e. created_at DESC. Well now you can specify default ordering, and other scopes, in edge rails directly in your ActiveRecord model.
Taking our Article example let’s specify the aforementioned default ordering:
| 1 2 3 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end |
Now, when any find method or named_scope is executed the default ordering comes along for the ride:
| Article.find(:all) #=> "SELECT * FROM `articles` ORDER BY created_at DESC" |
The same holds true for any named scopes you might have:
| 1 2 3 4 5 6 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true } end Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC" |
There are some things to keep in mind, however. First is that scopes like :join, :offset, :limit and :order will get clobbered by the innermost rule. For example, here the default scope ordering loses out to the named_scope ordering.
| 1 2 3 4 5 6 7 8 9 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true }, :order => 'published_at DESC' end # published_at DESC clobbers default scope Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY published_at DESC" |
Also keep in mind that the default scoping is inherited, so child-classes of Article will have the same default scoping.
And for those occasions when you want to override or remove your default scope, just use with_exclusive_scope:
| 1 2 3 4 5 6 | class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end # Ignore other scoping within this block Article.with_exclusive_scope { find(:all) } #=> "SELECT * FROM `articles` |
default_scope is a great way to specify reasonable query defaults and relieve yourself of having to create your own named_scopes for that purpose or specify such conditions at every invocation. Yes, I just said ‘relieve yourself’... giggity giggity
tags: ruby, rubyonrails
Proxies are a powerful tool in software development, allowing you to transparently provide extra functionality or a slight abstraction to an underlying object. One of the more visible uses of proxies is in ActiveRecord which uses a proxy to represent its many associations. For instance, in the following article definition:
| 1 2 3 | class Article < ActiveRecord::Base has_many :comments end |
when you call article.comments what you get back is actually a proxy object that wraps the comments collection with some extra functionality like the << and build methods. Although it looks like a normal Array when you directly access comments, it’s really a proxy that’s marshaling method calls to an underlying collection or intercepting the method calls if it’s functionality it wants to handle itself. Named scopes also work in a similar manner.
That’s great and all, but these proxies are tied very specifically to their implementations within ActiveRecord .. and that’s what Roxy is intended to address. Roxy brings some serious moxie to your development with the ability to easily define and use proxies in your non ActiveRecord classes.
Let’s take a look at an example: Suppose I have a person object that has a list of parents and children (again, this is outside the scope of ActiveRecord or any other persistence framework where you might be able to do this with some other mechanism).
| 1 2 3 | class Person attr_accessor :first, :last, :parents, :children end |
If you want add functionality to a person that determines if their parents are divorced, or if they have any stepchildren you could easily enough add that functionality directly to the Person object:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person attr_accessor :first, :last, :parents, :children def initialize(first, last) @first, @last = first, last end # If my parents have different last names, then assume they're divorced def parents_divorced? parents.size > 1 and parents.collect { |parent| parent.last }.uniq.size > 1 end # Any child with a different last name than mine is considered # a step-child. def step_children children.select { |child| self.last != child.last } end end |
but this approach has always seemed very obtuse, however. If I am strictly modeling my domain to the real world, which is the approach I favor until it becomes unwieldy to do so, what I really want to do is ask a person’s parents if they’re divorced. After all, their divorce status is a property of the parents, not the person itself. With Roxy this structure is easy to model:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :first, :last, :parents, :children # Add ability to ask the parents collection if they are divorced proxy :parents do def divorced? proxy_target.size > 1 and proxy_target.collect { |parent| parent.last }.uniq.size > 1 end end # Add ability to ask the children collection for only the step-children proxy :children do def step proxy_target.select { |child| proxy_owner.last != child.last } end end def initialize(first, last) @first, @last = first, last end end # Now the following is possible: person.parents.divorced? #=> true|false person.children.step #=> [<Person...>, <Person...>] |
Roxy allows you transparently adorn existing attributes and methods with added functionality, making a more realistic domain model. This is very similar to rails’ association proxies except that you are now free to add functionality to all methods and objects.
Proxy methods are defined in the block that is passed to the proxy call. Within each proxy method you can reference the object that owns the proxy (the person instance here) as proxy_owner and the thing that is being proxied (the parents and children collections here) as proxy_target.
AdvancedYou’re not limited to proxying existing methods, you can just as easily proxy to another object using the :to option.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => ShippingMethod.all do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from all methods person.shipping.cheapest #=> <ShippingMethod...> |
If the value you want to proxy needs to be evaluated at runtime just pass in a proc. The proc should accept a single argument which will be the proxy_owner instance:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => proc { |person| ShippingMethod.available_for(person) } do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from the methods # only available to 'person' person.shipping.cheapest #=> <ShippingMethod...> |
You’ll notice that the best use of proxies is as a lightweight relationship between two things. I.e. instead of creating a whole other object to represent the relationship between a person and the various shipping methods you can quickly add functionality directly to that object-relationship as a proxy method.
A sign of abuse of this particular proxy pattern is when you reference only one of the proxy_owner or proxy_target and neither depends on the other in any way. That is usually an indication that the functionality should live solely in the referenced proxy owner/target and not in the proxy itself.
Proxy methods can also be defined as modules (as in Rails’ association extensions) for greater re-use between similar proxies with the :extend option (which can take one or more modules):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :age, :children, :parents # Re-usable functionality to find the oldest person in a collection module PersonCollection def oldest proxy_target.max { |p| p.age } end end proxy :children, :extend => PersonCollection proxy :parents, :extend => PersonCollection end # Now the following is possible: person.parents.oldest #=> <Person...> person.children.oldest #=> <Person...> |
Once you grasp the beauty, simplicity and power of proxies you’ll likely find many uses for them. They’re a great tool to have in your toolbox and Roxy would love it if she found a place in yours.
Update: Nov. 14th, 2008: Based on your feedback I just updated the gem (v0.2) to properly handle proxying methods that have arguments:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Person include Roxy::Moxie # Contrived example to show that reload value is passed through # to the proxy target def ancestors(reload = false) 1.upto(4).to_a.collect { |i| "#{reload ? 'r' : ''}ancestor#{i}" } end # Men = odd numbered ancestors proxy :ancestors do def men proxy_target.select { |a| a.include?('1') || a.include?('3') } end def women proxy_target - men end end end # Note how arg is passed through (reloaded ancestors start # with 'r') person.ancestors(true).men #=> ['rancestor1', 'rancestor3'] # Even default arg is retained (default is false, so no # ancestors have 'r' prefix) person.ancestors.men #=> ['ancestor1', 'ancestor3'] |
Installing is pretty simple…
sudo gem install yfactorial-roxy --source http://gems.github.com TeaserI hope to have an extension library up soon that utilizes Roxy to provide ActiveRecord-like association definitions in ActiveResource. Something like:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | require 'roxy' class User < ActiveResource::Base include Roxy::Moxie proxy :articles, :to => proc { |u| Article.find(:all, :params => { :user_id => u.id } } do def destroy_all proxy_target.each { |a| a.destroy } end end end # Now a remote user looks a lot like a first class active record object: user.articles #=> [<Article...>, <Article...>, ...] user.articles.destroy_all |
Stay tuned for that, and let me know where you’ve found proxies to be a great tool to have around. I’m always looking for better example scenarios.
tags: ruby
Just added to Edge Rails is the ability to exclude and include the default generated routes in your mapping configuration. Previously, map.resources :articles would generate routes to all seven default actions on the ArticlesController (index, create, new, edit, show, update, destroy). You can now tell your routes configuration to only generate a subset of those actions, or to exclude a subset of those actions:
| 1 2 3 4 5 6 7 8 | # Only generate the :index route of articles map.resources :articles, :only => :index # Generate all but the destroy route of articles map.resources :articles, :except => :destroy # Only generate the non-modifying routes of articles map.resources :articles, :only => [:index, :show] |
Note that you can use the :all and :none values to denote all or none of the default routes.
| 1 2 3 4 5 | # Don't generate any default article routes, just the approve route map.resources :articles, :except => :all, :member => { :approve => :put } # Same map.resources :articles, :only => :none, :member => { :approve => :put } |
You should also note that these options will be inherited by nested resources that don’t override them. For instance, in this example, comments would only have the :index and :show routes exposed:
| 1 2 3 4 5 | # Because comments are nested within articles, they too will only # have the index and show routes generated. map.resources :articles, :only => [:index, :show] do |article| article.resources :comments end |
Specifying either an :except or :only option in a nested resource will override its parent resource’s options. E.g. this routing will result in comments having all actions but :show routed:
| 1 2 3 4 5 | map.resources :articles, :only => [:index, :show] do |article| # My :except option overrides my parent resources :only article.resources :comments, :except => :show end |
The motivation behind this feature is that complex routing consumes a lot of memory. So eliminating unnecessary and unused routes can significantly reduce your memory consumption. The holy grail of this, however, will be when you can specify which routes should be formatted (json, xml etc…) as this is essentially doubling the number of default routes when very rarely does every call need to be API-accessible.
tags: ruby, rubyonrails
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
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
Proxies are a powerful tool in software development, allowing you to transparently provide extra functionality or a slight abstraction to an underlying object. One of the more visible uses of proxies is in ActiveRecord which uses a proxy to represent its many associations. For instance, in the following article definition:
| 1 2 3 | class Article < ActiveRecord::Base has_many :comments end |
when you call article.comments what you get back is actually a proxy object that wraps the comments collection with some extra functionality like the << and build methods. Although it looks like a normal Array when you directly access comments, it’s really a proxy that’s marshaling method calls to an underlying collection or intercepting the method calls if it’s functionality it wants to handle itself. Named scopes also work in a similar manner.
That’s great and all, but these proxies are tied very specifically to their implementations within ActiveRecord .. and that’s what Roxy is intended to address. Roxy brings some serious moxie to your development with the ability to easily define and use proxies in your non ActiveRecord classes.
Let’s take a look at an example: Suppose I have a person object that has a list of parents and children (again, this is outside the scope of ActiveRecord or any other persistence framework where you might be able to do this with some other mechanism).
| 1 2 3 | class Person attr_accessor :first, :last, :parents, :children end |
If you want add functionality to a person that determines if their parents are divorced, or if they have any stepchildren you could easily enough add that functionality directly to the Person object:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person attr_accessor :first, :last, :parents, :children def initialize(first, last) @first, @last = first, last end # If my parents have different last names, then assume they're divorced def parents_divorced? parents.size > 1 and parents.collect { |parent| parent.last }.uniq.size > 1 end # Any child with a different last name than mine is considered # a step-child. def step_children children.select { |child| self.last != child.last } end end |
but this approach has always seemed very obtuse, however. If I am strictly modeling my domain to the real world, which is the approach I favor until it becomes unwieldy to do so, what I really want to do is ask a person’s parents if they’re divorced. After all, their divorce status is a property of the parents, not the person itself. With Roxy this structure is easy to model:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :first, :last, :parents, :children # Add ability to ask the parents collection if they are divorced proxy :parents do def divorced? proxy_target.size > 1 and proxy_target.collect { |parent| parent.last }.uniq.size > 1 end end # Add ability to ask the children collection for only the step-children proxy :children do def step proxy_target.select { |child| proxy_owner.last != child.last } end end def initialize(first, last) @first, @last = first, last end end # Now the following is possible: person.parents.divorced? #=> true|false person.children.step #=> [<Person...>, <Person...>] |
Roxy allows you transparently adorn existing attributes and methods with added functionality, making a more realistic domain model. This is very similar to rails’ association proxies except that you are now free to add functionality to all methods and objects.
Proxy methods are defined in the block that is passed to the proxy call. Within each proxy method you can reference the object that owns the proxy (the person instance here) as proxy_owner and the thing that is being proxied (the parents and children collections here) as proxy_target.
AdvancedYou’re not limited to proxying existing methods, you can just as easily proxy to another object using the :to option.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => ShippingMethod.all do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from all methods person.shipping.cheapest #=> <ShippingMethod...> |
If the value you want to proxy needs to be evaluated at runtime just pass in a proc. The proc should accept a single argument which will be the proxy_owner instance:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => proc { |person| ShippingMethod.available_for(person) } do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from the methods # only available to 'person' person.shipping.cheapest #=> <ShippingMethod...> |
You’ll notice that the best use of proxies is as a lightweight relationship between two things. I.e. instead of creating a whole other object to represent the relationship between a person and the various shipping methods you can quickly add functionality directly to that object-relationship as a proxy method.
A sign of abuse of this particular proxy pattern is when you reference only one of the proxy_owner or proxy_target and neither depends on the other in any way. That is usually an indication that the functionality should live solely in the referenced proxy owner/target and not in the proxy itself.
Proxy methods can also be defined as modules (as in Rails’ association extensions) for greater re-use between similar proxies with the :extend option (which can take one or more modules):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :age, :children, :parents # Re-usable functionality to find the oldest person in a collection module PersonCollection def oldest proxy_target.max { |p| p.age } end end proxy :children, :extend => PersonCollection proxy :parents, :extend => PersonCollection end # Now the following is possible: person.parents.oldest #=> <Person...> person.children.oldest #=> <Person...> |
Once you grasp the beauty, simplicity and power of proxies you’ll likely find many uses for them. They’re a great tool to have in your toolbox and Roxy would love it if she found a place in yours.
Update: Nov. 14th, 2008: Based on your feedback I just updated the gem (v0.2) to properly handle proxying methods that have arguments:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Person include Roxy::Moxie # Contrived example to show that reload value is passed through # to the proxy target def ancestors(reload = false) 1.upto(4).to_a.collect { |i| "#{reload ? 'r' : ''}ancestor#{i}" } end # Men = odd numbered ancestors proxy :ancestors do def men proxy_target.select { |a| a.include?('1') || a.include?('3') } end def women proxy_target - men end end end # Note how arg is passed through (reloaded ancestors start # with 'r') person.ancestors(true).men #=> ['rancestor1', 'rancestor3'] # Even default arg is retained (default is false, so no # ancestors have 'r' prefix) person.ancestors.men #=> ['ancestor1', 'ancestor3'] |
Installing is pretty simple…
sudo gem install yfactorial-roxy --source http://gems.github.com TeaserI hope to have an extension library up soon that utilizes Roxy to provide ActiveRecord-like association definitions in ActiveResource. Something like:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | require 'roxy' class User < ActiveResource::Base include Roxy::Moxie proxy :articles, :to => proc { |u| Article.find(:all, :params => { :user_id => u.id } } do def destroy_all proxy_target.each { |a| a.destroy } end end end # Now a remote user looks a lot like a first class active record object: user.articles #=> [<Article...>, <Article...>, ...] user.articles.destroy_all |
Stay tuned for that, and let me know where you’ve found proxies to be a great tool to have around. I’m always looking for better example scenarios.
tags: ruby
Proxies are a powerful tool in software development, allowing you to transparently provide extra functionality or a slight abstraction to an underlying object. One of the more visible uses of proxies is in ActiveRecord which uses a proxy to represent its many associations. For instance, in the following article definition:
| 1 2 3 | class Article < ActiveRecord::Base has_many :comments end |
when you call article.comments what you get back is actually a proxy object that wraps the comments collection with some extra functionality like the << and build methods. Although it looks like a normal Array when you directly access comments, it’s really a proxy that’s marshaling method calls to an underlying collection or intercepting the method calls if it’s functionality it wants to handle itself. Named scopes also work in a similar manner.
That’s great and all, but these proxies are tied very specifically to their implementations within ActiveRecord .. and that’s what Roxy is intended to address. Roxy brings some serious moxie to your development with the ability to easily define and use proxies in your non ActiveRecord classes.
Let’s take a look at an example: Suppose I have a person object that has a list of parents and children (again, this is outside the scope of ActiveRecord or any other persistence framework where you might be able to do this with some other mechanism).
| 1 2 3 | class Person attr_accessor :first, :last, :parents, :children end |
If you want add functionality to a person that determines if their parents are divorced, or if they have any stepchildren you could easily enough add that functionality directly to the Person object:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person attr_accessor :first, :last, :parents, :children def initialize(first, last) @first, @last = first, last end # If my parents have different last names, then assume they're divorced def parents_divorced? parents.size > 1 and parents.collect { |parent| parent.last }.uniq.size > 1 end # Any child with a different last name than mine is considered # a step-child. def step_children children.select { |child| self.last != child.last } end end |
but this approach has always seemed very obtuse, however. If I am strictly modeling my domain to the real world, which is the approach I favor until it becomes unwieldy to do so, what I really want to do is ask a person’s parents if they’re divorced. After all, their divorce status is a property of the parents, not the person itself. With Roxy this structure is easy to model:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :first, :last, :parents, :children # Add ability to ask the parents collection if they are divorced proxy :parents do def divorced? proxy_target.size > 1 and proxy_target.collect { |parent| parent.last }.uniq.size > 1 end end # Add ability to ask the children collection for only the step-children proxy :children do def step proxy_target.select { |child| proxy_owner.last != child.last } end end def initialize(first, last) @first, @last = first, last end end # Now the following is possible: person.parents.divorced? #=> true|false person.children.step #=> [<Person...>, <Person...>] |
Roxy allows you transparently adorn existing attributes and methods with added functionality, making a more realistic domain model. This is very similar to rails’ association proxies except that you are now free to add functionality to all methods and objects.
Proxy methods are defined in the block that is passed to the proxy call. Within each proxy method you can reference the object that owns the proxy (the person instance here) as proxy_owner and the thing that is being proxied (the parents and children collections here) as proxy_target.
AdvancedYou’re not limited to proxying existing methods, you can just as easily proxy to another object using the :to option.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => ShippingMethod.all do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from all methods person.shipping.cheapest #=> <ShippingMethod...> |
If the value you want to proxy needs to be evaluated at runtime just pass in a proc. The proc should accept a single argument which will be the proxy_owner instance:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :address proxy :shipping, :to => proc { |person| ShippingMethod.available_for(person) } do def cheapest proxy_target.min { |m| m.cost_from(proxy_owner.address) } end end end # Find the cheapest shipping method from the methods # only available to 'person' person.shipping.cheapest #=> <ShippingMethod...> |
You’ll notice that the best use of proxies is as a lightweight relationship between two things. I.e. instead of creating a whole other object to represent the relationship between a person and the various shipping methods you can quickly add functionality directly to that object-relationship as a proxy method.
A sign of abuse of this particular proxy pattern is when you reference only one of the proxy_owner or proxy_target and neither depends on the other in any way. That is usually an indication that the functionality should live solely in the referenced proxy owner/target and not in the proxy itself.
Proxy methods can also be defined as modules (as in Rails’ association extensions) for greater re-use between similar proxies with the :extend option (which can take one or more modules):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | require 'roxy' class Person # Add in proxy ability include Roxy::Moxie attr_accessor :age, :children, :parents # Re-usable functionality to find the oldest person in a collection module PersonCollection def oldest proxy_target.max { |p| p.age } end end proxy :children, :extend => PersonCollection proxy :parents, :extend => PersonCollection end # Now the following is possible: person.parents.oldest #=> <Person...> person.children.oldest #=> <Person...> |
Once you grasp the beauty, simplicity and power of proxies you’ll likely find many uses for them. They’re a great tool to have in your toolbox and Roxy would love it if she found a place in yours.
InstallationInstalling is pretty simple…
sudo gem install yfactorial-roxy --source http://gems.github.com TeaserI hope to have an extension library up soon that utilizes Roxy to provide ActiveRecord-like association definitions in ActiveResource. Something like:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | require 'roxy' class User < ActiveResource::Base include Roxy::Moxie proxy :articles, :to => proc { |u| Article.find(:all, :params => { :user_id => u.id } } do def destroy_all proxy_target.each { |a| a.destroy } end end end # Now a remote user looks a lot like a first class active record object: user.articles #=> [<Article...>, <Article...>, ...] user.articles.destroy_all |
Stay tuned for that, and let me know where you’ve found proxies to be a great tool to have around. I’m always looking for better example scenarios.
tags: ruby
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
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
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
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