I badge (come StackOverflow).Associazioni/chiavi esterne a colonne multiple in ActiveRecord/Rails
Alcuni di questi possono essere allegati a cose che si possono verificare (ad esempio un badge per> X commenti su un post è allegato al post). Quasi tutti sono disponibili in più livelli (ad es.> 20,> 100,> 200) e puoi avere un solo livello per ogni tipo di badge x indebitabile (= badgeset_id
).
Per rendere più facile per far rispettare il vincolo di un livello-per-badge, voglio badgings di specificare il distintivo da una chiave esterna a due colonne - badgeset_id
e level
- piuttosto che dalla chiave primaria (badge_id
), anche se badge ha anche una chiave primaria standard.
in codice:
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
Come è possibile specificare un'associazione belongs_to
che lo fa (e non cerca di utilizzare un badge_id
), in modo che posso usare il has_many :through
?
ETA: Questo funziona parzialmente (cioè @ badging.badge fabbrica), ma si sente sporco:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
noti che le condizioni sono in singoli apici e non doppio, il che rende interpretato a runtime piuttosto rispetto al tempo di caricamento.
Tuttavia, quando si tenta di utilizzare questo con l'associazione: through, viene visualizzato l'errore undefined local variable or method 'level' for #<User:0x3ab35a8>
. E nulla di ovvio (ad esempio 'badges.level = #{badgings.level}'
) sembra funzionare ...
ETA 2: Prendere il codice di EmFi e ripulirlo funziona un po '. È necessario aggiungere badge_set_id
a Badge, che è ridondante, ma vabbè.
Il codice:
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
Anche se questo funziona - ed è probabilmente una soluzione migliore - io non considero questa una risposta reale alla domanda di come fare a) multi-chiave chiavi esterne, o b) associazioni di condizioni dinamiche che funzionano con: attraverso associazioni. Quindi se qualcuno ha una soluzione per questo, per favore parla.
Che funziona, più o meno. Non è una risposta alla domanda, anche se è una risposta al problema e lo sto accreditando come tale. Ho ripulito il codice e inserito la domanda. – Sai
Lo so. Quello che stavi chiedendo sembrava andare oltre ciò che è facile fare con Rails. Hai cercato plugin? A prima vista, http://compositekeys.rubyforge.org/ sembra che potrebbe fare ciò che stai cercando. – EmFi