I've looked at over 10 pages trying to find the benefit of a scope over any other ActiveRecord class method that returns an ActiveRecord::Relation.
In the following for example why are scopes better than the alternative below it which do the same thing:
#scope :pat1, lambda {{:conditions => ["name like ?", 'J%']}}
#scope :pat2, lambda {{:conditions => ["id > 5"]}}
def self.pat1
where("name like ?", 'J%')
end
def self.pat2
where("id > 5")
end
def patx
self.class.pat1.pat2.first
end
The documentation over and over again says that scopes are beneficial because they can be chained...
"All scope methods will return an ActiveRecord::Relation object which will allow for further methods (such as other scopes) to be called on it."
-guides.rubyonrails.org
"The main reason scopes are better than plain class methods is that they can be chained with other methods"
http://ruby.railstutorial.org
...but the alternative above can also be chained producing the same results.
Just trying to figure out if there's an emperor's new clothes thing going on here. Even from a syntactic standpoint there appears to be no benefit. Are they faster- some sources vaguely suggest that.
ActiveRecord scopes are really just syntax sugar wrapped in a best practice, as noted already.
In the 2.x days of Rails, when they were called "named_scope", they mattered a bit more. They allowed easy chaining of conditions for generating a query. With the improvements in Rails 3.x with Arel, it is simple to create functions for query relations, as you noted. Scopes just provide a simple and elegant solutions for chainable, predefined queries. Having all the scopes at the top of a model improves the readability and helps shows how the model is used.
When you write a scope, it is essentially doing the same thing. Here is what the Rails source looks like:
def scope(name, scope_options = {})
name = name.to_sym
valid_scope_name?(name)
extension = Module.new(&Proc.new) if block_given?
scope_proc = lambda do |*args|
options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
relation = scoped.merge(options)
extension ? relation.extending(extension) : relation
end
singleton_class.send(:redefine_method, name, &scope_proc)
end
The benefits to scopes in this case are that they are the idiomatic way of defining queries, in some cases fewer lines of code, and you can do extensions.
The example in the source looks like this:
scope :red, where(:color => 'red') do
def dom_id
'red_shirts'
end
end
Which allows you to call Model.red.dom_id.
Yes, they are syntactic short-cuts that basically represent the methods have you have found.
Why better?
The most immediate effect is that 2 lines code is way easier to read and maintain than 9 lines of code.
Rails always seeks a DRY approach and here the repeated def self.method end's are obscuring the actual code.
There are a few very interesting differences between scopes and class methods that return relations.
It is easier to deal with nil parameters for scopes with a simple param.present? check, for class methods you must explicitly return a non-nil relation if a param would cause a nil relation.
Scopes are more easily extensible than class methods. Simply pass a block (for instance to deal with pagination) to add the methods. Class methods can be extended but not as elegantly.
For the full rundown see this post from Plataformatec.
Related
May be its weird for some people about the question. By looking at the syntax its identifiable as class method.
Model.find_by_*
So if its class method it should be defined either in model we created or in
ActiveRecord::Base
So my question is how rails manages to add these methods and makes us available.
Examples like
Model.find_by_id
Model.find_by_name
Model.find_by_status
and etc.
You need to look at ActiveRecord::FinderMethods. Here you can find more details.
Internally, it fires a WHERE query based on attributes present in find_by_attributes. It returns the first matching object.
def find_by_attributes(match, attributes, *args)
conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
result = where(conditions).send(match.finder)
if match.bang? && result.nil?
raise RecordNotFound, "Couldn't find #{#klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
else
yield(result) if block_given?
result
end
end
There is also find_all_by_attributes that returns all matching records.
Rails are using ruby metaprogramming method_missing for that. The method find_by_name is not in a model, instead of this rails are taking name as first argument and it calls it like find_by(name: ?) which is calling where(name: ?).take
I have an admittedly ugly query to do, to find a particular role related to the current role. This line produces the correct result:
#person_event_role.event_role.event.event_roles.
joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).
first.person_event_roles.first.person
(You can infer the associations from the plurality of those calls)
The only way to get this information requires a ton of knowledge of the structure of the database, but to remove the coupling... It would require filling in a bunch of helper functions in each step of that chain to give back the needed info...
I think the thing to do here is to create the helper functions where appropriate. I'm unclear what the beginning of your association chain is here, but I'd probably assign it a method #event that returns event_role.event. From there, an event has an #boss_role, or whatever makes sense semantically, and that method is
event_roles.joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).first
Finally, also on the Event model, there's a #boss method, which gets
boss_roles.first.person_event_roles.first.person
So, your original query becomes
#person_event_role.event.boss
Each leg of the chain is then self-contained and easy to understand, and it doesn't require the beginning of your chain to be omniscient about the end of it. I don't fully comprehend the full reach of these associations, but I'm pretty sure that just breaking it into three or four model methods will give you the clean reading and separation of concerns you're looking for. You might even break it down further for additional ease of reading, but that becomes a question of style.
Hope that helps!
The following is by the original questioner
I think I followed this advice and ended up with:
#person_event_role.get_related_event_roles_for('Boss').first.filled_by.first
#person_event_role:
def get_related_event_roles_for(role)
event.event_roles_for(role)
end
def event
event_role.event
end
#event:
def event_roles_for(role)
event_roles.for_role(role)
end
#event_role:
scope :for_role, lambda {|role| joins(:mission_role).where(:mission_roles => {:title => role})}
def filled_by
person_event_roles.collect {|per| per.person}
end
I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.
Is Rails' find(x) method on a model lazy? If not, what is the equivalent?
I am new to Rails, so I found myself writing scopes like this:
class Course < ActiveRecord::Base
scope :by_instructor_id, lambda { |instructor_id| where(:instructor_id => instructor_id) }
scope :by_course_template_id, lambda { |course_template_id| where(:course_template_id => course_template_id ) }
scope :by_company_id, lambda { |company_id| joins(:instructor).merge(CompanyUser.by_company_id(company_id)) }
end
It's not a lot of work, but now I'm asking myself... if Rails provided these with a scope, I wouldn't have to write them.
So, does Rails offer them? Can I do something like the below code and only make it do 1 database call?
Company.find(params[:id]).instructors.courses
instead of
Course.by_company_id(params[:id])
Which is correct? I know Course.by_company_id(params[:id]) is only 1 database call. It is very familiar to writing SQL or queries in Hibernate. But if you can write it the other way, maybe one should?
However, I don't want to write Company.find(params[:id]).instructors.courses if it results in more than 1 database call. I can see the advantage though because it means never having to write the 3 scopes I showed you above, but I am worried that Company.find(x) is not lazy. Is it?
Try using #scoped method on a model before calling #find:
user = User.scoped.instructors.courses.find(params[:id])
To make find by id query lazy you can add new method to your controller and then add this method as helper.
# company
def company
#company ||= Company.find(params[:id])
end
helper :company
#view
<%= company.name %>
To get more information you can check great RailsCast - Decent Exposure
Mongoid has built-in versioning when you mix-in the Mongoid::Versioning module. It works really well for me but I'm inelegantly working with versions on a model. Let me give you an example. Assume I'm building a blog app (I'm not).
My model is Post. Let's say I wanted to find all previous published versions of a single post. The following works:
post = Post.first # just grab something
published_posts = post.versions.find_all{ |v| v.published == true }
Etc. Then I could do something with published_posts or whatever. I'd love to make a named scope for this so I'm not putting the find_all block in my view but I can't figure out how to override the built-in functionality of the Version class.
I've tried various scopes in my Post class. For example:
# I can't get this and other variations on this to work
scope :versions_published,
:where => 'self.versions.find_all{ |v| v.published == true }'
I also tried monkey patching the Version class but it doesn't like it very much. I have a workaround, I was just hoping to learn more about the built-in Versioning Mongoid provides, especially how to extend the Version class.
The scope you've defined is very, very wrong for two fundamental reasons:
You can't pass a string to the :where argument. You need to pass in a hash
scopes are defined on the class (Post in this case), so you cannot reference self.versions since self refers to the Post class and not to any specific post
The solution is to use the matches helper in Mongoid (which is a shortcut to Mongo's $elemMatch):
class Post
#...
scope :versions_published, where(:versions.matches => {:published => true})
#...
end
I would not recommend monkeypatching the Version class unless you really know what you are doing.