Rails ActiveRecord Association

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-11 02:17:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!