2009-10-27 3 views
7

배지가 있습니다 (예 : StackOverflow와 유사).ActiveRecord/Rails의 복수 열 외래 키/연결

일부는 badgeable 물건에 첨부 할 수 있습니다 (예 : 게시물의 X 코멘트에 대한 배지가 게시물에 첨부 됨). 거의 모두 여러 수준 (예 :> 20,> 100,> 200)으로 표시되며 배지 가능 x 배지 유형 (= badgeset_id) 당 하나의 수준 만 가질 수 있습니다. , 배지하지만 오히려 기본 키 (badge_id)보다는 - badgeset_idlevel -

내가 badgings은 2 열 외부 키에 의해 자신의 배지를 지정하려면, 그것은 쉽게 한 레벨 당 배지 제약 조건을 적용 할 수 있도록 표준 기본 키가 너무 있습니다. 코드에서

: 나는이 has_many :through을 사용할 수 있도록

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 

나는 것을 않습니다 (그리고 badge_id를 사용하려고하지 않습니다)를 belongs_to 연결을 지정할 수 있습니까?

ETA : 이것은 부분적으로 (즉, @ 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을 추가해야합니다. 중복되었지만 잘되었습니다.

코드 :이 작동하는 동안

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 

는 - 그것은 아마도 더 나은 솔루션입니다 - 나는) 멀티 키 외래 키 작업을 수행하는 방법에 대한 질문이 실제 답을 고려하지 않거나 b) 연관 관계를 통해 작동하는 동적 조건 연결. 그래서 누군가가 그 해결책을 가지고 있다면, 위로 말하십시오.

답변

1

배지를 두 모델로 분리하면 가장 잘 맞는 것 같습니다. 다음은 원하는 기능을 구현하는 방법을 설명합니다. 실제로 일을 깨끗하게하는 코드를 유지하기 위해 일부 명명 된 범위를 던졌습니다.

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 
+0

다소 효과가 있습니다. 이 질문에 대한 대답은 아니지만 문제의 해답이 될 수 있습니다. 나는 코드를 정리하고 질문에 넣었습니다. – Sai

+0

알아. 당신이 묻고있는 것은 Rails로 쉽게 할 수있는 것을 넘어서는 것처럼 보였습니다. 플러그인을 찾았습니까? 한눈에, http://compositekeys.rubyforge.org/는 당신이 찾고있는 것을 할 수있는 것처럼 보입니다. – EmFi