Rails 3 joining a related model with it's default scope - ruby-on-rails

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

Related

How to use Active Record counter cache with the all scope

I have an activerecord class method scope that returns all when the scope should remain unchanged. However I would expect it to use the counter cache when chaining size to the all scope. Here is an example:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
def self.filtered(only_approved:)
if only_approved
where(approved: true)
else
all
end
end
end
# This does not use the counter cache but should since the scope is unchanged
Post.first.comments.filtered(only_approved: false).size
So it looks like Post.comments.size triggers the counter cache while Post.comments.all.size does not. Is there a way around this?
This happens because of how the counter_cache works. It needs 2 things:
Add the counter_cache: true to the belonging model (Comment)
Add a column comments_count to the having model (Post)
The column added to the Post model gets updated everytime you create or destroy a model so it will count all existing records on the table. This is the reason why it won't work on a scope (a scope might be useful to filter the resulting records, but the actual column comments_count is still counting the whole table).
As a workaround I'd suggest you to take a look at and see if it can be used for your usecase https://github.com/magnusvk/counter_culture.
From their own repo:
class Product < ActiveRecord::Base
belongs_to :category
scope :awesomes, ->{ where "products.product_type = ?", 'awesome' }
scope :suckys, ->{ where "products.product_type = ?", 'sucky' }
counter_culture :category,
column_name: proc {|model| "#{model.product_type}_count" },
column_names: -> { {
Product.awesomes => :awesome_count,
Product.suckys => :sucky_count
} }
end
The only way I found to deal with this is to pass the scope to the class method and return it if no additional scope is to be added. It's not as clean but it works. Here is the updated code:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
def self.filtered(scope:, only_approved:)
if only_approved
scope.where(approved: true)
else
scope
end
end
end
# This works with counter cache if the scope is returned as is
Comment.filtered(scope: Post.first.comments, only_approved: false).size

Creating a named scope that includes an associated model

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)

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}%'")

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

Best Practice to abstract ActiveRecord model queries in Rails?

I'd like to extract out logic from the controllers to somewhere that it can be more DRY. What's the best way of handling something like the following in Rails?
For instance, as opposed to having the query in the controllers, I could place it in the model:
class SmoothieBlender < ActiveRecord::Base
belongs_to :user
def self.get_blenders_for_user(user)
self.where(["user_id = ?", user.id])
end
end
Or would it be better to create a module as a service layer and include that in each model that uses it?
module BlenderUser
def get_blenders_for_user(user)
SmoothieBlender.where(["user_id = ?", user.id])
end
end
class SmoothieBlender < ActiveRecord::Base
include BlenderUser
belongs_to :user
end
class User < ActiveRecord::Base
include BlenderUser
has_many :smoothie_blenders
end
Or just make it a full blown service class that's accessible from the User and Blender controller? Where would you put this class?
class BlenderService
def self.get_blenders_for_user(user)
SmoothieBlender.where(["user_id = ?", user.id])
end
end
I'm new to Ruby and Rails, so if this is a silly question/syntax is incorrect, forgive me. Thanks in advance!
I'd create a named_scope (I think it's just scope in Rails 3)
class SmoothieBlender < ActiveRecord::Base
belongs_to :user
scope :for_user, lambda { |user_id|
where("user_id = ?", user_id)
}
end
This way you can call
SmoothieBlender.for_user(user.id)

Resources