2009-10-27 155 views
7

我有徽章(类似StackOverflow)。ActiveRecord/Rails中的多列外键/关联

其中一些可以附加到可加标签的东西(例如,在帖子上添加了> X的评论徽章附加到帖子)。几乎所有人都进入多个级别(例如> 20,> 100,> 200),并且每个徽章x徽章类型只能有一个级别(= badgeset_id)。

为了更容易执行的一个级别,每个徽章的约束,我想badgings由两列的外键,指定他们的徽章 - badgeset_idlevel - 而不是由主键(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

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}')似乎工作...

ETA 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 2009-10-28 00:10:46

+0

我知道。你所问的似乎超出了Rails轻松完成的事情。你有没有找过插件?乍看之下,http://compositekeys.rubyforge.org/似乎可以做你想要的。 – EmFi 2009-10-28 00:51:02