Rails: HABTM - find all records with no association - ruby-on-rails

I have 2 models (Workout, Equipment) in a has and belongs to many relationship. If I use Workout.find(:all, :joins => :equipment, :conditions => "equipment.id = 5") it works, but if I use Workout.find(:all, :joins => :equipment, :conditions => "equipment.id = null") it doesn't return the records with no association. Any ideas?

Give this a whirl;
Workout.joins("left join equipments e on workouts.id = e.workouts_id").where("e.id is null")

Related

named_scope join aliasing issues

I have a named_scope which does a join. I have included the named_scope method below
named_scope :has_more_than_one,{
:select => "sessions.*",
:joins => :attenders,
:conditions => {:attenders => {:attending => true}},
:group => "sessions.id",
:having => "count(sessions.id) > 1"
Meeting.has_more_than_one.all(:group => "sessions.id",
:include => [:attenders => [ :issues ]],
:conditions => ["sessions.id in (select attenders.session_id from attenders where person_id in (select persons.id from persons where first_name like (?) or last_name like (?) or first_name like (?) or last_name like (?)))",
"#{attendee_first_name}%","#{attendee_last_name}%","#{attendee_last_name}%","#{attendee_first_name}%"])
I ran the above line and got an aliasing error
ActiveRecord::StatementInvalid: Mysql::Error: Not unique table/alias: 'member_meetings'
Is there any way to get around this..
somewhere you have a has_many something :through => :member_meetings that you're not showing here.
I'd guess that something is attenders.
Anyway, you're joining attenders in the scope's joins and then again in the :include options, thus requiring the same tables twice.
If that's correct you should define the scope like this
named_scope :has_more_than_one,{
:select => "sessions.*",
:joins => <specify the join with the condition here aliasing the tables>,
# this line would go away :conditions => {:attenders => {:attending => true}},
:group => "sessions.id",
:having => "count(sessions.id) > 1"
That joins query might look like this
:joins => "inner join member_meetings mm ON mm.meeting_id = meetings.id
inner join attendees at ON mm.attendee_id = at.id AND
at.attending is true"
Note that I aliased both member_meetings & attendees.

ActiveRecord find all parents that have associated children

I don't know why I can't figure this out, I think it should be fairly simple. I have two models (see below). I'm trying to come up with a named scope for SupplierCategory that would find all SupplierCategory(s) (including :suppliers) who's associated Supplier(s) are not empty.
I tried a straight up join, named_scope :with_suppliers, :joins => :suppliers which gives me only categories with suppliers, but it gives me each category listed separately, so if a category has 2 suppliers, i get the category twice in the returned array:
Currently I'm using:
named_scope :with_suppliers, :include => :suppliers
and then in my view I'm using:
<%= render :partial => 'category', :collection => #categories.find_all{|c| !c.suppliers.empty? } %>
Not exactly eloquent but illustrates what I'm trying to achieve.
Class Definitions
class SupplierCategory < AR
has_many :suppliers, :order => "name"
end
class Supplier < AR
belongs_to :supplier
end
Here is one more approach:
named_scope :with_suppliers, :include => :suppliers,
:conditions => "suppliers.id IS NOT NULL"
This works because Rails uses OUTER JOIN for include clause. When no matching rows are found the query returns NULL values for supplier columns. Hence NOT NULL check returns the matching rows.
Rails 4
scope :with_suppliers, { includes(:steps).where("steps.id IS NOT NULL") }
Or using a static method:
def self.with_suppliers
includes(:steps).where("steps.id IS NOT NULL")
end
Note:
This solution eager loads suppliers.
categories = SupplierCategory.with_suppliers
categories.first.suppliers #loaded from memory
class SupplierCategory < AR
has_many :supliers
def self.with_supliers
self.all.reject{ |c| c.supliers.empty? }
end
end
SupplierCategory.with_supliers
#=> Array of SuplierCategories with supliers
Another way more flexible using named_scope
class SupplierCategory < AR
has_many :supliers
named_scope :with_supliers, :joins => :supliers, :select => 'distinct(suplier_categories.id), suplier_categories.*', :having => "count(supliers.id) > 0"
end
SupplierCategory.with_supliers(:all, :limit => 4)
#=> first 4 SupplierCategories with suppliers
Simpler version:
named_scope :with_suppliers, :joins => :suppliers, :group => :id
If you want to use it frequently, consider using counter_cache.
I believe it would be something like
#model SupplierCategory
named_scope :with_suppliers,
:joins => :suppliers,
:select => "distinct(supplier_categories), supplier_categories.*",
:conditions => "suppliers.supplier_categories_id = supplier_categories.id"
Let me know if it works for you.
Edit:
Using fl00r's idea:
named_scope :with_suppliers,
:joins => :suppliers,
:select => "distinct(supplier_categories), supplier_categories.*",
:having => "count(supliers.id) > 0"
I believe this is the faster way.

How do I do a .count on the model an object belongs_to in rails?

I have #contacts_added defined as follows:
#contacts_added = Contact.all(:conditions => ["date_entered >?", 5.days.ago.to_date])
Each contact belongs_to a Company.
I want to be able the count the number of distinct Companies that #contacts_added belong to. contacts_added will have many contacts that belong to a single company, accessible through a virtual attribute contacts_added.company_name
How do I do that?
#contacts_added.map(&:company_name).uniq.length
sql (ORM) solution:
#contacts_added_companies = Contact.count(:joins => :company, :conditions => ["date_entered >?", 5.days.ago.to_date], :select => 'DISTINCT(company.id)' )

HABTM Relationships and the Join Table

I'm trying to connect the values of two join tables that I have and show the results based on a conditional relationship...and i'm having some problems
I have a Users Model(:name, :password, :email), and Events model(:name, :etc) and Interests model (:name)
I created about 5 records in each model.
Then I created two join tables -> UsersInterests and EventsInterests; each not containing a primary key and only comprised of the user_id/interest_id and event_id/interest_id respectively.
Then I added to the model files the HABTM Relationship
users => has_and_belongs_to_many :interests
events => has_and_belongs_to_many :interests
interests => has_and_belongs_to_many :users
has_and_belongs_to_many :events
Now I wanted to create a controller that finds only the events where the users interests correspond with the events interests
From working on this for a while I've figured that I need something in the area of
#Events = Event.User.find([condition])
[condition] = where users.interest == event.interest
or something like that... I'm kind of lost..How do you state the find condition?...I know how to do the inner join in sql but I'm looking for the elegant Rails way to do this... any tips guys?
The elegant ruby way to do this is with named scopes. However because you've decided to use has_and_belongs_to_many relationships instead of has_many :through relationships, you're going to need to define the join with raw SQL, which isn't very elegant. And because of the way Rails handles SQL generation, you will have to make a scope for use with a single user, and a second named scope for use with many users.
Class Event < ActiveRecord::Base
...
#find events that share an interest with a single user
named_scope :shares_interest_with_user, lambda {|user|
{ :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
"LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
:conditions => ["ui.user_id = ?", user], :group_by => "events.id"
}
#find events that share an interest with a list of users
named_scope :shares_interest_with_users, lambda {|users|
{ :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
"LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
:conditions => ["ui.user_id IN ?", users], :group_by => "events.id"
}
}
#find events that share an interest with any user
named_scope :shares_interest_with_any_user, lambda {
{ :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
"JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
:conditions => "ui.user_id IS NOT NULL", :group_by => "events.id"
}
}
end
Now you can do this to get all the events a user might be interested in:
#events = Event.shares_interest_with_user(#user)
Or this to get all the events a list of users might be interested in:
#events = Event.shares_interest_with_users(#users)
But as I warned, that's not really elegant.
You can greatly simplify the joins if you redefine your relationships to be has_many through relationships with proper join models instead of HABTM relationships. Your case would require the nested has many through plugin for this to work. N.B. You'll have to add corresponding has_many/belongs_to statements in all of the other models. Even the join models.
Class Event < ActiveRecord::Base
has_many :event_interests
has_many :interests, :through => :event_interests
has_many :user_interests, :through => :interests
has_many :users, :through => :user_interests
...
#find events that share an interest with a list of users
named_scope :shares_interest_with_users, lambda {|user|
{ :joins => :user_interests, :group_by => "events.id",
:conditions => {:user_interests => {:user_id => user}}
}
}
#find events that share an interest with any user
named_scope :shares_interest_with_any_user, lambda {
{ :joins => :user_interests, :group_by => "events.id",
:conditions => "user_interests.user_id IS NOT NULL"
}
end
Now, the following will work.
#user = User.first; #users = User.find(1,2,3)
# #events = all events a single user would be interested in
#events = Event.shares_interest_with_users(#user)
# #events = all events any of the listed users would be interested in.
#events = Event.shares_interest_with_users(#user)
You could even define a named scope to select events that haven't happened yet and chain the two:
named_scope :future_events, lambda {
{ :conditions => ["start_time > ?", Time.now]}
}
Events.future_events #=> Events that haven't started yet.
# Events that a user would be interested in but only choose those
# that haven't started yet.
Events.future_events.shares_interest_with_user(#user)

how do I join and include the association

How do I use both include and join in a named scope?
Post is polymorphic
class Post
has_many :approved_comments, :class_name => 'Comment'
end
class Comment
belongs_to :post
end
Comment.find(:all, :joins => :post, :conditions =>
["post.approved = ? ", true], :include => :post)
This does not work as joins does an inner join, and include does a left out join.
The database throws an error as both joins can't be there in same query.
Have you yet tried simply omitting the :joins part of the ActiveRecord call? In my test case of a has_many association, :include will use a join if your conditions refer to the included table name. For example,
Comment.all :include => :post
will run two queries total: one for comments, and one for their posts. The Rails team says that that is better performance. However, if Rails detects that your conditions need a join,
Comment.all :include => :post, :conditions => ['post.approved = ?', true]
will run one query, since you need it.
Isn't ActiveRecord so smart?
If you want to get all the approved Posts with their Comments you can just do something like this:
Post.find(:all, :include => "comments", :conditions => ['approved = ?', true])
This will give you all the approved posts with all the comments.
You should not probably use join as it will result in too huge data set (product columns will be included for each comment related to it).

Resources