Creating a named scope that includes an associated model - ruby-on-rails

The model Organizer has_many events.
Event has attributes begin_day:date and published:boolean.
I have the following query for events that haven't ocurred yet:
#organizer.events.order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true).limit(8)
which I would like to extraxt to a scope such that it's implemented something like this:
#organizer.upcoming_events.limit(8)
How do I do create this scope though that includes an associated model?

Try smth like this:
scope :upcoming_events, -> { joins(:events).where("events.begin_day >= ?", Date.today).where(events: {published: true}).order('events.begin_day asc') }

The has_many and belongs_to helpers automatically create fields and scopes that easily allow you to join two models, in a user defined scope you have to join those models manually. ;)
class Event
belongs_to :organizer
end
class Organizer
has_many :events
scope :upcoming_events, joins(:events).order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true)
# lol007's query
scope :upcoming_events, -> { joins(:events).where("events.begin_day >= ?", Date.today).where(events: {published: true}).order('events.begin_day asc') }
end
end

Reading #TheChamp's reasoning in his answer, it seems like just doing a method pasting in the query part works equally well. Chainable as well.
class Event
belongs_to :organizer
end
class Organizer
has_many :events
def upcoming_events
self.events.order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true)
end
end
end
This works now:
#organizer.upcoming_events.limit(8)

Related

Rails 4, scope on related model loads all enties (scope doesn't filter)

I have models like that:
class Post < AvtiveRecord::Base
belongs_to :article
default_scope { order(created_at: :desc) }
scope :active, -> { where(active: true).order(created_at: :desc) }
scope :where_author, -> (author) { where("author LIKE ?", "#{author}%") }
end
class Article < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
When on rails console I try:
Article.find(123).posts.where_author("guest")
I get expected values.
But when I do this in ArticlesController:
#articles = Article.includes(:posts).posts.where_author("guest") # I will use params array when it work
This loads all posts and ignores scope conditions, actually SQL query doesn't include scope part at all.
I've tried this with joins and includes with same result.
What I'm doing wrong?
Thanks.
This should work, you need articles but the condition is on the post
Article.joins(:posts).where('posts.author like ?', 'guest%')
There's a better way to do this using the scope that's only accessible from the Post model.
Article.joins(:posts).merge(Post.where_author('guest'))
Complete solution (code I use in my project) is:
on Article
scope :post_author, -> (author) { joins(:posts).merge(Post.where_author(author)) }
on Post
scope :where_author, -> (author) { where("posts.author LIKE ?", "#{author}%") }
Now I can use scopes and chain them as follow:
#articles = Article.post_author(params[:post_author])
merge() part is very important here.
Thanks.

Is there a better way to find associations on a class level?

Here are the objects I am working with:
class Client < ActiveRecord::Base
has_many :servers
def self.active
where("updated_at > ?", 1.month.ago)
end
end
class Server
belongs_to :client
end
I would like to be able to get all the servers that belong to active clients like so:
Client.active.servers
The only way I can think to do this is:
def Client.servers
Server.where(id: all.collect(&:id))
end
There must be a more Rails-y to do this!
I believe I've found what you're looking for, and it's the Active Record method merge.
Server.joins(:client).merge(Client.active)
In testing, if you find a conflict with your updated_at column, be sure to disambiguate it in your active scope:
def self.active
where("clients.updated_at > ?", 1.month.ago)
end
You want to join to the client from server. Something like this should work:
Server.joins(:client).where('clients.updated_at > ?', 1.month.ago)

Join scope in Rails 3

I'd like to write a Rails 3 scope, on Client, to do the following:
select * from clients c, dependents d
where d.last_name like '%ss%' and c.id = d.client_id;
Dependent model:
class Dependent < ActiveRecord::Base
belongs_to :client
scope :by_last_name, (lambda do |name| { :conditions => ['last_name LIKE ?', "#{name}%"]} end )
Client model:
class Client < ActiveRecord::Base
has_many :dependents, :dependent => :destroy
I have not yet been able to get a scope join to work syntactically. The dependent's last name needs to be passed in as a parameter.
Thanks.
Try this:
class Client < ActiveRecord::Base
scope :by_last_name, lambda { |last_name| includes(:dependents).where("dependents.last_name LIKE ?", "#{last_name}%") }
then call
Client.by_last_name "last_name"
Edit: Changed the code to reflect your problem.
Also find_by_id(1) is the same as find(1) except that it does not throw an error when nothing is found, instead it returns nil.
Try the following:
scope :by_last_name, lambda {|name| where("dependents.last_name like ?", "'%#{name}%'")

How to combine two scopes with OR

I have a tag feed and a friend feed.
I want to combine these two and build the ultimate "all" feed.
For friend feed:
class Post < ActiveRecord::Base
scope :friendfeed, lambda{|x| followed_by}
def self.followed_by(user)
where("user_id IN (?) OR user_id = ?", user.watched_ids, user.id)
end
end
For tag feed:
class Post < ActiveRecord::Base
scope :tagfeed, lambda{|x| infatuated_with}
def self.infatuated_with(user)
joins(:attachments).where("attachments.tag_id IN (?)", user.tags).select("DISTINCT pages.*")
end
end
And I would call something like this from the controller (I'm using Kaminari gem for pagination):
#tag_feed = Post.tagfeed(current_user).page(params[:page]).per(21)
#friend_feed = Post.friendfeed(current_user).page(params[:page]).per(21)
Now I want to have a universal feed, but I'm lost. Scopes are meant for narrowing down, but in this case I'm trying to do an OR operation. Doing stuff like
#mother_of_all_feed = #tag_feed + #friend_feed
would be redundant, and I wouldn't be able to control the number of posts appearing on a single page. How can I go about doing this? Thanks!
By the way, for tags I have association set up like this:
class Post < ActiveRecord::Base
has_many :attachments
has_many :tags, :through => :attachments
end
class Tag < ActiveRecord::Base
has_many :attachments
has_many :posts, :through => :attachments
end
class Attachment < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
There's a rails pull request for this feature (https://github.com/rails/rails/pull/9052), but in the meantime, some one has created a monkey patch that you can include in your initializers that will allow you to or scopes and where clauses in one query and still give you an ActiveRecord::Relation:
https://gist.github.com/j-mcnally/250eaaceef234dd8971b
With that, you'd be able to OR your scopes like this
Post.tagfeed(current_user).or.friendfeed(current_user)
or write a new scope
scope :mother_of_all_feed, lambda{|user| tagfeed(user).or.friendfeed(user)}
Answering my own question. I think I figured out a way.
where("pages.id IN (?) OR pages.id IN (?)",
Page.where(
"user_id IN (?) OR user_id = ?",
user.watched_ids, user.id
),
Page
.joins(:attachments)
.where("attachments.tag_id IN (?)", user.tags)
.select("DISTINCT pages.*")
)
It seems to be working so far, hope this is it!
Here's an example of how I combined two scopes.
scope :reconcilable, -> do
scopes = [
with_matching_insurance_payment_total,
with_zero_insurance_payments_and_zero_amount
]
where('id in (?)', scopes.flatten.map(&:id))
end

Rails 3 joining a related model with it's default scope

I'm trying to query across models with the following setup
Class Scorecard < AR::Base
default_scope where(:archived => false)
belongs_to :user
has_many :scorecard_metrics
end
Class ScorecardMetric < AR::Base
belongs_to :scorecard
end
Class User < AR::Base
has_many :scorecards
end
I am trying to query from scorecard metrics with a named scope that joins scorecard and I want it to include the default scope for scorecard, my current implementation (which works) looks like this
# on ScorecardMetric
scope :for_user, lambda {
|user| joins(:scorecard).
where("scorecards.user_id = ? and scorecards.archived = ?", user.id, false)
}
This just seems messy to me, is there any way to join and include the default scope of the joined association?
Looks like I found the answer I was looking for, I just did this
scope :for_user, lambda { |user| joins(:scorecard).where('scorecards.user_id = ?', user.id) & Scorecard.scoped }
which is much nicer without the duplicated logic

Resources