ActiveRecord/Rails의 여러 열 외래 키/연결
-
06-07-2019 - |
문제
배지가 있습니다(StackOverflow와 같은 것).
그 중 일부는 배지를 부착할 수 있는 항목(예:게시물의 X개 이상의 댓글에 대한 배지가 게시물에 첨부됩니다.거의 모든 것이 여러 수준으로 제공됩니다(예:>20, >100, >200), 배지를 받을 수 있는 x 배지 유형당 하나의 레벨만 가질 수 있습니다(= badgeset_id
).
배지당 1레벨 제약 조건을 더 쉽게 적용하기 위해 배지에서 2열 외래 키로 배지를 지정하고 싶습니다. 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>
.그리고 명백한 것은 없습니다(예: 'badges.level = #{badgings.level}'
) 작동하는 것 같습니다 ...
도착 예정 시간 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 연관과 함께 작동하는 동적 조건 연관을 수행하는 방법에 대한 질문에 대한 실제 대답이라고 생각하지 않습니다.따라서 이에 대한 해결책이 있는 사람이 있으면 말해주세요.
해결책
Badge를 두 가지 모델로 분리하면 운동 효과가 가장 좋을 것 같습니다.원하는 기능을 달성하기 위해 분석하는 방법은 다음과 같습니다.실제로 작업을 수행하는 코드를 깔끔하게 유지하기 위해 명명된 범위를 추가했습니다.
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