J'ai des badges (sorta comme StackOverflow).Clés étrangères/associations de plusieurs colonnes dans ActiveRecord/Rails
Certains d'entre eux peuvent être attachés à des objets pouvant être badgés (par exemple, un badge pour> X commentaires sur un article est attaché à la publication). Presque tous viennent dans plusieurs niveaux (par exemple> 20,> 100,> 200), et vous ne pouvez avoir qu'un seul niveau par type de badge x badgeable (= badgeset_id
).
Pour le rendre plus facile à appliquer la contrainte d'un niveau par badge, je veux badgings de spécifier leur insigne par une clé étrangère à deux colonnes - badgeset_id
et level
- plutôt que par clé primaire (badge_id
), bien que badges a aussi une clé primaire standard.
Dans le 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
Comment puis-je spécifier une association belongs_to
qui fait cela (et ne pas essayer d'utiliser un badge_id
), afin que je puisse utiliser le has_many :through
?
ETA: Cela fonctionne en partie (ie @ badging.badge fonctionne), mais se sent sale:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
Notez que les conditions sont en simples entre guillemets, pas le double, ce qui en fait interprété lors de l'exécution plutôt que le temps de chargement. Cependant, lorsque j'essaie de l'utiliser avec l'association: through, j'obtiens l'erreur undefined local variable or method 'level' for #<User:0x3ab35a8>
. Et rien d'évident (par exemple 'badges.level = #{badgings.level}'
) ne semble fonctionner ...
ETA 2: Prendre le code d'EmFi et le nettoyer un peu fonctionne. Il faut ajouter badge_set_id
à Badge, ce qui est redondant, mais bon.
Le 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
Bien que cela fonctionne - et il est probablement une meilleure solution - Je ne considère pas une réponse réelle à la question de savoir comment faire a) clés étrangères multi-clés, ou b) les associations de conditions dynamiques qui fonctionnent avec: à travers des associations. Donc, si quelqu'un a une solution pour cela, parlez s'il vous plaît.
Cela fonctionne, plus ou moins. Ce n'est pas tout à fait une réponse à la question, même si c'est une réponse au problème, et je le crédite en tant que tel. J'ai nettoyé votre code et l'ai mis dans la question. – Sai
Je sais. Ce que vous demandiez semblait aller au-delà de ce qui est facile à faire avec Rails. Avez-vous cherché des plugins? En un coup d'œil, http://compositekeys.rubyforge.org/ semble faire ce que vous cherchez. – EmFi