Using named_scope with counts of child models - ruby-on-rails

I have a simple parent object having many children. I'm trying to figure out how to use a named scope for bringing back just parents with specific numbers of children.
Is this possible?
class Foo < ActiveRecord::Base
has_many :bars
named_scope :with_no_bars, ... # count of bars == 0
named_scope :with_one_bar, ... # count of bars == 1
named_scope :with_more_than_one_bar, ... # count of bars > 1
end
class Bar < ActiveRecord::Base
belongs_to :foo
end
I'm hoping to do something like Foo.with_one_bar
I could write methods on the parent class for something like this, but I'd rather have the power of the named scope

class Foo < ActiveRecord::Base
has_many :bars
# I don't like having the number be part of the name, but you asked for it.
named_scope :with_one_bar, :joins => :bars, :group => "bars.foo_id", :having => "count(bars.foo_id) = 1"
# More generically...
named_scope :with_n_bars, lambda {|n| {:joins => :bars, :group => "bars.foo_id", :having => ["count(bars.foo_id) = ?", n]}}
named_scope :with_gt_n_bars, lambda {|n| {:joins => :bars, :group => "bars.foo_id", :having => ["count(bars.foo_id) > ?", n]}}
end
Called like so:
Foo.with_n_bars(2)

I would use the counter cache for this. Therefore you need the following migration:
class AddBarCount < ActiveRecord::Migration
def self.up
add_column :foos, :bars_count, :integer, :default => 0
Foo.reset_column_information
Foo.all.each do |p|
p.update_attribute :bars_count, p.bars.length
end
end
def self.down
remove_column :foos, :bars_count
end
end
Than you need too change you Bar model like this:
class Bar < ActiveRecord::Base
belongs_to :foo, :counter_cache => true
end
Now the count of bars is cached in the foo model, that will speed up your queries for the count of bars.
Your named_scopes then have too look like this:
#rails 2
named_scope :with_no_bars, :conditions => { :bars_count => 0 }
named_scope :with_one_bar, :conditions => { :bars_count => 1 }
named_scope :with_more_than_one_bar, :conditions => ["bars_count > 1"]
#rails 3 & ruby 1.9+
scope :with_no_bars, where(bars_count: 0)
scope :with_one_bar, where(bars_count: 1)
scope :with_more_than_on_bar, where("bars_count > 1")
#rails 4* & ruby 1.9+
scope :with_no_bars, -> { where(bars_count: 0) }
scope :with_one_bar, -> { where(bars_count: 1) }
scope :with_more_than_one_bar, -> { where("bars_count > 1") }
That way you can save time counting bars for each foo every time you make such a request.
I got this idea watching the railscast about counter cache: http://railscasts.com/episodes/23-counter-cache-column
* What's new in Active Record [Rails 4 Countdown to 2013]

Related

select specific attributes from array rails

I have post model
class Post < ActiveRecord::Base
acts_as_voteable
end
and Vote model
class Vote < ActiveRecord::Base
scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) }
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) }
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
scope :descending, order("created_at DESC")
belongs_to :voteable, :counter_cache=>true,:polymorphic => true,:touch=>true
belongs_to :voter, :polymorphic => true
attr_accessible :vote, :voter, :voteable
# Comment out the line below to allow multiple votes per user.
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
end
when I get the post voters with these method
<% #post.voters_who_voted.each do |voter|%>
<%= voter.name %>
<% end %>
I load my database
how can I select only the user name and user id from these array?
update I changed my code I am using thumbs_up gem I pasted less code first to simplify the question
What do you mean by "load database"? If you want to select only id and name columns, then use #post.users.select([:id, :name]).each ...
Or is it about this problem (according to code that you provided)?
UPD.
voters_who_voted loads all voters and returns array https://github.com/bouchard/thumbs_up/blob/master/lib/acts_as_voteable.rb#L113. You have to add own association to Post model:
has_many :voters, :through => :votes, :source => :voter, :source_type => 'User'
It's just example, perhaps voters will clash with already existing method, if any.
Then use it here instead of voters_who_voted
did you try collect method ??
names = #post.users.collect(&:name)
ids = #post.user.collect(&:id)
If you want it to be related you can make a HASH with it. Id's mapped to the names.

Scope Lambda wrong number of arguments

Can anyone please have a look what I'm doing wrong?
It's Rails 3:
class Ad < ActiveRecord::Base
belongs_to :postingtemplate
scope :active, (lambda do |ad|
Item.exists?(:postingtemplate => ad.postingtemplate_id)
end)
end
It's a scope inside Ad model, and is supposed to return all ads, for which Item exists where item.postingtemplate == ad.postingtemplate_id
UPDATE
Broke it into two scopes and it worked :)
class Ad < ActiveRecord::Base
belongs_to :postingtemplate
scope :active, where(:postingtemplate_id => Postingtemplate.active)
end
class Postingtemplate < ActiveRecord::Base
has_many :ads
scope :active, where(:id => Item.all.collect{|x| x.postingtemplate}.uniq)
end
if anyone knows a better way - feel free to tell
You can do it with a join
scope :active, lambda { |ad| joins(:postingtemplate => :items).where(:postingtemplate => {:postingtemplate_id => ad.postingtemplate_id}) }
Maybe this will work too:
scope :active, lambda { |ad| joins(:postingtemplate => :items).where(:postingtemplate => ad.postingtemplate) }

ActiveRecord associations and scopes oh my

I'm trying to figure out how to pull data from multiple tables and making sense of it.
I have events, occurrences and venues tables.
Here are the models/associations so far:
models/event.rb:
class Event < ActiveRecord::Base
has_many :occurences, :dependent => :destroy
belongs_to :price
belongs_to :venue
validates_presence_of :venue
end
models/venue.rb:
class Venue < ActiveRecord::Base
has_many :events, :dependent => :destroy
end
models/occurence.rb:
class Occurence < ActiveRecord::Base
belongs_to :event
scope :today, lambda { where("occurence.datetime_start <= ? AND datetime_end >= ?", Time.now.end_of_day, Time.now) }
scope :this_week, lambda { where("occurence.datetime_start <= ? AND datetime_end <= ?", Time.now.end_of_day, Time.now.at_end_of_week) }
scope :next_week, lambda { where("occurence.datetime_start > ? AND datetime_end < ?", Time.now.at_end_of_week, Time.now.at_end_of_week + 1.weeks) }
scope :this_month, lambda { where("occurence.datetime_start <= ? AND datetime_end >= ?", Time.now.end_of_day, Time.now.at_end_of_month) }
scope :next_month, lambda { where("occurence.datetime_start >= ? AND datetime_end <= ?", Time.now.at_beginning_of_month + 1.months, Time.now.at_end_of_month + 1.months) }
end
What I'd like to show in my view/events/index.html.erb is a table of all events happening today and I like to do something like:
<% #events.today.each do |event| %>
and display all the events that match the .today scope.
I tried moving the scope to the event model to no avail. Should I move the scopes to the event model? So far my searches have lead me to
"Using scope to return results within multiple DateTime ranges in ActiveRecord"
and
"Multiple scope in rails 3.0" but I can't really make anything out of it.
Can anyone point me in the right direction? Should I create a new function in my event model?
class Event < ActiveRecord::Base
has_many :occurences, :dependent => :destroy
belongs_to :price
belongs_to :venue
validates_presence_of :venue
scope :today, lambda {
joins(:occurences).where("datetime_start <= ? AND datetime_end >= ?", Time.now.end_of_day, Time.now)
}
end
If you want your syntax to work like:
#events.today
Then the scope would have to be in the Event model.
If you're OK with:
#event.occurences.today
then you're OK now, i.e.:
#events = Event.all
#events.each do |ev|
ev.occurences.today.each do |oc|
end
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)

Silblings in has_many relationship

A user has many employments.
What do you think?
Is this a valid and clear way to fetch all siblings (belonging to the same user) of a given employment object?
class Employment < ActiveRecord::Base
belongs_to :user
has_many :silblings,
:primary_key => :user_id,
:foreign_key => :user_id,
:class_name => 'Employment'
end
This can be extended with the following named scope:
named_scope :except, lambda {|id| {:conditions => ["id != ?", id]} if id}
Now I can do stuff like:
self.silblings.except(self.id).each do |silbling|
puts silbling
end
The resulting SQL statement looks like:
SELECT * FROM `employments`
WHERE (`employments`.user_id = 49)
AND ((id != 46) AND (`employments`.user_id = 49))
Comments like 'no, you abuse XY, rather use this XZ' are very welcome!
Reto
Looks fine. Except that the SQL doubles ('employments'.user_id = 49) in the query. Which is nothing major. If it's something you really don't want, you could go about defining siblings like this:
class Employment < ActiveRecord::Base
belongs_to :user
named_scope :for_user, lambda { |user|
{ :conditions => {:user_id => user} }
}
named_scope :except, lambda {|employment|
{:conditions => ["id != ?", employment}
}
def siblings
Employment.for_user(user_id).except(id)
end
end
Believe it or not you can still call named scopes on #employment.siblings. Although doing things this way means you can't assign to siblings. The siblings call comes out a little cleaner. There may be a performance improvement, but it probably won't be significant to make a difference.

Resources