Rails: Finding children of children in habtm activerecord relationships - ruby-on-rails

I have 3 classes related by habtm associations...Users, Locations, Employees.
Users is related to Locations via a habtm relationship, and Locations is related to Employees via a habtm relationship.
What I would like to be able to do is say:
current_user.locations.employees
Does anyone know the "Rails Way" to do this? I can do it in SQL, but I am wondering if there is a cleaner way.

You can extend associations in ActiveRecord:
class User
has_many :locations do
def employees
# Use whatever logic you'd like here.
locations.find(:all, :include => [:employees]).collect {|l| l.employees }
end
end
end
u = User.find 1
u.locations.employees #=> calls our method defined above
And see this:
http://ryandaigle.com/articles/2006/12/3/extend-your-activerecord-association-methods
You may also try has_many :through:
class User
has_many :user_locations
has_many :locations, :through => :user_locations
# Not sure if you can nest this far, this guy has problems with it:
# http://tim.theenchanter.com/2008/10/how-to-hasmany-through-hasmany-through.html
#
# Maybe if locations was a habtm instead of :through? experiment with it!
#
has_many :employees, :through => :locations
end
u = User.find 1
u.employees #=> Uses associations to figure out the SQL
In general, Lee, I'm concerned about your data model. HABTM relationships are not really recommended now. Using has_many :through allows you to name the join table, and then you can store attributes on a relationship with better business meaning. I would say the "Railsy" thing is to add some through relationships to expose more domain modelling.
Also, some example models would be helpful to really understand your question.
Good luck!

I assume that the following is true and that your ActiveRecords will reflect this.
user belongs_to location
location has_many users
location has_many employees
employee belongs_to location
then you can say in your User ActiveRecord
has_many :employees :through => :locations
I think that should work.
UPDATE:
Just realized that your user has many locations as well. Not quite sure if the above will work or not. Worth a try though.

You could define method in User sort of like the following
def get_all_related_employees
employees = []
self.locations.each do |location|
employees << location.employees
end
employees
end
or you could do it right inline
current_user.locations.map {|location| location.employees }.each do |employee|
puts employee.name
end

Related

Custom scope on has_many, :through association (Rails 4)

CONTEXT:
In my setup Users have many Communities through CommunityUser, and Communities have many Posts through CommunityPost. If follows then, that Users have many Posts through Communities.
User.rb
has_many :community_users
has_many :communities, through: :community_users
has_many :posts, through: :communities
Given the above User.rb, Calling "current_user.posts" returns posts with one or more communities in common with current_user.
QUESTION:
I'm looking to refine that association so that calling "current_user.posts" returns only those posts whose communities are a complete subset of the current_user's communities.
So, given a current_user with community_ids of [1,2,3], calling "current_user.posts" would yield only those posts whose "community_ids" array is either 1, [2], [3], [1,2], [1,3], [2,3], or [1,2,3].
I've been researching scopes here, but can't seem to pinpoint how to accomplish this successfully...
Nice question...
My immediate thoughts:
--
ActiveRecord Association Extension
These basically allow you to create a series of methods for associations, allowing you to determine specific criteria, like this:
#app/models/user.rb
has_many :communities, through: :community_users
has_many :posts, through: :communities do
def in_community
where("community_ids IN (?)", user.community_ids)
end
end
--
Conditional Association
You could use conditions in your association, like so:
#app/models/user.rb
has_many :posts, -> { where("id IN (?)", user.community_ids) }, through: :communities #-> I believe the model object (I.E user) is available in the association
--
Source
I originally thought this would be your best bet, but looking at it more deeply, I think it's only if you want to use a different association name
Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, through: :subscriptions will look
for either :subscribers or :subscriber on Subscription, unless a
:source is given.
Being honest, this is one of those questions which needs some thought
Firstly, how are you storing / calling the community_ids array? Is it stored in the db directly, or is it accessed through the ActiveRecord method Model.association_ids?
Looking forward to helping you further!
You don't show the model and relationship definitions for for Community or CommunityPost so make sure you have a has_many :community_posts and a has_many :posts, through: :community_posts on your Community model and a belongs_to :community and a belongs_to :post on CommunityPost. If you don't need to track anything on ComunityPost you could just use a has_and_belongs_to_many :posts on Community and a join table communities_posts containing just the foreign keys community_id and post_id.
Assuming you have the relationships setup as I describe you should be able to just use current_user.posts to get a relation that can be further chained and which returns all posts for all communities the user is associated with when you call .all on it. Any class methods (such as scope methods) defined your Post model will also be callable from that relation so that is where you should put your refinements unless those refinements pertain to the Community or CommunityPost in which case you would put them on the Community or CommunityPost models respectively. Its actually rare to need an AR relationship extension for scopes since usually you also want to be able to refine the model independently of whatever related model you may use to get to it.

Rails Query Associations

I am fairly new to Rails and understand the basics of joins and associations but I am having a challenge with how to best use rails for some general queries. There is a lot of documentation I have reviewed but still not clear on best approach for this query. The models are as follows
User:
has_many :accounts
Account:
belongs_to :user
belongs_to :address, :class_name => "Location", :foreign_key => 'address_id'
Address:
belongs_to :account (This is really a many to one relationship where an address,
can belong to more than one account)
The query is to find all account addresses for the user. A standard sql query would look something like this.
SELECT Users.ID AS Users_ID, Users.Username, Account.acct_name, Address.*
FROM Address INNER JOIN (Users INNER JOIN Account ON Users.ID = Account.user_id)
ON Address.ID = Account.address_id
WHERE (((Users.ID)=2));
There seems to be a lot of power in rails to do this without a direct sql query. What is the best approach to doing this and are the models correct? Is the foreign key representation in the correct model.
Thank you in advance for your assistance.
You might be looking for a has_many :through association.
class User < AR::B
has_many :accounts
has_many :addresses, through: :accounts
end
You can then query all addresses of the user with #user.addresses.
If you want to take a look at the SQL query that ActiveRecord generates for your query, you may call .to_sql on a query: #user.addresses.to_sql.
Edit: Regarding the rest of your associations setup, you'll need to rework how Address and Account are joined. One approach might be this one.
class Account < AR::B
belongs_to :user
belongs_to :address # the accounts table should have an address_id integer column
end
class Address < AR::B
has_many :accounts
end
For more info on the options you can pass to the associations, see the Rails API Docs.
First, based on what you've described, the Address class should probably have the relationship has_many :accounts rather than belongs_to :account. The general rule of thumb is to use belongs_to when there's a foreign key in that model's table for the belongs_to table. But for this query that really doesn't matter, as you're starting from users.
Something like this should be close to what you want:
User.where(:id => 2).includes(:accounts => :addresses)
See the Rails guide for more information. The squeel gem is also useful for even more advanced querying capabilities.

Rails association with almost all other models

I'm looking for some suggestions on how to deal with "Regions" in my system.
Almost all other models in the system (news, events, projects, and others) need to have a region that they can be sorted on.
So far, I've considered a Region model with has_many :through on a RegionLink table. I've never had a model joined to so many others and wonder if this route has any negatives.
I've also considered using the acts_as_taggable_on gem and just tag regions to models. This seems ok but I'll have to write more cleanup type code to handle the customer renaming or removing a region.
Whatever I choose I need to handle renaming and, more importantly, deleting regions. If a region gets deleted I will probably just give the user a choice on another region to replace the association.
Any advice on handling this is greatly appreciated.
If each News, Event, etc. will belong to only 1 Region, tags don't seem the most natural fit IMO. This leaves you with 2 options:
Add a region_id field to each model
This is simplest, but has the drawback that you will not be able to look at all the "regioned" items at once - you'll have to query the news, events, etc. tables separately (or use a UNION, which ActiveRecord doesn't support).
Use RegionLink model with polymorphic associations
This is only slightly more complicated, and is in fact similar to how acts_as_taggable_on works. Look at the Rails docs on *belongs_to* for a fuller description of polymorphic relationships if you are unfamiliar
class Region < ActiveRecord::Base
has_many :region_links
has_many :things, :through => :region_links
end
# This table with have region_id, thing_id and thing_type
class RegionLink < ActiveRecord::Base
belongs_to :region
belongs_to :thing, :polymorphic => true
end
class Event < ActiveRecord::Base
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
end
# Get all "things" (Events, Projects, etc.) from Region #1
things = Region.find(1).things
Renaming is quite simple - just rename the Region. Deleting/reassigning regions is also simple - just delete the RegionLink record, or replace it's region_id.
If you find yourself duplicating a lot of region-related code in your Event, etc. models, you may want to put it into a module in lib or app/models:
module Regioned
def self.inluded(base)
base.class_eval do
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
...
end
end
end
class Event < ActiveRecord::Base
include Regioned
end
class Project < ActiveRecord::Base
include Regioned
end
Checkout the cast about polymorphic associations. They did change a bit in rails 3 though: http://railscasts.com/episodes/154-polymorphic-association?view=asciicast

rails 3 habtm delete only association

class Company
has_and_belongs_to_many :users
end
class User
has_and_belongs_to_many :companies
end
when i delete a company, what's the best (recommended) way to delete ONLY the associations of the users from that company? (i mean not the actual users, only the associations)
I prefer the following since it keeps model logic in the model. I don't understand why ActiveRecord doesn't just do it. Anyway, in both joined models, I add the following callback.
before_destroy {|object| object.collection.clear}
So in your example:
class Company
has_and_belongs_to_many :users
before_destroy {|company| company.users.clear}
end
class User
has_and_belongs_to_many :companies
before_destroy {|user| user.companies.clear}
end
In a lot of discussions around doing a cascade delete on a collection association, many people declare the HABTM association dead and recommend has_many :through instead. I disagree. Use whatever makes sense. If the association has no intrinsic attributes, then use HABTM.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many
collection.delete will do the trick.
If you call destroy instead of delete, associations will be deleted automatically.

How do I do multiple has_and_belongs_to_many associations between the same two classes?

I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end

Resources