Hi I have a named_scope in my User model as following.
named_scope :by_gender, lamdba { |gender| { :conditions => { :gender => gender } } }
I want to create other two named scopes which re use this one something like,
named_scope :male, lambda { by_gender('male') }
named_scope :female, lambda { by_gender('female') }
Any idea what to do?
You could provide class methods that perform the hardwired argument passing:
def self.male
by_gender('male')
end
def self.female
by_gender('female')
end
or, as the named_scope you are using is so simple you could cut out the by_gender scope and simply use:
named_scope :male, :conditions => {:gender => 'male'}
named_scope :female, :conditions => {:gender => 'female'}
The second option is of course conditional on you not actually requiring the by_gender scope explicitly anywhere else.
Related
I don't know why I can't figure this out, I think it should be fairly simple. I have two models (see below). I'm trying to come up with a named scope for SupplierCategory that would find all SupplierCategory(s) (including :suppliers) who's associated Supplier(s) are not empty.
I tried a straight up join, named_scope :with_suppliers, :joins => :suppliers which gives me only categories with suppliers, but it gives me each category listed separately, so if a category has 2 suppliers, i get the category twice in the returned array:
Currently I'm using:
named_scope :with_suppliers, :include => :suppliers
and then in my view I'm using:
<%= render :partial => 'category', :collection => #categories.find_all{|c| !c.suppliers.empty? } %>
Not exactly eloquent but illustrates what I'm trying to achieve.
Class Definitions
class SupplierCategory < AR
has_many :suppliers, :order => "name"
end
class Supplier < AR
belongs_to :supplier
end
Here is one more approach:
named_scope :with_suppliers, :include => :suppliers,
:conditions => "suppliers.id IS NOT NULL"
This works because Rails uses OUTER JOIN for include clause. When no matching rows are found the query returns NULL values for supplier columns. Hence NOT NULL check returns the matching rows.
Rails 4
scope :with_suppliers, { includes(:steps).where("steps.id IS NOT NULL") }
Or using a static method:
def self.with_suppliers
includes(:steps).where("steps.id IS NOT NULL")
end
Note:
This solution eager loads suppliers.
categories = SupplierCategory.with_suppliers
categories.first.suppliers #loaded from memory
class SupplierCategory < AR
has_many :supliers
def self.with_supliers
self.all.reject{ |c| c.supliers.empty? }
end
end
SupplierCategory.with_supliers
#=> Array of SuplierCategories with supliers
Another way more flexible using named_scope
class SupplierCategory < AR
has_many :supliers
named_scope :with_supliers, :joins => :supliers, :select => 'distinct(suplier_categories.id), suplier_categories.*', :having => "count(supliers.id) > 0"
end
SupplierCategory.with_supliers(:all, :limit => 4)
#=> first 4 SupplierCategories with suppliers
Simpler version:
named_scope :with_suppliers, :joins => :suppliers, :group => :id
If you want to use it frequently, consider using counter_cache.
I believe it would be something like
#model SupplierCategory
named_scope :with_suppliers,
:joins => :suppliers,
:select => "distinct(supplier_categories), supplier_categories.*",
:conditions => "suppliers.supplier_categories_id = supplier_categories.id"
Let me know if it works for you.
Edit:
Using fl00r's idea:
named_scope :with_suppliers,
:joins => :suppliers,
:select => "distinct(supplier_categories), supplier_categories.*",
:having => "count(supliers.id) > 0"
I believe this is the faster way.
User model:
class User < ActiveRecord::Base
named_scope :clients,
:conditions => "roles_users.role_id = #{Role.find_by_name('client').id}"
end
When testing, throws error:
Called id for nil, which would mistakenly be 4 -- if you really wanted (etc.)
Role fixtures:
client:
name: client
user:
name: user
Apparent problem: Rails is loading this class before it loads fixtures. When it loads the class it evaluates the named_scope. There are no roles at that point, so it blows up.
Possible solution:
named_scope :clients,
lambda { { :conditions => "roles_users.role_id = #{Role.named('client').id}" } }
However, I am not pleased with this solution, seeing as it introduces additional complexity and presumably a (small?) performance hit, just so that tests run properly. I'd like an alternative. Can you help?
The solution you propose is the correct solution. I would also recommend changing your code to:
named_scope :clients, lambda { { :conditions => ['roles_users.role_id = ?', Role.named('client').id } }
An alternative might be:
named_scope :clients, :joins => :role, :conditions => ['roles.name = ?', 'client']
You might also want to think about doing:
named_scope :with_role, lambda { |r| { :conditions => ['roles_users.role_id = ?', r.id] } }
Or even (for extra points)
Role.find_by_name('client').users
Anyway, I hope this helps.
I've created a method that allows me to return all of the Books. I'd like to limit the books returned to those that are not not loaned. What do I need to add to available_books to ensure only unloaned books are returned. Can I leverage my preexisting loaned? method?
class Book < ActiveRecord::Base
has_many :book_loans
has_many :borrowers, :through => :book_loans, :source => :person
def loaned?
book_loans.exists?(:return_date => nil)
end
def self.available_books
#books = find(:all, :order => "title")
end
end
You can modify your find to make it look like this:
find(:all, :select => "books.*", :joins => :book_loans, :conditions => ['book_loans.return_date is null'], :order => "title")
First off you might want to consider using named scopes in place defining methods, so for example the available_books method you have written could be rewritten as
named_scope :available_books, :order => "title"
Which would allow you to write Book.available_books in the same way you are doing, but in addition you can chain multiple named scopes like Book.available_books.by_author("bob") (assuming you defined another named scope called by_author which took a name as a param.
For checking if it is loaned you could try something like:
named_scope :loaned, :joins => :book_loans, :conditions => { :book_loans => { :return_date => nil } }
Alternatively you should be able to use a string for the conditions in the same way that Vincent has done.
How do I turn this into a has_one association?
(Possibly has_one + a named scope for size.)
class User < ActiveRecord::Base
has_many :assets, :foreign_key => 'creator_id'
def avatar_asset size = :thumb
# The LIKE is because it might be a .jpg, .png, or .gif.
# More efficient methods that can handle that are OK. ;)
self.assets.find :first, :conditions =>
["thumbnail = '#{size}' and filename LIKE ?", self.login + "_#{size}.%"]
end
end
EDIT: Cuing from AnalogHole on Freenode #rubyonrails, we can do this:
has_many :assets, :foreign_key => 'creator_id' do
def avatar size = :thumb
find :first, :conditions => ["thumbnail = ? and filename LIKE ?",
size.to_s, proxy_owner.login + "_#{size}.%"]
end
end
... which is fairly cool, and makes syntax a bit better at least.
However, this still doesn't behave as well as I would like. Particularly, it doesn't allow for further nice find chaining (such that it doesn't execute this find until it's gotten all its conditions).
More importantly, it doesn't allow for use in an :include. Ideally I want to do something like this:
PostsController
def show
post = Post.get_cache(params[:id]) {
Post.find(params[:id],
:include => {:comments => {:users => {:avatar_asset => :thumb}} }
...
end
... so that I can cache the assets together with the post. Or cache them at all, really - e.g. get_cache(user_id){User.find(user_id, :include => :avatar_assets)} would be a good first pass.
This doesn't actually work (self == User), but is correct in spirit:
has_many :avatar_assets, :foreign_key => 'creator_id',
:class_name => 'Asset', :conditions => ["filename LIKE ?", self.login + "_%"]
(Also posted on Refactor My Code.)
Since there are actually multiple avatar_assets ( one for each size ), you have to keep it as a has_many association.
class User < AR::B
has_many :avatar_assets, :conditions => ['filename like ?' '%avatar%'], :class_name => 'Asset'
named_scope :avatar_size, lambda { |size|
{ :conditions => [ "thumbnail = ?", size ] }
}
end
An alternative would be to put all the work in the named scope:
class User < AR::B
named_scope :avatar_for, lambda { |user, options|
if options[:size]
{ :conditions => [ "filename like ? AND thumbnail = ?", user.login, options[:size] ] }
else
{ :conditions => [ "filename like ?", user.login ] }
end
}
end
this allows you to say
Asset.avatar_for(current_user, :size => :medium)
but is less cool when you find yourself saying
current_user.avatar_for( current_user, :size => :medium )
you could add some :avatar, :avatar?, etc methods to User to clean this up.
Personally I advise you to check out the Paperclip plugin and avoid these issues entirely.
EDIT:
Per your comment, to create a condition like "show me comments by avatar-having users", I'm not sure that will do it. You'd could make a relationship like so:
class Comment
named_scope :with_avatars, :include => { :user => :avatar_assets }, :conditions => [ 'assets.thumbnail = ?', :thumb ]
end
EDIT:
Since you're only interested in caching, rather than conditions, we can drop the condition array:
named_scope :with_avatars, :include => { :user => :avatar_assets }
I revised the code above to be more workable. The key difference is to make the 'avatar'-ness of the assets easily queryable. If you can update your existing avatar_assets to have a filename including the pattern 'avatar-[login]', you can make the condition set static which is much cleaner than always having to search for the avatar based on the user login. Association extensions are another way to resolve this, however I don't think you'll be able to chain them or combine them with named scopes.
I have
class Foo < ActiveRecord::Base
named_scope :a, lambda { |a| :conditions => { :a => a } }
named_scope :b, lambda { |b| :conditions => { :b => b } }
end
I'd like
class Foo < ActiveRecord::Base
named_scope :ab, lambda { |a,b| :conditions => { :a => a, :b => b } }
end
but I'd prefer to do it in a DRY fashion. I can get the same effect by using
Foo.a(something).b(something_else)
but it's not particularly lovely.
At least since 3.2 there is a clever solution :
scope :optional, ->() {where(option: true)}
scope :accepted, ->() {where(accepted: true)}
scope :optional_and_accepted, ->() { self.optional.merge(self.accepted) }
Well I'm still new to rails and I'm not sure exactly what you're going for here, but if you're just going for code reuse why not use a regular class method?
def self.ab(a, b)
a(a).b(b)
end
You could make that more flexible by taking *args instead of a and b, and then possibly make one or the other optional. If you're stuck on named_scope, can't you extend it to do much the same thing?
Let me know if I'm totally off base with what you're wanting to do.
By making it a class method you won't be able to chain it to an association proxy, like:
#category.products.ab(x, y)
An alternative is applying this patch to enable a :through option for named_scope:
named_scope :a, :conditions => {}
named_scope :b, :conditions => {}
named_scope :ab, :through => [:a, :b]
Yes Reusing named_scope to define another named_scope
I copy it here for your convenience:
You can use proxy_options to recycle one named_scope into another:
class Thing
#...
named_scope :billable_by, lambda{|user| {:conditions => {:billable_id => user.id } } }
named_scope :billable_by_tom, lambda{ self.billable_by(User.find_by_name('Tom').id).proxy_options }
#...
end
This way it can be chained with other named_scopes.
I use this in my code and it works perfectly.
I hope it helps.
#PJ: you know, I had considered that, but dismissed it because I thought I wouldn't be able to later chain on a third named scope, like so:
Foo.ab(x, y).c(z)
But since ab(x, y) returns whatever b(y) would return, I think the chain would work. Way to make me rethink the obvious!
Check out:
http://github.com/binarylogic/searchlogic
Impressive!
To be specific:
class Foo < ActiveRecord::Base
#named_scope :ab, lambda { |a,b| :conditions => { :a => a, :b => b } }
# alias_scope, returns a Scope defined procedurally
alias_scope :ab, lambda {
Foo.a.b
}
end