Learn and Love the Scope Method
If you want to optimize your code and minimize the complexity we can increasing the opportunity for code reuse is by leveraging the Active Record scoping methods.
For Example
class RemoteProcess < ActiveRecord::Base
def self.find_top_running_processes(limit=5)
find(:all,
:conditions => "state = 'running'",
:order => "percent_cpu desc",
:limit => limit)
end
def self.find_top_running_system_processes(limit=5)
find(:all,
:conditions => "state = 'running' and ( owner in ('root', 'mysql') )",
:order => "percent_cpu desc",
:limit => limit)
end
end
We can clean up this method and make the components reusable by employing named scopes. The scope method defines class methods on your model that can be chained together and combined into one SQL query.
A scope can be defined by a hash of options that should be merge into the find call or by a lambda that can take arguments and return such a hash.
When you call a scope, you get back an ActiveRecord::Relation object, which walks and talks just like the array you would have gotten back from find.
You could use scopes as follows the rewrite the preceding finder:
class RemoteProcess < ActiveRecord::Base
scope :running, where(:state => 'Running')
scope :system, where(:owner => ['root','mysql'])
scope :sorted, order("percent_cpu desc")
scope :top, lambda {|1| limit(1) }
end
RemoteProcess.running.sorted.top(10)
RemoteProcess.running.system.sorted.top(5)
We can shore this up nicely by wrapping the chain in a descriptive class method:
class RemoteProcess < ActiveRecord::Base
scope :running, where(:state => 'Running')
scope :system, where(:owner => ['root', 'mysql'])
scope :sorted, order("percent_cpu desc")
scope :top, lambda { |1| limit(1) }
def self.find_top_running_processes(limit=5)
running.sorted.top(limit)
end
def self.find_top_running_system_processes(limit=5)
running.system.sorted.top(limit)
end
end
Example 2
Now we've taken a quick look at basic scope usage, let's return to the original problem of writing advanced search methods.
For Example
class Song < ActiveRecord::Base
def self.search(title, artist, genre, published, order, limit, page)
condition_values = { :title => "%#{title}%",
:artist => "%#{artist}%",
:genre => "%#{genre}%"
}
case order
when "name" : order_clause = "name DESC"
when "length" : order_clause = "duration ASC"
when "genre" : order_clause = "genre DESC"
else
order_clause = "album DESC"
end
joins = []
conditions = []
conditions << "(title LIKE ':title')" unless title.blank?
conditions << "(artist LIKE ':artist')" unless artist.blank?
conditions << "(genre LIKE ':genre')" unless genre.blank?
unless published.blank?
conditions << "(published_on == :true OR published_on IS NOT NULL)"
end
find_opts = { :conditions => [ conditions.join("AND"), condition_values ],
:joins => joins.join(''),
:limit => limit,
:order => order_clause }
page = 1 if page.blank?
paginate(:all, find_opts.merge(:page => page, :per_page => 25))
end
end
we can clean up the preceding method by employing scopes as follows:
class Song < ActiveRecord::Base
def self.top(number)
limit(number)
end
def self.matching(column,value)
where(["#{column} like ?, "%#{value}"])
end
def self.published
where("published on is not null")
end
def self.order(col)
sql = case col
when "name" : "name desc"
when "length" : "duration asc"
when "genre" : "genre desc"
else "album desc"
end
order(sql)
end
def self.search(title, artist,genre, published)
finder = matching(:title, title)
finder = finder.matching(:artist, artist)
finder = finder.matching(:genre, genre)
finder = finder.published unless published.blank?
return finder
end
Song.search("fool", "billy", "rock", true).order("length).top(10).paginate(:page => 1)
end
While this re-implementation using scopes reduces the code size somewhat, the real benefits are elsewhere
Scope object instead of a results array:
class Song < ActiveRecord::Base
has_many :uploads
has_many :users, :through => :uploads
# top and order are implemented the same as before,
# using named_scope...
def self.search(title, artist, genre, published)
finder = where(["title LIKE ? ", "%#{title}%"])
finder = finder.where(["artist LIKE ? ", "%#{artist}%"])
finder = finder.where(["genre LIKE ? ", "%#{genre}%"])
unless published.blank?
finder = finder.where("published_on is not null")
end
return finder
end
end
No comments:
Post a Comment