I have icons (e.g. StackOverflow).
Some of them can be attached to meaningful things (for example, an icon is attached to the message for> X comments to the message). Almost all go to several levels (e.g.> 20,> 100,> 200), and you can only have one level for each meaningful x (= badgeset_id ).
To simplify the enforcement of a single-level restriction for each icon, I want the buggies to indicate their icon using a two-column foreign key - badgeset_id and level - instead of the primary key ( badge_id ), although the icons also have a standard primary key.
In code:
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
How can I specify the belongs_to association that does this (and does not try to use badge_id ), so that I can use has_many :through ?
ETA: This partially works (i.e. @ badging.badge works), but feels dirty:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
Note that conditions are enclosed in single quotes, not doubles, which makes it interpreted at runtime, not for loading.
However, trying to use this with: through an association, I get an undefined local variable or method 'level' for #<User:0x3ab35a8> . And nothing obvious (like 'badges.level = #{badgings.level}' ) seems to work ...
ETA 2: Taking EmFi code and clearing it works a bit. This requires adding badge_set_id to the icon, which is redundant but good.
The code:
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
While this works - and this is probably the best solution - I do not think this is the real answer to the question of how to make) multi-key foreign keys, or b) dynamic state associations that work with: through associations. Therefore, if anyone has a solution for this, please speak.