问题
Okay, so here is my question. I have a 3 different models, People, Roles, Client, and Store. Clients have many Stores and can also have many people. Stores have many people. People have various roles. 1 Person can work at multiple stores, and they may have different roles at each store.
For example. Joe may be an assistant manager at one store and a manager at another store. What I would like to be able to do is pull the correct roles by doing something like
Store.find(1).people.find(1).roles(would return 'assistant manager' for example) or
Store.find(2).people.find(1).roles(would return 'manager' for example). Is this possible to do in ActiveRecord?
I've created a table :roles_people which has the following definition:
create_table :roles_people, :id => false do |t| t.references :role t.references :person t.references :store t.references :client end
However i can't figure out how to get associations to work properly using this table. Can anyone point me in the right direction?
Thanks
回答1:
class People
belongs_to :client
has_many :store_roles
end
class Roles
has_many :store_roles
end
class StoreRole
belongs_to :role
belongs_to :people
belongs_to :store
end
class Client
has_many :stores
has_many :people
end
class Store
belongs_to :client
has_many :store_roles
has_many :roles, :through => :store_roles
end
Assume that all of those classes inherit from ActiveRecord::Base
;)
You're going to need to setup the migration and database structure to mirror these relationships. For each belongs_to
there is an :object_id
field on the table reference the appropriate table's id.
Your query is going to need to look something like:
Store.find(1).roles.find(:all, :conditions => ["store_roles.person_id = ?", 1])
I would probably add a method to the store model to make this a little easier:
def roles_for(person_id)
roles.find(:all, :conditions => ["store_roles.person_id = ?", person_id])
end
This way you can find the roles using:
Store.find(1).roles_for(1)
Or, better yet:
def self.roles_for(store_id, person_id)
Role.find(:all, :joins => :store_roles, :conditions => ["store_roles.store_id = ? AND store_roles.person_id = ?", store_id, person_id])
end
Which changes our finder to:
Store.roles_for(1, 1)
I would say that this last method is the most ideal since it causes only a single query, while each of the other options execute two queries to the database per role look-up (one to find the store, and one to get the roles for a person_id). Of course if you already have the Store object instantiated then it's not a big deal.
Hopefully this answer was sufficient :)
回答2:
I think what you want is has_many :through
class Person < ActiveRecord::Base
has_many :roles_people
has_many :roles, :through => :roles_people
end
class Store < ActiveRecord::Base
has_many :roles_people
has_many :people, :through => roles_people
end
You'll also need to add relationships to RolePerson:
class RolePerson < ActiveRecord::Base
belongs_to :store
belongs_to :person
has_one :role
end
Is that what you were looking for?
Very helpful link @blog.hasmanythrough.com
回答3:
has_and_belongs_to_many
is your friend.
class Person < ActiveRecord::Base
has_and_belongs_to_many :roles
end
That way, you can get all roles the person has by calling Person.roles.all
. The resulting query is going to use the people_roles
table. You can also use has_many :through
but have to build model classes for the join table yourself and maintain all the associations yourself. Sometimes it's necessary, sometimes it's not. Depends on the complexity of your actual model.
回答4:
Nice question. You can't do exactly what you wanted, but i guess we can come close. For completeness, i am going to recap your datastructure:
class Client
has_many :stores
end
class Store
has_many :people
has_many :roles
end
class Person
has_many :roles
has_many :stores
end
class Role
belongs_to :store
belongs_to :person
end
You see that the role does not need the link to the client, because that can be found straightaway from the store (i am assuming a stored is "owned" by only one client).
Now a role
is linked both to a person
and a store
, so a person can have different roles per store.
And to find these in a clean way, i would use a helper function:
class Person
has_many :roles
has_many :stores
def roles_for(store)
roles.where("store_id=?", store.id)
end
end
So you can't write something like
store.people.first.roles
to get the roles of the first person working for that store. But writing something like:
store.people.first.roles_for(store)
is not too hard i hope.
The reason why this is so is because in the context of the person (-> store.people.first
) we no longer have any notion of the store (how we got there).
Hope this helps.
回答5:
You need to change your table name in people_roles
and you can drop both store and client references:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
end
Role is something that belongs only to people.
You then need to use has_and_belongs_to_many:
class Person < ActiveRecord::Base
has_many :roles
has_many :stores, :through => :people_roles
end
class Store < ActiveRecord::Base
has_many :roles
has_many :people, :through => :people_roles
end
Than you can query:
Store.find(1).people.find(1).roles
来源:https://stackoverflow.com/questions/5212741/rails-activerecord-association