I have an Event class with a HABTM relationship with a User class. I'm trying to create a Event scope that includes only Events that have 2 Users associated with it.
I currently have a Event#status method that returns the following:
def status
self.users.length == 2 ? "matched" : "not matched"
end
So now basically I'm trying to find how to write a scope that includes all "matched" events. I tried scope :matched, self.users.length == 2, which didn't work at all, but is there a similar way that I'm missing?
EDIT: This class method does this correctly, but it'd still be nice if I could encapsulate it in a scope.
def self.pending
Event.all.map { |e| e if e.status == "matched" }
end
You've got a few problems here. Right now, your status method is returning literal strings, which is a bit surprising -- it would be more common to have this return a Boolean value. Also, the name status is not descriptive -- perhaps exactly_two_users? would be better. In addition, if you use users.count instead of users.length, then the DB will do the count more efficiently.
Your scope could simply be where(:users.count => 2), I believe.
Related
While creating a Rails app I have a common problem of finding a unique object in database that matches some conditions. And if there is more than one result, some kind of error needs to be triggered.
I do it like this right now:
results = ModelName.where(attr1: condition1, attr2: condition2)
raise "too many ModelName objects for condition" if results.count > 1
unique_result = results.first
But it seems too verbose for such a common task. It would be nice to only have to write something like this:
unique_result = ModelName.unique_where(attr1: condition1, attr2: condition2)
Is there a method that returns the record if it is unique or raises an exception if more than one record is found without manually extending ActiveRecord?
It seems like there's no such built-in method, so I created an ActiveRecord::Relation extension for this purpose:
module ActiveRecordRelationExtension
extend ActiveSupport::Concern
def single_object!
if self.many?
raise "More than one instance of #{self.klass.name} returned"
end
return self.first
end
end
I'm using scope to fetch a random banner, I'm using the gem Randumb
scope :sidebar_top, -> { where(ad_type_id: 2).order_by_rand.first }
When no advert is found, all rows are returned. According to the Randumb docs, it should return nil in the instance of nothing being found.
Should I be using scope to return a single instance in the first place? It seems like the best way, but I rarely see scope examples returning less than a small subset.
Any ideas how I can return nil if nothing is found?
Thanks
Something like this? A class method instead of scope.
def self.sidebar_top
where(ad_type_id: 2).blank? ? nil : where(ad_type_id: 2).order_by_rand.first
end
You shouldn't use scope to return a single instance. Observe [1]
and as for the answer:
def self.sidebar_top
Array(where(ad_type_id: 2).order_by_rand).first # single query
end
[1] scope expects a scope return: If the proc returns nil or false, a scope is returned instead.
scope :this_or_all, ->(feeling) { where(state: feeling) if feeling.present? }
Hence, you can do this User.this_or_all(nil).this_or_all(happy).
Can somebody explain what this method does and what I can pass to it?
scoped(options = nil)
Returns an anonymous scope.
And also what the scope method does? I don't understand after reading the documentation.
In ActiveRecord, all query building methods (like where, order, joins, limit and so forth) return a so called scope. Only when you call a kicker method like all or first the built-up query is executed and the results from the database are returned.
The scoped class method also returns a scope. The scope returned is by default empty meaning the result set would not be restricted in any way meaning all records would be returned if the query was executed.
You can use it to provide an "empty" alternative like in the query_by_date example by MurifoX.
Or you can use it to combine multiple conditions into one method call, like for example:
Model.scoped(:conditions => 'id < 100', :limit => 10, :order => 'title ASC')
# which would be equivalent to
Model.where('id < 100').limit(10).order('title ASC')
The scope class method allows you to define a class method that also returns a scope, like for example:
class Model
scope :colored, lambda {|col|
where(:color => col)
}
end
which can be used like this:
Model.colored
The nice thing with scopes is that you can combine them (almost) as you wish, so the following is absolutely possible:
Model.red.where('id < 100').order('title ASC').scoped(:limit => 10)
I also strongly suggest reading through http://guides.rubyonrails.org/active_record_querying.html
I have used it in the past.When you make chained calls to the ActiveRecord query interface like this:
Model.where(:conditions).where(:more_conditions).where(:final_conditions)
Each one of them is already scoped, making the chain work without any problems. But let's say you have something like this:
Model.query_by_date(date).query_by_user(user).query_by_status(status)
scope :query_by_date, lambda { |date|
case date
when "today"
where(:date => Date.today)
when "tomorrow"
where(:date => Date.tomorrow)
else
# Any value like '' or 0 or Date.whatever
end
}
This would cause an error if the date param is not today or tomorrow. It would pick the last value and try to chain this query with the next one query_by_user, resulting in a undefined method default_scoped? for ''. But if you put a scoped method in the else condition, it would work without any flaws, because you are saying to activerecord that you pass through this method/named scope and didn't make any calls to where/find/other activerecord methods, but returned a scoped object, so you can continue chaining queries and stuff.
It would be this way in the end.
else
scoped
end
Hope you understand this simple example.
In my rails3.1 application, I'm trying to apply the following logic in one of my order model.
def digital?
line_items.map { |line_item| return false unless line_item.variant_id = '102586070' }
end
I've created a separate variant called prepaid_voucher which has id = 102586070. Despite this, the result is false...
Order has many line_items
LineItem belongs to order and variant
Variant has many line_items
Is this the best way to perform such a task and how can I fix?
First of all I think you want a double == here line_item.variant_id = '102586070', then I rather go for something like that (If I understand what you want)
def digital?
line_items.select{|line_item| line_item.variant_id == '102586070'}.any?
end
But it's hard to understand what you really want, what is the expected behavior if the id is not found?
named_scope :with_country, lambad { |country_id| ...}
named_scope :with_language, lambad { |language_id| ...}
named_scope :with_gender, lambad { |gender_id| ...}
if params[:country_id]
Event.with_country(params[:country_id])
elsif params[:langauge_id]
Event.with_state(params[:language_id])
else
......
#so many combinations
end
If I get both country and language then I need to apply both of them. In my real application I have 8 different named_scopes that could be applied depending on the case. How to apply named_scopes incrementally or hold on to named_scopes somewhere and then later apply in one shot.
I tried holding on to values like this
tmp = Event.with_country(1)
but that fires the sql instantly.
I guess I can write something like
if !params[:country_id].blank? && !params[:language_id].blank? && !params[:gender_id].blank?
Event.with_country(params[:country_id]).with_language(..).with_gender
elsif country && language
elsif country && gender
elsif country && gender
.. you see the problem
Actually, the SQL does not fire instantly. Though I haven't bothered to look up how Rails pulls off this magic (though now I'm curious), the query isn't fired until you actually inspect the result set's contents.
So if you run the following in the console:
wc = Event.with_country(Country.first.id);nil # line returns nil, so wc remains uninspected
wc.with_state(State.first.id)
you'll note that no Event query is fired for the first line, whereas one large Event query is fired for the second. As such, you can safely store Event.with_country(params[:country_id]) as a variable and add more scopes to it later, since the query will only be fired at the end.
To confirm that this is true, try the approach I'm describing, and check your server logs to confirm that only one query is being fired on the page itself for events.
Check Anonymous Scopes.
I had to do something similar, having many filters applied in a view. What I did was create named_scopes with conditions:
named_scope :with_filter, lambda{|filter| { :conditions => {:field => filter}} unless filter.blank?}
In the same class there is a method which receives the params from the action and returns the filtered records:
def self.filter(params)
ClassObject
.with_filter(params[:filter1])
.with_filter2(params[:filter2])
end
Like that you can add all the filters using named_scopes and they are used depending on the params that are sent.
I took the idea from here: http://www.idolhands.com/ruby-on-rails/guides-tips-and-tutorials/add-filters-to-views-using-named-scopes-in-rails
Event.with_country(params[:country_id]).with_state(params[:language_id])
will work and won't fire the SQL until the end (if you try it in the console, it'll happen right away because the console will call to_s on the results. IRL the SQL won't fire until the end).
I suspect you also need to be sure each named_scope tests the existence of what is passed in:
named_scope :with_country, lambda { |country_id| country_id.nil? ? {} : {:conditions=>...} }
This will be easy with Rails 3:
products = Product.where("price = 100").limit(5) # No query executed yet
products = products.order("created_at DESC") # Adding to the query, still no execution
products.each { |product| puts product.price } # That's when the SQL query is actually fired
class Product < ActiveRecord::Base
named_scope :pricey, where("price > 100")
named_scope :latest, order("created_at DESC").limit(10)
end
The short answer is to simply shift the scope as required, narrowing it down depending on what parameters are present:
scope = Example
# Only apply to parameters that are present and not empty
if (!params[:foo].blank?)
scope = scope.with_foo(params[:foo])
end
if (!params[:bar].blank?)
scope = scope.with_bar(params[:bar])
end
results = scope.all
A better approach would be to use something like Searchlogic (http://github.com/binarylogic/searchlogic) which encapsulates all of this for you.