2009-10-27 53 views
7

Tôi có các huy hiệu (sắp xếp như StackOverflow).Nhiều khóa/liên kết ngoài của cột trong ActiveRecord/Rails

Một số trong số đó có thể được đính kèm với những thứ có thể gây hại (ví dụ: huy hiệu cho> nhận xét X trên bài đăng được đính kèm vào bài đăng). Hầu như tất cả đều có nhiều cấp độ (ví dụ:> 20,> 100,> 200) và bạn chỉ có thể có một cấp cho mỗi loại huy hiệu x có thể xáo trộn (= badgeset_id).

Để làm cho nó dễ dàng hơn để thực thi các chế một cấp mỗi huy hiệu, tôi muốn badgings để xác định huy hiệu của họ bằng một hai cột nước ngoài chủ chốt - badgeset_idlevel - chứ không phải bằng khóa chính (badge_id), mặc dù phù hiệu cũng có khóa chính chuẩn.

Trong mã:

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 

Làm thế nào tôi có thể chỉ định một hiệp hội belongs_to nào đó (và không cố gắng sử dụng một badge_id), vì vậy mà tôi có thể sử dụng has_many :through?

ETA: Đây một phần hoạt động (ví dụ: @ badging.badge hoạt động), nhưng cảm thấy bẩn:

belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}' 

Lưu ý rằng các điều kiện là trong đơn dấu ngoặc kép, không tăng gấp đôi, mà làm cho nó giải thích tại thời gian chạy khá hơn thời gian tải.

Tuy nhiên, khi cố gắng sử dụng điều này với: thông qua liên kết, tôi nhận được lỗi undefined local variable or method 'level' for #<User:0x3ab35a8>. Và không có gì rõ ràng (ví dụ: 'badges.level = #{badgings.level}') dường như hoạt động ...

ETA 2: Lấy mã của EmFi và làm sạch nó một chút. Nó yêu cầu thêm badge_set_id vào Huy hiệu, là thừa, nhưng tốt.

Mã:

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 

Trong khi làm việc này - và nó có thể là một giải pháp tốt hơn - Tôi không coi đây là một câu trả lời thực tế cho câu hỏi làm thế nào để làm một) khóa ngoại đa-key, hoặc b) các hiệp hội điều kiện động hoạt động với: thông qua các hiệp hội. Vì vậy, nếu có ai có một giải pháp cho điều đó, xin vui lòng lên tiếng.

Trả lời

1

Dường như nó có thể hoạt động tốt nhất nếu bạn tách Huy hiệu thành hai mô hình. Đây là cách tôi sẽ phá vỡ nó xuống để đạt được các chức năng mà bạn muốn. Tôi đã ném vào một số phạm vi được đặt tên để giữ cho mã thực sự làm mọi thứ sạch sẽ.

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

Điều đó hoạt động, nhiều hơn hoặc ít hơn. Nó không hoàn toàn là câu trả lời cho câu hỏi, mặc dù nó là một câu trả lời cho vấn đề, và tôi tin nó như vậy. Tôi đã xóa mã của bạn và đặt mã đó vào câu hỏi. – Sai

+0

Tôi biết. Những gì bạn đang yêu cầu dường như vượt xa những gì dễ dàng thực hiện với Rails. Bạn đã tìm các plugin chưa? Trong nháy mắt, http://compositekeys.rubyforge.org/ có vẻ như nó có thể làm những gì bạn đang tìm kiếm. – EmFi

Các vấn đề liên quan