Tengo insignias (como StackOverflow).Llaves foráneas de columna múltiple/asociaciones en ActiveRecord/Rails
Algunos de ellos se pueden adjuntar a elementos identificables (por ejemplo, una insignia para> X comentarios en una publicación se adjunta a la publicación). Casi todos vienen en varios niveles (por ejemplo,> 20,> 100,> 200), y solo puede tener un nivel por tipo de insignia x identificable (= badgeset_id
).
Para que sea más fácil hacer cumplir la restricción de un nivel-por-insignia, quiero badgings especificar su insignia por una clave externa de dos columnas - badgeset_id
y level
- en lugar de la clave principal (badge_id
), aunque insignias tiene una clave primaria estándar también.
En código:
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
cómo puedo especificar una asociación belongs_to
que hace eso (y no trata de utilizar un badge_id
), de modo que pueda utilizar el has_many :through
?
ETA: Esto funciona parcialmente (es decir, @ badging.badge obras), pero se siente sucia:
belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'
Tenga en cuenta que las condiciones individuales está en cotizaciones, no doble, lo que hace que sea interpretado en tiempo de ejecución en lugar que tiempo de carga
Sin embargo, cuando trato de usar esto con la asociación: a través de, obtengo el error undefined local variable or method 'level' for #<User:0x3ab35a8>
. Y nada obvio (por ejemplo, 'badges.level = #{badgings.level}'
) parece funcionar ...
ETA 2: Tomar el código de EmFi y limpiarlo funciona un poco. Requiere agregar badge_set_id
a Badge, que es redundante, pero bueno.
El código:
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
Aunque esto funciona - y es probablemente una mejor solución - no considero esto una respuesta real a la pregunta de cómo hacer a) las claves externas de múltiples claves, o b) asociaciones de condición dinámica que trabajan con: a través de asociaciones. Entonces, si alguien tiene una solución para eso, por favor hable.
que funciona, más o menos. No es exactamente una respuesta a la pregunta, aunque es una respuesta al problema, y lo atribuyo como tal. He limpiado su código y lo he puesto en la pregunta. – Sai
Lo sé. Lo que estaba preguntando parecía ir más allá de lo que se hace fácilmente con Rails. ¿Has buscado complementos? De un vistazo, http://compositekeys.rubyforge.org/ parece que podría hacer lo que estás buscando. – EmFi