Rails - using :include to find objects based on their child's attributes - ruby-on-rails

I have a sentence and correction model
class Sentence < ActiveRecord::Base
has_one :correction
class Correction < ActiveRecord::Base
belongs_to :sentence
and I'm trying find all sentences which don't have a correction. To do this I'm simply looking for corrections which don't exist i.e. whose id = nil. But it is failing and i can't figure out why
Sentence.find :all, :include => :correction, :conditions => {:correction => {:id => nil}}
from (irb):4>> Sentence.find :all, :include => :correction, :conditions => {:correction => {:id => nil}}
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'correction.sentence_id' in 'where clause': SELECT * FROM sentences WHERE (correction.sentence_id IS NULL)
Perhaps its the syntax or maybe just the overall approach. Can anyone help?

You can use this:
Sentence.all(:include => :correction,
:conditions => "corrections.sentence_id IS NULL")

Related

named_scope join aliasing issues

I have a named_scope which does a join. I have included the named_scope method below
named_scope :has_more_than_one,{
:select => "sessions.*",
:joins => :attenders,
:conditions => {:attenders => {:attending => true}},
:group => "sessions.id",
:having => "count(sessions.id) > 1"
Meeting.has_more_than_one.all(:group => "sessions.id",
:include => [:attenders => [ :issues ]],
:conditions => ["sessions.id in (select attenders.session_id from attenders where person_id in (select persons.id from persons where first_name like (?) or last_name like (?) or first_name like (?) or last_name like (?)))",
"#{attendee_first_name}%","#{attendee_last_name}%","#{attendee_last_name}%","#{attendee_first_name}%"])
I ran the above line and got an aliasing error
ActiveRecord::StatementInvalid: Mysql::Error: Not unique table/alias: 'member_meetings'
Is there any way to get around this..
somewhere you have a has_many something :through => :member_meetings that you're not showing here.
I'd guess that something is attenders.
Anyway, you're joining attenders in the scope's joins and then again in the :include options, thus requiring the same tables twice.
If that's correct you should define the scope like this
named_scope :has_more_than_one,{
:select => "sessions.*",
:joins => <specify the join with the condition here aliasing the tables>,
# this line would go away :conditions => {:attenders => {:attending => true}},
:group => "sessions.id",
:having => "count(sessions.id) > 1"
That joins query might look like this
:joins => "inner join member_meetings mm ON mm.meeting_id = meetings.id
inner join attendees at ON mm.attendee_id = at.id AND
at.attending is true"
Note that I aliased both member_meetings & attendees.

ActiveRecord find through has_one association child attribute

I have models like this:
class Discussion < ActiveRecord::Base
has_many :comments
has_one :special_comment, :class_name => "Comment"
end
class Comment < ActiveRecord::Base
belongs_to :discussion
# contains author
end
How can I select every Discussion through its adjoined :special_comment 'author' association. Effectively I want to do something like:
select * from discussions
inner join comments on comments.discussion_id=discussion.id
where comments.author = 'poopface'
I get something close with this:
Discussion.find(:all, :conditions => {:author => 'poopface'}, :joins => :special_comment)
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: discussions.author: SELECT "discussions".* FROM "discussions" INNER JOIN "comments" ON comments.discussion_id = discussions.id WHERE ("discussions"."author" = 'poopface')
But it should be WHERE ("comments"."author" = 'poopface')
Thanks!
Try this:
Discussion.all(:conditions => {:comments=> {:author => "blah"}},
:joins => :comments)
OR
Discussion.all(:conditions => ["comments.author = ?", "blah"],
:joins => :comments)
Note: May be you could have used a better author name in your sample code.
Assuming you have some foreignkey on your Comments table that your has_one is referencing...
Discussion.find(:all, :conditions => {:comments=>{:author => 'blah'}},
:joins => :special_comment)
will give you all Discussions where special_comment is authored by 'blah'.
Discussion.find(:all, :conditions => {:comments=>{:author => 'blah'}},
:joins => :comments)
will give you all Discussions where they have any comments authored by 'blah'.

ActiveRecord find all parents that have associated children

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.

Activerecord Nested :include fails

I have an AR query using 'will_paginate' that looks like this:
paginate :all,
:page => criteria[:page],
:per_page => criteria[:per_page],
:include => { :user, :person },
:conditions => [conditions , criteria[:from_date], criteria[:to_date], criteria[:patient_id],criteria[:user_id]].concat(criteria[:actions]).concat(criteria[:types]).concat(criteria[:users]).concat(criteria[:statuses]).concat(criteria[:priorities]).compact,
:order => criteria[:order]
I get an error in the order clause:
Unknown column 'user.person.last_name' in 'order clause'
I am trying to order by a person's last name. As you can see I have included user and person in a nested include. User belongs to person with this statement:
belongs_to :person, :class_name => 'Party', :foreign_key => 'person_id', :with_disabled => true
Person is a subclass of Party:
class Person < Party
Party has a last_name field
The order by should be table_name.column, something like people.last_name

Shortcut for specifying an order and limit when accessing a has_many relation?

Is there a shortcut for giving a limit and order when accessing a has_many relation in an ActiveRecord model?
For example, here's what I'd like to express:
#user.posts(:limit => 5, :order => "title")
As opposed to the longer version:
Post.find(:all, :limit => 5, :order => "title", :conditions => ['user_id = ?', #user.id])
I know you can specify it directly in the has_many relationship, but is there a way to do it on the fly, such as showing 10 posts on one page, but only 3 on another?
I have something similar in a blog model:
has_many :posts, :class_name => "BlogPost", :foreign_key => "owner_id",
:order => "items.published_at desc", :include => [:creator] do
def recent(limit=3)
find(:all, :limit => limit, :order => "items.published_at desc")
end
end
Usage:
Blog.posts.recent
or
Blog.posts.recent(5)
You can do it using a named scope on the post model:
class Post < ActiveRecord::Base
named_scope :limited, lambda {|*num| {:limit => num.empty? ? DEFAULT_LIMIT : num.first}}
end
This is essentially similar to utility_scopes, as reported by #Milan, except you do it piece meal, only where you need to do it.
You can use Ryan Daigle's utility_scopes. After installation (it's a gem) you will get some new useful scopes such as:
#user.posts.ordered('title ASC').limited(5)
You can even set default order and limit for any model.

Resources