ActiveRecord find through has_one association child attribute - ruby-on-rails

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'.

Related

how to use joins to get back a single query selected elements from two tables

I have (in Rails 3.2.13):
class User < ActiveRecord::Base
has_many :app_event_login_logouts
end
class AppEventLoginLogout < ActiveRecord::Base
belongs_to :user
end
and would like to get back something like:
AppEventLoginLogoug.select("id, type, users.email").joins(:user)
basically id, type from app_event_login_logouts and email from users but this doesn't seem to be working. What would be the correct syntax?
Try out following code:
ret = User.joins(:app_event_login_logouts).select('app_event_login_logouts.id, app_event_login_logouts.type, users.email')
ret.first.id # will return app_event_login_logouts.id
ret.first.email # will return users.email
...
AppEventLoginLogoug.find(:all,
{:include => [:users],
:select => ['id', 'type', 'users.email']})
I also checked the apidoc and found something like that:
result= AppEventLoginLogoug.find(:all,
:conditions => ['condition_here'],
:joins => [:users],
:select => 'whatever_to_select'
:order => 'your.order')

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

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")

Correct ActiveRecord joins query assistance

Need a little help with a SQL / ActiveRecord query. Let's say I have this:
Article < ActiveRecord::Base
has_many :comments
end
Comment < ActiveRecord::Base
belongs_to :article
end
Now I want to display a list of "Recently Discussed" articles - meaning I want to pull all articles and include the last comment that was added to each of them. Then I want to sort this list of articles by the created_at attribute of the comment.
I have watched the Railscast on include /joins - very good, but still a little stumped.
I think I want to use a named_scope, something to this effect:
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, :include => :comments, :conditions => { some_way_to_limit_just_last_comment_added }, :order => "comments.created_at DESC"
end
Using MySQL, Rails 2.3.4, Ruby 1.8.7
Any suggestions? :)
You have two solutions for this.
1) You treat n recent as n last. Then you don't need anything fancy:
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, :include => :comments,
:order => "comments.created_at DESC",
:limit => 100
end
Article.recently_commented # will return last 100 comments
2) You treat recent as in last x duration.
For the sake of clarity let's define recent as anything added in last 2 hours.
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, lambda { {
:include => :comments,
:conditions => ["comments.created_at >= ?", 2.hours.ago]
:order => "comments.created_at DESC",
:limit => 100 }}
end
Article.recently_commented # will return last 100 comments in 2 hours
Note Code above will eager load the comments associated with each selected article.
Use :joins instead of :include if you don't need eager loading.
You're gonna have to do some extra SQL for this:
named_scope :recently_commented, lambda {{
:select => "articles.*, IFNULL(MAX(comments.created_at), articles.created_at) AS last_comment_datetime",
:joins => "LEFT JOIN comments ON comments.article_id = articles.id",
:group => "articles.id",
:conditions => ["last_comment_datetime > ?", 24.hours.ago],
:order => "last_comment_datetime DESC" }}
You need to use :joins instead of :include otherwise Rails will ignore your :select option. Also don't forget to use the :group option to avoid duplicate records. Your results will have the #last_comment_datetime accessor that will return the datetime of the last comment. If the Article had no comments, it will return the Article's created_at.
Edit: Named scope now uses lambda

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