ruby-on-rails - many - rails foreign key
Rails Polymorphic Association con mĂșltiples asociaciones en el mismo modelo (10)
Mi pregunta es esencialmente la misma que esta: Asociación polimórfica con múltiples asociaciones en el mismo modelo
Sin embargo, la solución propuesta / aceptada no funciona, como lo ilustra un comentarista posterior.
Tengo una clase de fotografía que se utiliza en toda mi aplicación. Una publicación puede tener una sola foto. Sin embargo, quiero reutilizar la relación polimórfica para agregar una foto secundaria.
Antes de:
class Photo
belongs_to :attachable, :polymorphic => true
end
class Post
has_one :photo, :as => :attachable, :dependent => :destroy
end
Deseado:
class Photo
belongs_to :attachable, :polymorphic => true
end
class Post
has_one :photo, :as => :attachable, :dependent => :destroy
has_one :secondary_photo, :as => :attachable, :dependent => :destroy
end
Sin embargo, esto falla ya que no puede encontrar la clase "SecondaryPhoto". Basado en lo que podría decir de ese otro hilo, me gustaría hacer:
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy
Excepto que se llama a Post # secondary_photo, simplemente devuelve la misma foto que se adjunta a través de la asociación de fotos, por ejemplo, Post # photo === Post # secondary_photo. Mirando el SQL, lo hace DONDE type = "Photo" en lugar de, digamos, "SecondaryPhoto" como me gustaría ...
¿Pensamientos? ¡Gracias!
Para uso mongoide esta solución
Tuve tiempos difíciles después de descubrir este problema pero obtuve una solución genial que funciona
Agregar a su Gemfile
gem ''mongoid-multiple-polymorphic''
Y esto funciona como un encanto:
class Resource
has_one :icon, as: :assetable, class_name: ''Asset'', dependent: :destroy, autosave: true
has_one :preview, as: :assetable, class_name: ''Asset'', dependent: :destroy, autosave: true
end
¿Puedes agregar un modelo de SecondaryPhoto como:
class SecondaryPhoto < Photo
end
y luego omita: class_name de has_one: secondary_photo?
Algo parecido a lo siguiente funcionó para las consultas, pero la asignación desde el usuario a la dirección no funcionó
Clase de usuario
has_many :addresses, as: :address_holder
has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" },
class_name: "Address", foreign_key: "address_holder_id"
Clase de dirección
belongs_to :address_holder, polymorphic: true
Lo he hecho en mi proyecto.
El truco es que las fotos necesitan una columna que se utilizará en la condición de has_one para distinguir entre fotos primarias y secundarias. Preste atención a lo que sucede en :conditions
aquí.
has_one :photo, :as => ''attachable'',
:conditions => {:photo_type => ''primary_photo''}, :dependent => :destroy
has_one :secondary_photo, :class_name => ''Photo'', :as => ''attachable'',
:conditions => {:photo_type => ''secondary_photo''}, :dependent => :destroy
La belleza de este enfoque es que cuando creas fotos usando @post.build_photo
, el tipo_de_la foto se rellenará automáticamente con el tipo correspondiente, como ''primary_photo''. ActiveRecord es lo suficientemente inteligente como para hacer eso.
Ninguna de estas soluciones parece funcionar en Rails 5. Por alguna razón, parece que el comportamiento en torno a las condiciones de asociación ha cambiado. Al asignar el objeto relacionado, las condiciones no parecen usarse en el inserto; solo cuando lees la asociación.
Mi solución fue anular el método setter para la asociación:
has_one :photo, -> { photo_type: ''primary_photo''},
as: ''attachable'',
dependent: :destroy
def photo=(photo)
photo.photo_type = ''primary_photo''
super
end
Ninguna de las respuestas anteriores me ayudó a resolver este problema, así que pondré esto aquí en caso de que alguien más se encuentre con esto. Usando Rails 4.2 +.
Cree la migración (suponiendo que ya tenga una tabla Addresses):
class AddPolymorphicColumnsToAddress < ActiveRecord::Migration
def change
add_column :addresses, :addressable_type, :string, index: true
add_column :addresses, :addressable_id, :integer, index: true
add_column :addresses, :addressable_scope, :string, index: true
end
end
Configura tu asociación polimórfica:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
Configure la clase desde donde se llamará a la asociación:
class Order < ActiveRecord::Base
has_one :bill_address, -> { where(addressable_scope: :bill_address) }, as: :addressable, class_name: "Address", dependent: :destroy
accepts_nested_attributes_for :bill_address, allow_destroy: true
has_one :ship_address, -> { where(addressable_scope: :ship_address) }, as: :addressable, class_name: "Address", dependent: :destroy
accepts_nested_attributes_for :ship_address, allow_destroy: true
end
El truco es que debe llamar al método de compilación en la instancia de Order
o la columna de scope
no se completará.
Entonces esto NO funciona:
address = {attr1: "value"... etc...}
order = Order.new(bill_address: address)
order.save!
Sin embargo, esto SÍ FUNCIONA.
address = {attr1: "value"... etc...}
order = Order.new
order.build_bill_address(address)
order.save!
Espero que ayude a alguien más.
No lo usé, pero busqué en Google y estudié las fuentes de Rails y creo que lo que estás buscando es :foreign_type
. Pruébalo y di si funciona :)
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => ''SecondaryPost''
Creo que el tipo en su pregunta debería ser Post
lugar de Photo
y, respectivamente, sería mejor usar SecondaryPost
como asignado al modelo de Post
.
EDITAR:
La respuesta anterior es completamente incorrecta. :foreign_type
está disponible en el modelo polimórfico en la asociación belongs_to
para especificar el nombre de la columna que contiene el tipo de modelo asociado.
Cuando miro en las fuentes de Rails, esta línea establece este tipo para la asociación:
dependent_conditions << "#{reflection.options[:as]}_type = ''#{base_class.name}''" if reflection.options[:as]
Como puede ver, usa base_class.name
para obtener el nombre de tipo. Por lo que sé, no puedes hacer nada con eso.
Entonces mi sugerencia es agregar una columna al modelo de Foto, por ejemplo: photo_type
. Y ajústelo a 0 si es la primera foto, o ajústelo a 1 si es la segunda foto. En sus asociaciones agregue :conditions => {:photo_type => 0}
y :conditions => {:photo_type => 1}
, respectivamente. Sé que no es una solución que está buscando, pero no puedo encontrar nada mejor. Por cierto, ¿tal vez sería mejor usar simplemente has_many
association?
Rails 4.2+
class Photo
belongs_to :attachable, :polymorphic => true
end
class Post
has_one :photo, :as => :attachable, :dependent => :destroy
has_one :secondary_photo, -> { where attachable_type: "SecondaryPhoto"},
class_name: Photo, foreign_key: :attachable_id,
foreign_type: :attachable_type, dependent: :destroy
end
Debe proporcionar foreign_key de acuerdo con .... able''ness o Rails solicitará la columna post_id en la tabla de fotos. La columna Attachable_type se llenará con la magia de Rails como SecondaryPhoto
Vas a tener que parchear la noción de tipo_extranjero en una relación de has_one. Esto es lo que hice para has_many. En un nuevo archivo .rb en su carpeta de inicializadores, llamé a mine add_foreign_type_support.rb. Le permite especificar cuál será su attachable_type. Ejemplo: has_many photo,: class_name => "Picture",: as => attachable,: foreign_type => ''Pic''
module ActiveRecord
module Associations
class HasManyAssociation < AssociationCollection #:nodoc:
protected
def construct_sql
case
when @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
when @reflection.options[:as]
resource_type = @reflection.options[:foreign_type].to_s.camelize || @owner.class.base_class.name.to_s
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND "
@finder_sql += "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(resource_type)}"
else
@finder_sql += ")"
end
@finder_sql << " AND (#{conditions})" if conditions
else
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
@finder_sql << " AND (#{conditions})" if conditions
end
if @reflection.options[:counter_sql]
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
elsif @reflection.options[:finder_sql]
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (///*.*?/*// )?(.*)/bFROM/b/im) { "SELECT #{$1}COUNT(*) FROM" }
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
else
@counter_sql = @finder_sql
end
end
end
end
end
# Add foreign_type to options list
module ActiveRecord
module Associations # :nodoc:
module ClassMethods
private
mattr_accessor :valid_keys_for_has_many_association
@@valid_keys_for_has_many_association = [
:class_name, :table_name, :foreign_key, :primary_key,
:dependent,
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
:as, :foreign_type, :through, :source, :source_type,
:uniq,
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
:validate, :inverse_of
]
end
end
Referencia futura para las personas que revisan esta publicación
Esto se puede lograr usando el siguiente código ...
Rails 3:
has_one :banner_image, conditions: { attachable_type: ''ThemeBannerAttachment'' }, class_name: ''Attachment'', foreign_key: ''attachable_id'', dependent: :destroy
Rails 4:
has_one :banner_image, -> { where attachable_type: ''ThemeBannerAttachment''}, class_name: ''Attachment'', dependent: :destroy
No estoy seguro de por qué, pero en Rails 3, debe proporcionar un valor de foreign_key junto con las condiciones y class_name. No use ''as:: attachable'' ya que esto utilizará automáticamente el nombre de la clase llamante cuando configure el tipo polimórfico.
Lo anterior también se aplica a has_many.