How do I use both include and join in a named scope?
Post is polymorphic
class Post
has_many :approved_comments, :class_name => 'Comment'
end
class Comment
belongs_to :post
end
Comment.find(:all, :joins => :post, :conditions =>
["post.approved = ? ", true], :include => :post)
This does not work as joins does an inner join, and include does a left out join.
The database throws an error as both joins can't be there in same query.
Have you yet tried simply omitting the :joins part of the ActiveRecord call? In my test case of a has_many association, :include will use a join if your conditions refer to the included table name. For example,
Comment.all :include => :post
will run two queries total: one for comments, and one for their posts. The Rails team says that that is better performance. However, if Rails detects that your conditions need a join,
Comment.all :include => :post, :conditions => ['post.approved = ?', true]
will run one query, since you need it.
Isn't ActiveRecord so smart?
If you want to get all the approved Posts with their Comments you can just do something like this:
Post.find(:all, :include => "comments", :conditions => ['approved = ?', true])
This will give you all the approved posts with all the comments.
You should not probably use join as it will result in too huge data set (product columns will be included for each comment related to it).
Related
I'm trying to bind a param to a join via a named scope., but I'm getting an error.
What is the correct way to do that?
class Idea < ActiveRecord::Base
#relations
has_many :votes, :inverse_of => :idea
has_one :has_voted, :class_name => 'Vote', :conditions => ['ip = :ip']
# named scopes
scope :with_vote, lambda {|ip| {
:include => [:has_voted],
# like this ??
:conditions => [:has_voted => {:conditions => {:userIp => ip}} ]
}}
end
Idea.with_vote(request.ip).all
I believe I need the condition definition in the model for it to appear in the ON clause of a JOIN, rather then in the WHERE one.
Edit I'm trying to get the following query
select Ideas.*, Votes.* from Ideas
left outer join Votes
on Votes.Idea_id = Idea.id AND Votes.ip = {request.ip}
I do not think you can use incomplete conditions in an association.
If I understand correctly, you need Idea has many votes and votes records the request.ip and idea id.
You want the scope to retrieve all ideas your current request ip voted for.
class Idea
has_many :votes
scope :with_vote_from_ip, lambda {|ip| {
:include => [:votes],
:conditions => ['votes.ip = ?', ip]
}}
end
but if you want all ideas including only votes from current up you need extra conditions on the outer join. I think this is not possible without sql fragment:
class Idea
has_many :votes
scope :with_vote_from_ip, lambda {|ip| {
:joins => 'left outer join Votes on Votes.Idea_id = Idea.id AND Votes.ip = #{ip}'
}}
end
now Idea.with_vote_from_ip(request.ip).all should work.
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
I'm trying to pull out Post data, while eager loading the User and Comment data as well, but with the limitation of not loading the Comments which have been blocked (TINYINT field in the Comment table). The following works when there are comments present, but it's causing issues when I load posts that don't have any comments yet:
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => "comments.blocked = 0")
Any suggestions on how I can run this query such that it will work when no comments are present? Thanks.
What error does it give when you try to do that on a post that has no comments?
Update:
What about this variation?
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => {:comments => {:blocked => false}})
Conditions on eagerly loaded associations is sort of unusual. Perhaps you should be using the :joins option instead? Or skip eagerly loading the post (since it's just a single one), and have a named scope for the non-blocked comments to use in your view. Something like this perhaps:
#post = Post.find(params[:id])
#comments = #post.comments.where(:blocked => false).all(:include => :user)
(Just typing off the cuff here, not certain that's exactly the right syntax for you)
OK, so after a bit of fiddling, here's the answer (which may seem obvious to some, but the count() function in MySQL was giving me some grief):
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => "comments.blocked = 0 OR posts.comments_count = 0")
comments_count is a counter_cache field, so I'm getting around the explicit use of the MySQL count() function. It feels like a kludge, but for now I'm OK with that. If anyone has a more elegant solution, please let me know!
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.
I love making named scopes for rails. however, I ran into somewhat of a pickle.
Ive gotten pretty comfortable using named scopes for joins like so:
named_scope :foo, :joins => :bar, :conditions => "bar_attribute = 'something'"
Now pretend I have a table called baz which is contains a foreign key from the bar table. I need something like this:
named_scope :foo, :joins => (:bar => :baz), :conditions => "bar.id = baz.bar_id AND baz_attribute = 'something_else'"
How possible is this?
thanks
You are not that far off. This should work for you:
named_scope: foo, :joins => {:bar => :baz}, :conditions => "bazes.your_attr = 'something_else'"
This assumes that the plural of baz is bazes. You don't need to specify the condition that joins bar.id to bazes.bar_id, it will be inferred from :joins.
Maybe you can do this in combination with a has_many :through => ... relation.
I have the following models:
class Person < ActiveRecord::Base
has_many :images
has_one :preference
end
class Image < ActiveRecord::Base
belongs_to :person
end
class Preference < ActiveRecord::Base
belongs_to :person
end
I am trying to fetch all images that are public and at the same time eager load the people who own those images:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference], :include => :person)
It appears Rails does not like the :include (I believe because :person is referenced in 2 models). This is the error I get (which disappears when I drop the :include option):
"ActiveRecord::StatementInvalid: Mysql::Error: Not unique table/alias: 'people'"
I can get around this by writing out the actual JOIN command as a string and passing it into the :include option, but this not Rails-y so I was hoping there's a cleaner way to do this.
Any help would be much appreciated.
Thanks!
It looks like you call it "preferences", and not user_preferences. So you join should be:
:joins => [:person => :preference])
By using JOIN you are actively including the People table, so you shoudn't need to add "include" again. This should work:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference])
It might be the issue with Table Aliasing, rails doc has great details in Table Aliasing
also, post SQL here will be useful too.
You wrote :conditions => ["images.person_id", user.id] and you said that you want to load images and people who owns those images. But it looks like you are loading images that belongs to one person (not to group of people), because you specify only one user.id.
I would do it this way:
Person.find(user.id, :include => [:images, :preference], :conditions => ["preferences.image_privacy = ?", PRIVACY_PUBLIC])
It will load person and his/her images.
Probably I don't understand your problem correctly, because what I think you want to do doesn't seem logic to me.
Instead of using conditions you can try named_scope