[英]Multiple column foreign keys / associations in ActiveRecord/Rails
我有徽章(類似於StackOverflow的徽章)。
其中一些可以附加到帶有徽章的事物上(例如,帖子上附加了> X評論的徽章)。 幾乎所有的標簽都具有多個級別(例如,> 20,> 100,> 200),並且每個可標記x標記類型(= badgeset_id
)只能具有一個級別。
為了使執行每個徽章一個級別的約束更加容易,我希望徽章使用兩列外鍵( badgeset_id
和level
)而不是主鍵( badge_id
)來指定其徽章,盡管徽章確實具有標准主鍵也是。
在代碼中:
class Badge < ActiveRecord::Base
has_many :badgings, :dependent => :destroy
# integer: badgeset_id, level
validates_uniqueness_of :badgeset_id, :scope => :level
end
class Badging < ActiveRecord::Base
belongs_to :user
# integer: badgset_id, level instead of badge_id
#belongs_to :badge # <-- how to specify?
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badgeset_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badgeset_id, :level, :user_id
# instead of this:
def badge
Badge.first(:conditions => {:badgeset_id => self.badgeset_id, :level => self.level})
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant badgeset, level, badgeable = nil
b = Badging.first(:conditions => {:user_id => proxy_owner.id, :badgeset_id => badgeset,
:badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)}) ||
Badging.new(:user => proxy_owner, :badgeset_id => badgeset, :badgeable => badgeable)
b.level = level
b.save
end
end
has_many :badges, :through => :badgings
# ....
end
我如何指定一個做到這一點的belongs_to
關聯(並且不嘗試使用badge_id
),以便可以使用has_many :through
?
預計到達時間:這部分有效(即@ badging.badge有效),但感覺很臟:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
請注意,條件用單引號引起來,而不是雙引號,這使得它在運行時而不是加載時進行解釋。
但是,當嘗試將其與:through關聯一起使用時,我得到undefined local variable or method 'level' for #<User:0x3ab35a8>
的錯誤undefined local variable or method 'level' for #<User:0x3ab35a8>
。 似乎沒有任何明顯的效果(例如'badges.level = #{badgings.level}'
)...
ETA 2:提取EmFi的代碼並對其進行一些清理。 它需要將badge_set_id
添加到Badge,這是多余的,但是很好。
代碼:
class Badge < ActiveRecord::Base
has_many :badgings
belongs_to :badge_set
has_friendly_id :name
validates_uniqueness_of :badge_set_id, :scope => :level
default_scope :order => 'badge_set_id, level DESC'
named_scope :with_level, lambda {|level| { :conditions => {:level => level}, :limit => 1 } }
def self.by_ids badge_set_id, level
first :conditions => {:badge_set_id => badge_set_id, :level => level}
end
def next_level
Badge.first :conditions => {:badge_set_id => badge_set_id, :level => level + 1}
end
end
class Badging < ActiveRecord::Base
belongs_to :user
belongs_to :badge
belongs_to :badge_set
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badge_set_id, :badge_id, :user_id
named_scope :with_badge_set, lambda {|badge_set|
{:conditions => {:badge_set_id => badge_set} }
}
def level_up level = nil
self.badge = level ? badge_set.badges.with_level(level).first : badge.next_level
end
def level_up! level = nil
level_up level
save
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant! badgeset_id, level, badgeable = nil
b = self.with_badge_set(badgeset_id).first ||
Badging.new(
:badge_set_id => badgeset_id,
:badge => Badge.by_ids(badgeset_id, level),
:badgeable => badgeable,
:user => proxy_owner
)
b.level_up(level) unless b.new_record?
b.save
end
def ungrant! badgeset_id, badgeable = nil
Badging.destroy_all({:user_id => proxy_owner.id, :badge_set_id => badgeset_id,
:badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)})
end
end
has_many :badges, :through => :badgings
end
盡管這可行-並且可能是更好的解決方案-但我不認為這是對如何執行a)多鍵外鍵或b)與:through關聯一起使用的動態條件關聯問題的實際答案。 因此,如果有人對此有解決方案,請說出來。
如果將“徽章”分為兩個模型,似乎最適合鍛煉。 這是我將其分解以實現所需功能的方式。 我添加了一些命名作用域,以保持實際執行操作的代碼的整潔。
class BadgeSet
has_many :badges
end
class Badge
belongs_to :badge_set
validates_uniqueness_of :badge_set_id, :scope => :level
named_scope :with_level, labmda {|level
{ :conditions => {:level => level} }
}
named_scope :next_levels, labmda {|level
{ :conditions => ["level > ?", level], :order => :level }
}
def next_level
Badge.next_levels(level).first
end
end
class Badging < ActiveRecord::Base
belongs_to :user
belongs_to :badge
belongs_to :badge_set
belongs_to :badgeable, :polymorphic => true
validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
validates_presence_of :badge_set_id, :badge_id, :user_id
named_scope :with_badge_set, lambda {|badge_set|
{:conditions => {:badge_set_id => badge_set} }
}
def level_up(level = nil)
self.badge = level ? badge_set.badges.with_level(level).first
: badge.next_level
save
end
end
class User < ActiveRecord::Base
has_many :badgings, :dependent => :destroy do
def grant badgeset, level, badgeable = nil
b = badgings.with_badgeset(badgeset).first() ||
badgings.build(
:badge_set => :badgeset,
:badge => badgeset.badges.level(level),
:badgeable => badgeable
)
b.level_up(level) unless b.new_record?
b.save
end
end
has_many :badges, :through => :badgings
# ....
end
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.