Mehrere Spalte Fremdschlüssel / Verbände in Active / Rails
-
06-07-2019 - |
Frage
Ich habe Abzeichen (sorta wie Stackoverflow).
Einige von ihnen befestigt werden kann, Dinge badgeable (zum Beispiel ein Abzeichen für> X Kommentare zu einem Beitrag an der Säule angebracht ist). Fast alle in mehreren Ebenen kommen (zum Beispiel> 20,> 100> 200), und Sie können nur eine Stufe pro badgeable x Abzeichen Typ haben (= badgeset_id
).
Um es einfacher, die auf einer Ebene-per-Abzeichen Einschränkung zu erzwingen, ich badgings will ihre Abzeichen von einem zweispaltigen Fremdschlüssel spezifizieren - badgeset_id
und level
- und nicht durch Primärschlüssel (badge_id
), obwohl Abzeichen tun habe einen Standard-Primärschlüssel zu.
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
Wie kann ich einen belongs_to
Verein angeben, die das tut (und nicht versucht, eine badge_id
zu verwenden), so dass ich die has_many :through
verwenden kann?
ETA: Diese teilweise funktioniert (d @ badging.badge Werke), fühlt sich aber schmutzig:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
Beachten Sie, dass die Bedingungen in Single Zitate, nicht verdoppeln, die es zur Laufzeit interpretiert macht anstatt Ladezeit.
Wenn jedoch versucht, dies mit der zu verwenden: durch Assoziation, erhalte ich die Fehler undefined local variable or method 'level' for #<User:0x3ab35a8>
. Und nichts offensichtlich (z 'badges.level = #{badgings.level}'
) scheint zu funktionieren ...
ETA 2: Unter Emfi Code und es auf ein bisschen Reinigungsarbeiten. Es erfordert das Hinzufügen badge_set_id
Abzeichen, die überflüssig ist, aber na ja.
Der 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
Während das funktioniert - und es ist wahrscheinlich eine bessere Lösung - ich halte dies nicht für eine tatsächliche Antwort auf die Frage, wie a) Multi-Keys Fremdschlüssel, oder b) dynamic-Zustand Assoziationen zu tun, die mit der Arbeit: durch Verbände. Also, wenn jemand eine Lösung dafür hat, bitte sprechen.
Lösung
Scheint, wie es vielleicht Training am besten, wenn Sie Abzeichen in zwei Modellen trennen. Hier ist, wie ich es brechen würde Sie die Funktionalität erreichen Sie wollen. Ich warf in einigen Scopes, den Code zu halten, die tatsächlich tut Dinge sauber.
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