Rails 2.3 using another model's named_scope or alternative - ruby-on-rails

Let's say I have two models like so:
class Comment < ActiveRecord::Base
belongs_to :user
named_scope :about_x :conditions => "comments.text like '%x%')"
end
class User < ActiveRecord::Base
has_many :comments
end
I would like to use the models so that I can return all the users and all comments with text like '%x%'
all_user_comments_about_x = User.comments.about_x
How to proceed?
Thank you

Try Following
class User < ActiveRecord::Base
has_many :comments
named_scope :about_x, :joins => :comments, :conditions => ["comments.text like '%x%'"]
end
and then
#comments = User.about_x

If I understood correctly, you need
# all comments about x and the user who did it
#comments = Comment.about_x(:include => :user)
or
#user = User.first
# all #user's comments about x
#comments = #user.comments.about_x

Related

How to define scope to get one record of a has_many association?

class User
has_many :posts do
def latest(report_date)
order(:report_date).where('report_date <= ?', report_date).limit(1)
end
end
end
class Post
belongs_to :user
end
I would like to retrieve the records of user with the last post for each user.
I could do this:
users = User.all.map{|user| user.posts.latest(7.weeks.ago).first}.compact
Is there a better way to write this? something like:
users = User.posts.latest(7.weeks.ago).all
if that were valid?
I tend to add something like this. Would it work in your case? It's nice because you can 'include' it in list queries...
class User < ActiveRecord::Base
has_many :posts
has_one :latest_post, :class_name => 'Post', :order => 'report_date desc'
...
end
In practice, you would do something like this in the controller:
#users = User.include(:latest_post)
And then, in the view where you render the user, you could refer to user.lastest_post and it will be eager loaded.
For example - if this was in index.html.haml
= render #users
you can access latest_post in _user.html.haml
= user.name
= user.latest_post.report_date

Rails: How can I eager load associations with sorting through instance an method?

class Newsroom < ActiveRecord::Base
has_many :blog_posts
has_many :quote_posts
end
class BlogPost < ActiveRecord::Base
belongs_to :newsroom
end
class QuotePost < ActiveRecord::Base
belongs_to :newsroom
end
I would like to have an instance method, such that I could do #newsroom.posts to get a collection of blog_posts and quote_posts sorted by created_at.
def posts
#posts ||= #load and sort blog_posts, quote_posts, etc
end
What is the best and most efficient way to accomplish this? I have looked into using default_scope, something like:
default_scope :include => [:blog_posts, :quote_posts]
def posts
#posts ||= [blog_posts + quote_posts].flatten.sort{|x,y| x.created_at <=> y.created_at}
end
But I would rather keep the sorting at the database level, if possible. Any suggestions on how to accomplish this? Thanks.
Try something like this:
#app/models/newsroom.rb
scope :ordered_posts, lambda {
includes(:blog_posts,:quote_posts) & BlogPost.order("created_at asc") & QuotePost.order("created_at asc")
}
ARel should be able to handle the ordering of included Quote and Blog Posts. You could clean that up slightly by having scopes in both the BlogPost and QuotePost model that order by created_at and then use those scopes in the Newsroom#ordered_posts method.
I ended up using a polymorphic post model. This seems to give me what I want with the insignificant downside of having an extra model/table. I used delegate to hand off specific attribute getter methods to the correct model.
class Newsroom < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belong_to :blog_post, :polymorphic => true
delegate :title, :author, :etc, :to => :postable
end
class BlogPost < ActiveRecord::Base
has_one :post, :as => :postable
end

How to apply conditions when accessing records using a has_many through relationship in Rails?

I have the following models:
class Campaign < ActiveRecord::Base
has_many :campaign_keywords
has_many :leads, :through => :campaign_keywords
end
class CampaignKeyword < ActiveRecord::Base
belongs_to :campaign
has_many :leads
end
class Lead < ActiveRecord::Base
belongs_to :campaign_keyword
end
I am trying to build a function in the "Campaign" model that will only return leads which belong to a given campaign_keyword.
My attempt is:
def leads?(campaign_keyword_id = -1)
self.leads :conditions => ['campaign_keyword_id = #{campaign_keyword_id}']
end
but this does not work, the conditions are ignored.
Can you see a solution to this?
Create a named_scope for your Lead model, like so:
class Lead < ActiveRecord::Base
belongs_to :campaign_keyword
named_scope :with_keyword, lambda { |keyword| { :conditions => { :campaign_keyword => keyword } } }
end
Now, when you want to get leads for a particular campaign keyword, you would do so like this:
def leads_for_campaign(keyword)
self.leads.with_keyword(keyword)
end
This is much nicer and more re-usable, because the Lead model itself now knows how to find leads for a specific campaign.
For more of an idea of what you can do with named_scopes, check out http://apidock.com/rails/ActiveRecord/NamedScope/ClassMethods/named_scope
Try this:
def leads?(campaign_keyword_id = -1)
self.leads.all :conditions => ['campaign_keyword_id = #{campaign_keyword_id}']
end
I would rewrite your query as follows:
def leads?(campaign_keyword_id = -1)
self.leads.all :conditions => ['campaign_keyword_id = ?', campaign_keyword_id]
end
OR
self.leads.find_all_by_compaign_keyword_id(campaign_keyword_id)

Rails 3 find all associated records has_many :through

I would like to list all posts that are connected with some specific category and classroom.
I have:
class Post < ActiveRecord::Base
has_many :category_posts
has_many :categories, :through => :category_posts
has_many :classroom_posts
has_many :classrooms, :through => :classroom_posts
end
class Category < ActiveRecord::Base
has_many :category_posts
has_many :posts, :through => :category_posts
end
class CategoryPost < ActiveRecord::Base
belongs_to :category
belongs_to :post
end
class Classroom < ActiveRecord::Base
has_many :classroom_posts
has_many :posts, :through => :classroom_posts
end
class ClassroomPost < ActiveRecord::Base
belongs_to :classroom
belongs_to :post
end
And I wanna do something like this
Post.where(["category.id = ? AND classroom.id = ?", params[:category_id], params[:classroom_id]])
It indeed is very simple task, but I don't know what I should be looking for (keywords).
It's the same problem like this, but in rails.
EDIT:
I added more details to the question.
This works, but only if I have both params specified. Witch is not always the case - I dont know what params would be specified.
Post.joins(:categories, :classrooms).where(["categories.id = ? AND classrooms.id = ?", params[:classroom_id], params[:category_id]])
Category.find(params[:category_id]).posts
Also take a look at the guides:
Guides for Rails 2.35
Guides for Rails 3.0
Here is what I would do in Rails 3:
In post.rb:
def self.in_category(category_id)
if category_id.present?
join(:category_posts).where(category_posts: {category_id: category_id})
else
self
end
end
def self.in_classroom(classroom_id)
if classroom_id.present?
join(:classroom_posts).where(classroom_posts: {classroom_id: category_id})
else
self
end
end
I do not join Classroom or Category since it makes more work for DBMS and this is not required.
Now, you can do:
Post.in_category(params[:category_id]).in_classroom(params[:classroom_id])
I haven't tested it though. So do not hesitated to ask if needed.
I think that should work:
Post.joins(:category_posts, :classroom_posts)
.where(
["category_posts.category_id = ?
AND classroom_posts.classroom_id = ?", params[:category_id], params[:classroom_id]])
This will traslate to a SQL like :
SELECT
p.*
FROM
posts AS p
INNER JOIN
category_posts AS cap ON cap.id = p.category_posts_id
INNER JOIN
classroom_posts AS clp ON clpid = p.classroom_posts_id
WHERE
cap.category_id = '1' AND clp.classroom_id = '1'
;
As to whether to use :include or joins on Post look at this answer on stackoverflow.
Sounds like you need an if statment.
if params[:category_id] && params[:classroom_id]
Post.joins(:categories, :classrooms).where("classrooms.id" => params[:classroom_id], "categories.id" => params[:category_id]])
elsif params[:category_id]
Category.find(params[:category_id]).posts
else
Classroom.find(params[:classroom_id]).posts
end

Rails model relations depending on count of nested relations

I am putting together a messaging system for a rails app I am working on.
I am building it in a similar fashion to facebook's system, so messages are grouped into threads, etc.
My related models are:
MsgThread - main container of a thread
Message - each message/reply in thread
Recipience - ties to user to define which users should subscribe to this thread
Read - determines whether or not a user has read a specific message
My relationships look like
class User < ActiveRecord::Base
#stuff...
has_many :msg_threads, :foreign_key => 'originator_id' #threads the user has started
has_many :recipiences
has_many :subscribed_threads, :through => :recipiences, :source => :msg_thread #threads the user is subscribed to
end
class MsgThread < ActiveRecord::Base
has_many :messages
has_many :recipiences
belongs_to :originator, :class_name => "User", :foreign_key => "originator_id"
end
class Recipience < ActiveRecord::Base
belongs_to :user
belongs_to :msg_thread
end
class Message < ActiveRecord::Base
belongs_to :msg_thread
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
end
class Read < ActiveRecord::Base
belongs_to :user
belongs_to :message
end
I'd like to create a new selector in the user sort of like:
has_many :updated_threads, :through => :recipiencies, :source => :msg_thread, :conditions => {THREAD CONTAINS MESSAGES WHICH ARE UNREAD (have no 'read' models tying a user to a message)}
I was thinking of either writing a long condition with multiple joins, or possibly writing giving the model an updated_threads method to return this, but I'd like to see if there is an easier way first. Am I able to pass some kind of nested hash into the conditions instead of a string?
Any ideas? Also, if there is something fundamentally wrong with my structure for this functionality let me know! Thanks!!
UPDATE:
While I would still appreciate input on better possibilities if they exist, this is what I have gotten working now:
class User < ActiveRecord::Base
# stuff...
def updated_threads
MsgThread.find_by_sql("
SELECT msg_threads.* FROM msg_threads
INNER JOIN messages ON messages.msg_thread_id = msg_threads.id
INNER JOIN recipiences ON recipiences.msg_thread_id = msg_threads.id
WHERE (SELECT COUNT(*) FROM `reads` WHERE reads.message_id = messages.id AND reads.user_id = #{self.id}) = 0
AND (SELECT COUNT(*) FROM recipiences WHERE recipiences.user_id = #{self.id} AND recipiences.msg_thread_id = msg_threads.id) > 0
")
end
end
Seems to be working fine!
Also to check if a specific thread (and message) are read:
class Message < ActiveRecord::Base
# stuff...
def read?(user_id)
Read.exists?(:user_id => user_id, :message_id => self.id)
end
end
class MsgThread < ActiveRecord::Base
# stuff...
def updated?(user_id)
updated = false
self.messages.each { |m| updated = true if !m.read?(user_id) }
updated
end
end
Any suggestions to improve this?
Add a named_scope to the MsgThread model:
class MsgThread < ActiveRecord::Base
named_scope :unread_threads, lambda { |user|
{
:include => [{:messages=>[:reads]}, recipiencies],
:conditions => ["recipiences.user_id = ? AND reads.message_id IS NULL",
user.id],
:group => "msg_threads.id"
}}
end
Note: Rails uses LEFT OUTER JOIN for :include. Hence the IS NULL check works.
Now you can do the following:
MsgThread.unread_threads(current_user)
Second part can be written as:
class Message
has_many :reads
def read?(usr)
reads.exists?(:user_id => usr.id)
end
end
class MsgThread < ActiveRecord::Base
def updated?(usr)
messages.first(:joins => :reads,
:conditions => ["reads.user_id = ? ", usr.id]
) != nil
end
end
You might want to take a look at Arel, which can help with complex SQL queries. I believe (don't quote me) this is already baked into Rails3.

Resources