through rails many how has_and_belongs_to_many has foreign example create belongs and active ruby-on-rails activerecord

ruby on rails - rails - has_and_belongs_to_many, evitando engaños en la tabla de unión



rails foreign key (12)

Prevenir duplicados solo en la vista (solución diferida)

Lo siguiente no impide escribir relaciones duplicadas con la base de datos, solo garantiza que los métodos de find ignoren los duplicados.

En Rails 5:

has_and_belongs_to_many :tags, -> { distinct }

Nota: la Relation#uniq se depreció en Rails 5 ( commit )

En Rails 4

has_and_belongs_to_many :tags, -> { uniq }

Evite que se guarden datos duplicados (mejor solución)

Opción 1: evitar duplicados del controlador:

post.tags << tag unless post.tags.include?(tag)

Sin embargo, varios usuarios pueden intentar post.tags.include?(tag) al mismo tiempo, por lo tanto, esto está sujeto a las condiciones de la carrera. Esto se discute here .

Para mayor robustez también puede agregar esto al modelo de publicación (post.rb)

def tag=(tag) tags << tag unless tags.include?(tag) end

Opción 2: crear un índice único

La forma más infalible de evitar duplicados es tener restricciones duplicadas en la capa de la base de datos. Esto se puede lograr agregando un unique index en la tabla.

rails g migration add_index_to_posts # migration file add_index :posts_tags, [:post_id, :tag_id], :unique => true add_index :posts_tags, :tag_id

Una vez que tenga el índice único, intentar agregar un registro duplicado generará un error ActiveRecord::RecordNotUnique . Manejar esto está fuera del alcance de esta pregunta. Ver esta pregunta .

rescue_from ActiveRecord::RecordNotUnique, :with => :some_method

Tengo un conjunto de modelos HABTM bastante simple

class Tag < ActiveRecord::Base has_and_belongs_to_many :posts end class Post < ActiveRecord::Base has_and_belongs_to_many :tags def tags= (tag_list) self.tags.clear tag_list.strip.split('' '').each do self.tags.build(:name => tag) end end end

Ahora todo funciona bien, excepto que tengo un montón de duplicados en la tabla de etiquetas.

¿Qué debo hacer para evitar duplicados (bases en el nombre) en la tabla de etiquetas?


Además de las sugerencias anteriores:

  1. agregar :uniq a la asociación has_and_belongs_to_many
  2. agregando índice único en la tabla de unión

Haría una comprobación explícita para determinar si la relación ya existe. Por ejemplo:

post = Post.find(1) tag = Tag.find(2) post.tags << tag unless post.tags.include?(tag)


Debería agregar un índice en la propiedad tag: name y luego usar el método find_or_create en el método Tags # create

docs


En Rails4:

class Post < ActiveRecord::Base has_and_belongs_to_many :tags, -> { uniq }

(cuidado, el -> { uniq } debe estar directamente después del nombre de la relación, antes de otros parámetros)

Documentación de rieles


Establezca la opción de uniq:

class Tag < ActiveRecord::Base has_and_belongs_to_many :posts , :uniq => true end class Post < ActiveRecord::Base has_and_belongs_to_many :tags , :uniq => true


Esto es muy viejo, pero pensé que compartiría mi manera de hacer esto.

class Tag < ActiveRecord::Base has_and_belongs_to_many :posts end class Post < ActiveRecord::Base has_and_belongs_to_many :tags end

En el código donde necesito agregar etiquetas a una publicación, hago algo como:

new_tag = Tag.find_by(name: ''cool'') post.tag_ids = (post.tag_ids + [new_tag.id]).uniq

Esto tiene el efecto de agregar / eliminar etiquetas automáticamente según sea necesario o de no hacer nada si ese es el caso.


Extraiga el nombre de la etiqueta para seguridad. Verifique si la etiqueta existe o no en su tabla de etiquetas, luego créela si no:

name = params[:tag][:name] @new_tag = Tag.where(name: name).first_or_create

Luego, verifique si existe dentro de esta colección específica, y presione si no:

@taggable.tags << @new_tag unless @taggable.tags.exists?(@new_tag)


Para mi trabajo

  1. agregando índice único en la tabla de unión
  2. anula el << método en la relación

    has_and_belongs_to_many :groups do def << (group) group -= self if group.respond_to?(:to_a) super group unless include?(group) end end


Preferiría ajustar el modelo y crear las clases de esta manera:

class Tag < ActiveRecord::Base has_many :taggings has_many :posts, :through => :taggings end class Post < ActiveRecord::Base has_many :taggings has_many :tags, :through => :taggings end class Tagging < ActiveRecord::Base belongs_to :tag belongs_to :post end

Luego, envolvería la creación en lógica para que los modelos Tag se reutilizaran si ya existían. Probablemente incluso pondría una restricción única en el nombre de la etiqueta para hacerla cumplir. Eso hace que sea más eficiente buscar de cualquier manera, ya que puede usar los índices en la tabla de unión (para encontrar todas las publicaciones para una etiqueta en particular, y todas las etiquetas para una publicación en particular).

La única pega es que no puede permitir el cambio de nombre de las etiquetas, ya que cambiar el nombre de la etiqueta afectaría todos los usos de esa etiqueta. Haga que el usuario elimine la etiqueta y cree una nueva en su lugar.


Puede pasar la opción :uniq como se describe en la documentación . También tenga en cuenta que :uniq opciones de :uniq no impiden la creación de relaciones duplicadas, solo garantiza que los métodos de acceso / búsqueda los seleccionarán una vez.

Si desea evitar duplicados en la tabla de asociación, debe crear un índice único y manejar la excepción. También validates_uniqueness_of no funciona como se esperaba porque puede caer en el caso de que una segunda solicitud escriba en la base de datos entre el momento en que la primera solicitud verifica si hay duplicados y escribe en la base de datos.



Trabajé al respecto creando un filtro before_save que corrige las cosas.

class Post < ActiveRecord::Base has_and_belongs_to_many :tags before_save :fix_tags def tag_list= (tag_list) self.tags.clear tag_list.strip.split('' '').each do self.tags.build(:name => tag) end end def fix_tags if self.tags.loaded? new_tags = [] self.tags.each do |tag| if existing = Tag.find_by_name(tag.name) new_tags << existing else new_tags << tag end end self.tags = new_tags end end end

Se podría optimizar ligeramente para trabajar en lotes con las etiquetas, también puede necesitar un soporte transaccional ligeramente mejor.