Can someone provide an example on how to use
scope
and parameters?
For example:
class Permission < ActiveRecord::Base
scope :default_permissions, :conditions => { :is_default => true }
end
I have this code that returns the default_permissions and I want to convert it to return the default permissions for a given user (user_id)
Thanks
new syntax (ruby 1.9+), that will prevent errors even if you don't supply the user -
scope :default_permissions_for, ->(user = nil) { ... }
Use lambda scopes:
scope :default_permissions_for, lambda{|user| { :conditions => { :user_id => user.id, :is_default => true } }
Be careful because not passing a parameter to a lambda when it expects one will raise an exception.
Related
I have the following Scope in my Rails app, which is used to fetch active Choices from the database based on the current_user. This works just fine, but if there is no current_user the the code fetches alle the Choices in the database. Here I just want it to fetch nothing.
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : {} }
How do I rewrite thee above to return nothing if there is no current_user?
The problem is that I'm using Pusher to push new data to the website, but if the user session expires then all data are pushed instead of nothing.. hopes this makes sense :)
As scopes return an ActiveRecord::Relation instance so it would be more correct to return empty ActiveRecord::Relation object like it's described here.
So, you have to add :none scope which does the trick:
scope :none, limit(0)
and then use it inside your scope like:
scope :active, ->(user = nil) { user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : none }
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : nil }
That is because the empty hash ({}) has no conditions, which basically means return all rows.
Based on the way your code is structured, you could make a condition that is something like :id => -1, :id => nil or 1=0 or something that is always false so it won't return any rows.
(And as was mentioned in the comment below your question, scopes should not return nil since it cannot be chained.)
Okay, at the moment I have the following in my model:
has_many :current_monitorings, :class_name => "Monitoring",
:conditions => proc { [ 'monitorings.created_at > ?', Time.now.midnight ] }
I want to add a condition to this that checks if the outlet is_active attribute is set to false. I tried doing it like this:
has_many :current_monitorings, :class_name => "Monitoring",
:conditions => proc { [ 'monitorings.created_at > ? AND outlet.is_active = ?', Time.now.midnight, 'false' ] }
However, this doesn't work. I'm probably being stupid, but any help is greatly appreciated!
In your sql, outlet.is_active should be outlets.is_active. Assuming is_active is a boolean field, just pass false and not "false":
Try this:
has_many :monitorings
def current_monitorings
monitorings.joins(:outlets).where(
'monitorings.created_at > ? AND outlets.is_active = ?',
Time.now.midnight,
false
)
end
I've seen a lot of posts regarding this, but none seem to solve my problem. I have a default_scope on a model like so:
default_scope where(:is_active => true).order('LOWER(table.name)');
I have other (normal) scopes, and I want to create an inactive scope using unscoped. I would like to define it as a scope, but it only works when defined as a class method:
# works
def self.inactive
unscoped { where(:is_active => false) }
end
# none of these work
scope :inactive, unscoped { where(:is_active => false) }
scope :inactive, with_exclusive_scope { where(:is_active => true) }
scope :inactive, unscoped.where(:is_active => false)
scope :inactive, lambda { unscoped { where(:is_active => false) } }
scope :inactive, unscoped { lambda { where(:is_active => false) } }
unscoped do
scope :inactive, where(:is_active => false)
end
Is there a way that I missed, or do I have to use a class method to define this scope?
There does not seem to be a way to do this. I opened an issue on the rails repo on github...
Try this
scope :inactive, lambda { unscoped.where(:is_active => false) }
I have a bunch of named scopes and have a method within one of them that I would like to share between the other named scopes. I've sort of accomplished this by using define_method and a lambda. However, there is still some repeated code and I'm wondering is there a better approach?
Here's a simplified example of what I've got. Assume I have a table of projects and each project has many users.
Within the User model I have...
filter_by_name = lambda { |name| detect {|user| user.name == name} }
named_scope :active, :conditions => {:active => true} do
define_method :filter_by_name, filter_by_name
end
named_scope :inactive, :conditions => {:active => false} do
define_method :filter_by_name, filter_by_name
end
named_scope :have_logged_in, :conditions => {:logged_in => true} do
define_method :filter_by_name, filter_by_name
end
Then I would use it like...
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.logged_in
more_users = logged_in_users.filter_by_name "John"
Here's an entirely different solution that is probably more in spirit with what the question was asking for.
named_scope takes a block, which could be any Proc. So if you create a lambda/Proc which defines the filter_by_name method, you can pass it as the last argument to a named_scope.
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
named_scope(:active, :conditions => {:active => true}, &add_filter_by_name)
named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name)
named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name)
This will do what you're looking for. If you still think it's too repetitive, you can combine it with the techniques in mrjake2's solution to define many named scopes at once. Something like this:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
method_params.keys.each do |method_name|
send(:named_scope method_name, :conditions => method_params[method_name],
&add_filter_by_name)
end
Named scopes can be chained, so you're making this harder on your self than you need to.
The following when defined in the user model will get you what you want:
class User < ActiveRecord::Base
...
named_scope :filter_by_name, lambda { |name|
{:conditions => { :name => name} }
}
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
end
Then the following snippets will work:
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name( ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.have_logged_in
more_users = logged_in_users.filter_by_name "John"
I see that you're using detect, probably to avoid excess hits to the DB. But your examples don't use it properly. Detect only returns the first element in a list that the block returns true for. In the above example, some_users will only be a single record, the first user that is named either "Pete" or "Alan". If you're looking to get all users named "Pete" or "Alan" then you want select. And if you want select then you're better off using a named scope.
Named scopes when evaluated return a special object that contains the components necessary to build the SQL statement to generate the results, chaining other named scopes still doesn't execute the statement. Not until you try to access methods on the result set, such as calling each or map.
I would probably use a bit of metaprogramming:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
method_params.keys.each do |method_name|
send :named_scope method_name, :conditions => method_params[method_name] do
define_method :filter_by_name, filter_by_name
end
end
This way if you wanted to add more finders in the future, you could just add the method name and conditions to the methods_param hash.
You can also do this with a second named scope.
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]}
Then you can do #project.users.active.filter_by_name('Francis').
If you really need to do this with Enumerable#detect, I would define the filter_by_name method in a module which can then extend the named scopes:
with_options(:extend => FilterUsersByName) do |fubn|
fubn.named_scope :active, :conditions => {:active => true}
fubn.named_scope :inactive, :conditions => {:active => false}
fubn.named_scope :have_logged_in, :conditions => {:logged_in => true}
end
module FilterUsersByName
def filter_by_name(name)
detect {|user| user.name == name}
end
end
This adds the filter_by_name method to the class returned by all three named scopes.
I'm used to Django where you can run multiple filter methods on querysets, ie Item.all.filter(foo="bar").filter(something="else").
The however this is not so easy to do in Rails. Item.find(:all, :conditions => ["foo = :foo", { :foo = bar }]) returns an array meaning this will not work:
Item.find(:all, :conditions => ["foo = :foo", { :foo = 'bar' }]).find(:all, :conditions => ["something = :something", { :something = 'else' }])
So I figured the best way to "stack" filters is to modify the conditions array and then run the query.
So I came up with this function:
def combine(array1,array2)
conditions = []
conditions[0] = (array1[0]+" AND "+array2[0]).to_s
conditions[1] = {}
conditions[1].merge!(array1[1])
conditions[1].merge!(array2[1])
return conditions
end
Usage:
array1 = ["foo = :foo", { :foo = 'bar' }]
array2 = ["something = :something", { :something = 'else' }]
conditions = combine(array1,array2)
items = Item.find(:all, :conditions => conditions)
This has worked pretty well. However I want to be able to combine an arbitrary number of arrays, or basically shorthand for writing:
conditions = combine(combine(array1,array2),array3)
Can anyone help with this? Thanks in advance.
What you want are named scopes:
class Item < ActiveRecord::Base
named_scope :by_author, lambda {|author| {:conditions => {:author_id => author.id}}}
named_scope :since, lambda {|timestamp| {:conditions => {:created_at => (timestamp .. Time.now.utc)}}}
named_scope :archived, :conditions => "archived_at IS NOT NULL"
named_scope :active, :conditions => {:archived_at => nil}
end
In your controllers, use like this:
class ItemsController < ApplicationController
def index
#items = Item.by_author(current_user).since(2.weeks.ago)
#items = params[:archived] == "1" ? #items.archived : #items.active
end
end
The returned object is a proxy and the SQL query will not be run until you actually start doing something real with the collection, such as iterating (for display) or when you call Enumerable methods on the proxy.
I wouldn't do it like you proposed.
Since find return an array, you can use array methods to filter it, on example:
Item.find(:all).select {|i| i.foo == bar }.select {|i| i.whatever > 23 }...
You can also achive what you want with named scopes.
You can take a look at Searchlogic. It makes it easier to use conditions on
ActiveRecord sets, and even on Arrays.
Hope it helps.
You can (or at least used to be able to) filter like so in Rails:
find(:all, :conditions => { :foo => 'foo', :bar => 'bar' })
where :foo and :bar are field names in the active record. Seems like all you need to do is pass in a hash of :field_name => value pairs.