问题
Can you have a belongs_to
belongs_to
relationship in Rails?
Search results gave me two results. The fact that I can't find very much info on the subject, seems to indicate that it shouldn't be done or is bad practice.
I asked yesterday about a has_many relationship, but thought because I couldn't find information on this, I would generate a question so it is easier for people to search for this in the future. I'm paraphrasing another users answer (I hope this is ok).
A Shoe can have many Socks, but only one active Sock. Other Socks are inactive. All Socks are also unique with unique patterns. I want to make sure I don't have socks with the same pattern. I think I can do this three ways
class Sock < ActiveRecord::Base
belongs_to :shoe
end
To find out if a Sock is active or inactive, give its' owner shoe a reference to its active sock like so:
class Shoe < ActiveRecord::Base
belongs_to :sock
end
Go to its owner Shoe and check if the Shoe's active sock is the current Sock or not. E.g.
def is_active
owner_shoe.active_sock == self
Associate them with foreign keys
class CreateGettingDressed < ActiveRecord::Migration
def change
create_table :shoes do |t|
t.belongs_to :active_sock, foreign_key: "sock_id"
t.string :size
t.timestamps null: false
end
create_table :socks do |t|
t.belongs :owner_shoe, foreign_key: "shoe_id"
t.string :pattern
end
end
end
回答1:
The problem you have is your two pieces of functionality are conflicting:
A
Shoe
can have manySocks
, but only one activeSock
You're looking to associate the two models on two different associations. Although this is simply done, I feel the way you're trying to do is is a little restricted.
Here's how I'd set up the base association:
#app/models/sock.rb
class Sock < ActiveRecord::Base
#columns id | shoe_id | name | active (boolean) | created_at | updated_at
belongs_to :shoe
end
#app/models/shoe.rb
class Shoe < ActiveRecord::Base
#columns id | name | etc | created_at | updated_at
has_many :socks
scope :active, -> { where(active: true).first }
end
This will give you the ability to call:
@shoe = Shoe.find 1
@shoe.socks.active #-> first sock with "active" boolean as true
It will also negate the need to include an active?
method in your sock
model. You can call @shoe.socks.find(2).active?
to get a response as to whether it's active or not.
Now, this should work pretty well for basic functionality.
However, you mention several extensions:
if a Sock is
active
orinactive
I want to make sure I don't have socks with the same pattern
This adds extra specifications which I'd tackle with a join
model (has_many :through):
#app/models/sock.rb
class Sock < ActiveRecord::Base
has_many :shoe_socks
has_many :shoes, through: :shoe_socks
end
#app/models/shoe_sock.rb
class ShoeSock < ActiveRecord::Base
# columns id | shoe_id | sock_id | pattern_id | active | created_at | updated_at
belongs_to :shoe
belongs_to :sock
belongs_to :pattern
end
#app/models/shoe.rb
class Shoe < ActiveRecord::Base
has_many :shoe_socks
has_many :socks, through: :shoe_socks, extend: ActiveSock
scope :active, -> { where(active: true).first }
end
You can read more about the below code here:
#app/models/concerns/active_sock.rb
module ActiveSock
#Load
def load
captions.each do |caption|
proxy_association.target << active
end
end
#Private
private
#Captions
def captions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({active: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:active)
end
#Target
def target_collection
#load_target
proxy_association.target
end
This setup will basically put all the "logic" into the join model. IE you'll have a database of socks, one of shoes and a connecting DB with parings of both.
This will still permit you to call @shoe.socks.active
but without having to degrade the data integrity in your data models.
I have also added some code I wrote a while back - which gives you the ability to access attributes from the join model. It uses the proxy_association
object in ActiveRecord, so it doesn't invoke any more SQL.
This added code will append the active?
attribute to any associative Sock
objects.
回答2:
In Rails, belongs_to
is an indicator that the model has a foreign key. For instance, if a sock belongs_to
a shoe, then your sock table will have a shoe_id
field. There's a number of associations available to you, but having both shoes and socks use belongs_to
sounds needlessly complex.
The most straightforward solution is to change the language a little bit, and think of each sock as having one active shoe. That way if you switch a sock to another shoe, then you don't have anything to tidy up on the shoe. Your data's more likely to remain consistent that way.
class Shoe < ActiveRecord::Base
has_one :sock
end
class Sock < ActiveRecord::Base
belongs_to :shoe
end
You can now call shoe.sock
to get a shoe's active sock, or sock.shoe
to get a sock's shoe.
Alternatively, maybe you'd like each shoe to have a dedicated pool of socks. We can extend our previous solution to support that, name our relationship active_sock
to make things clearer, and use a validator to make sure that our sock is from the pool:
class Shoe < ActiveRecord::Base
has_many :socks
has_one :active_sock, class_name: "Sock", foreign_key: "active_shoe_id"
validates :active_sock_id, inclusion: { in: sock_ids }
end
class Sock < ActiveRecord::Base
belongs_to :shoe
def active?
shoe.active_sock == self
end
end
Now you can call shoe.socks
to get all the available socks for a shoe, and shoe.active_sock
to get the active sock. From the sock side, then sock.shoe
returns which shoe "owns" this sock, and sock.active?
returns whether this sock is currently active.
来源:https://stackoverflow.com/questions/32773946/belongs-to-belongs-to-association-only-no-has-many-or-has-one