简体   繁体   中英

Rails model associations, has_many :through and has_one with the same model

I have two models: User and State. The state model has records for each of the 50 states in the United States.

I would like each User to have two attributes: one "home" state, and many states to "visit".

I know I have to set up some sort of model associations to achieve this, but not sure what the best approach is.

Here's what I have so far, but I know there must be something wrong with have has_many and has_one association to the same model.

#user.rb
class User < ActiveRecord::Base
   has_many :visits
   has_many :states, :through => :visits
   has_one :state
end

#visit.rb
class Visit < ActiveRecord::Base
   belongs_to :user
   belongs_to :state
end

#state.rb
class State < ActiveRecord::Base
   has many :visits
   has many :users, :through => :visits 
   belongs_to :user
end
   

Any suggestions?

In my opinion what you already have is almost right, except you would store the home state foreign key on the user like thus:

# user.rb
class User < ActiveRecord::Base
  belongs_to :state
  has_many :visits
  has_many :states, through: visits
end

# visit.rb
class Visit < ActiveRecord::Base
  belongs_to :user
  belongs_to :state
end

# state.rb
class State < ActiveRecord::Base
  has_many :visits
  has_many :users, through: :visits
end

You would then access the home state like thus:

u = User.first
u.state

And the visited states, like thus:

u = User.first
u.states

For programming clarity, you can rename your relations:

# user.rb
class User < ActiveRecord::Base
  belongs_to :home_state, class_name: "State"
  has_many :visits
  has_many :visited_states, class_name: "State", through: visits
end

# state.rb
class State < ActiveRecord::Base
  has_many :residents, class_name: "User"
  has_many :visits
  has_many :visitors, class_name: "User", through: :visits
end

Your domain model would make more sense:

u = User.first
u.home_state
u.visited_states

s = State.first
s.residents
s.visitors

I expect you'll probably want to store additional information about the visit, so keeping the HMT join table for the Visit model will allow you to do this, rather than going with a HABTM relation. You could then add attributes to the visit:

# xxxxxxxxxxxxxxxx_create_visits.rb
class CreateVisits < ActiveRecord::Migration
  def change
    create_table :visits do |t|
      t.text :agenda
      t.datetime commenced_at
      t.datetime concluded_at
      t.references :state
      t.references :user
    end
  end
end

I would like each User to have two attributes: one "home" state, and many states to "visit".

In your models, a state may only be home to one user ( belongs_to ).

The correct semantics would be

class User < AR::Base
  belongs_to :home_state, :class_name => "State", :foreign_key => "home_state_id", :inverse_of => :users_living
  has_and_belongs_to_many :visited_states, :through => :visits
  # ...
end

class State < AR::Base
  has_many :users_living, :class_name => "User", :inverse_of => :home_state
  # ...
end

You can't have a has_many and has_one relationship on a single model, in this case state. One solution is to:

create a static model of states, they do not need to be a database model, they could be a static variable on the state model: US_STATES = {'1' => 'AK', '2' => 'AL', etc} or you could use fixtures to load a table of states into the database (more complicated because you need to use a rake task or the db:seed task to load the fixtures into the db, but nice because you can use active record to manage the model).

then you can provide a home_state_id on the user model that defines the home_state and the visits are simply a join between user_id and the state_id.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM