Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs Menu
Docs Home
/ /

Personalizar comportamiento de asociación

En Mongoid, las asociaciones permiten crear relaciones entre modelos. En esta guía, aprenderá a usar Mongoid para personalizar el comportamiento de las asociaciones en su aplicación. Las siguientes secciones describen cómo personalizar dicho comportamiento.

Las extensiones le permiten agregar funcionalidad personalizada a una asociación. Puedes definir una extensión en una asociación especificando un bloque en la definición de la asociación, como se muestra en el siguiente ejemplo:

class Band
include Mongoid::Document
embeds_many :albums do
def find_by_name(name)
where(name: name).first
end
end
end
band.albums.find_by_name("Omega") # returns album "Omega"

Puede utilizar el class_name Macro para especificar un nombre de clase personalizado para una asociación. Esto es útil cuando se desea nombrar la asociación de forma distinta al nombre de la clase. El siguiente ejemplo utiliza la macro class_name para especificar que una asociación incrustada llamada records representa la clase Album:

class Band
include Mongoid::Document
embeds_many :records, class_name: "Album"
end

Por defecto, Mongoid utiliza el campo _id de la clase principal al buscar asociaciones. Puedes especificar diferentes campos para usar mediante el uso de las macros primary_key y foreign_key. El siguiente ejemplo especifica una nueva clave primaria y una clave foránea para la asociación albums en una clase Band:

class Band
include Mongoid::Document
field :band_id, type: String
has_many :albums, primary_key: 'band_id', foreign_key: 'band_id_ref'
end
class Album
include Mongoid::Document
field :band_id_ref, type: String
belongs_to :band, primary_key: 'band_id', foreign_key: 'band_id_ref'
end

Si especificas una asociación has_and_belongs_to_many, también puedes utilizar las macros inverse_primary_key y inverse_foreign_key. La macro inverse_primary_key especifica el campo en el modelo local que el modelo remoto utiliza para buscar los documentos. El macro inverse_foreign_key especifica el campo en el modelo remoto que almacena los valores encontrados en inverse_primary_key.

El siguiente ejemplo especifica una nueva clave primaria y foránea para las clases Band y Members en una asociación has_and_belongs_to_many:

class Band
include Mongoid::Document
field :band_id, type: String
field :member_ids, type: Array
has_many :members,
primary_key: 'member_id', foreign_key: 'member_ids',
inverse_primary_key: 'band_id', inverse_foreign_key: 'band_ids'
end
class Member
include Mongoid::Document
field :member_id, type: String
field :band_ids, type: Array
has_many :bands,
primary_key: 'band_id', foreign_key: 'band_ids',
inverse_primary_key: 'member_id', inverse_foreign_key: 'member_ids'
end

Puede especificar el alcance de una asociación usando el parámetro scope. El parámetro scope determina los documentos que Mongoid considera parte de una asociación. Una asociación con alcance devuelve solo los documentos que cumplen las condiciones de alcance cuando se consultan. Puede establecer el scope en un Proc con una aridad de cero o en un Symbol que haga referencia a un alcance nombrado en el modelo asociado. El siguiente ejemplo establece ámbitos personalizados en las asociaciones de una clase Band:

class Band
include Mongoid::Document
has_many :albums, scope: -> { where(published: true) }
# Uses a scope called "upcoming" on the Tour model
has_many :tours, scope: :upcoming
end

Nota

Puedes añadir documentos que no cumplen con las condiciones del alcance a una asociación. Mongoid guarda los documentos en la base de datos y estos aparecerán en la memoria asociada. Sin embargo, no verás los documentos al consultar la asociación.

Cuando Mongoid carga una asociación en la memoria, por defecto, utiliza la macro validates_associated para validar que también estén presentes todos los hijos. Mongoid valida a los hijos para los siguientes tipos de asociaciones:

  • embeds_many

  • embeds_one

  • has_many

  • has_one

  • has_and_belongs_to_many

Puede desactivar este comportamiento de validación configurando la macro validate en false al definir la asociación, como se muestra en el siguiente ejemplo:

class Band
include Mongoid::Document
embeds_many :albums, validate: false
end

Mongoid admite el polimorfismo en las clases hijas de asociaciones uno a uno y uno a muchos. Las asociaciones polimórficas permiten que una sola asociación contenga objetos de diferentes tipos de clases. Se puede definir una asociación polimórfica configurando la opción polymorphic como true en una asociación hija y añadiendo la opción as a la asociación principal. El siguiente ejemplo crea una asociación polimórfica en una clase Band:

class Tour
include Mongoid::Document
has_one :band, as: :featured
end
class Label
include Mongoid::Document
has_one :band, as: :featured
end
class Band
include Mongoid::Document
belongs_to :featured, polymorphic: true
end

En el ejemplo anterior, la asociación de :featured en la clase Band puede contener un documento Label o Album.

Importante

Mongoid solo admite el polimorfismo de hijo a padre. No se puede especificar una asociación superior de has_one o has_many como polimórfica.

has_and_belongs_to_many las asociaciones no admiten polimorfismo.

A partir de la versión 9.0.2, Mongoid admite tipos polimórficos personalizados a través de un registro global. Puedes especificar claves alternativas para representar diferentes clases, desvinculando tu código de los datos. El siguiente ejemplo especifica la string "artist" como una clave alternativa para la clase Band:

class Band
include Mongoid::Document
identify_as 'artist'
has_many :albums, as: :record
end

En el ejemplo anterior, la directiva identify_as instruye a Mongoid para almacenar la clase Band en la base de datos como el string "artist".

También puedes especificar múltiples alias, como se muestra en el siguiente ejemplo:

class Band
include Mongoid::Document
identify_as 'artist', 'group', 'troupe'
has_many :albums, as: :record
end

En el ejemplo anterior, artist es el nombre por defecto y los demás se usan únicamente para buscar registros. Esto le permite refactorizar su código sin romper las asociaciones en sus datos.

Los alias de tipo polimórfico son globales. Las claves que se especifiquen deben ser únicas en toda la base de código. Sin embargo, puede registrar resolutores alternativos que se puedan usar para diferentes subconjuntos de sus modelos. En este caso, las claves deben ser únicas solo para cada resolutor. El siguiente ejemplo muestra cómo registrar resolutores alternativos:

Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :mus
Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :tool
module Music
class Band
include Mongoid::Document
identify_as 'bnd', resolver: :mus
end
end
module Tools
class Band
include Mongoid::Document
identify_as 'bnd', resolver: :tool
end
end

Tanto Music::Band como Tools::Band están enmascarados como "bnd", pero cada modelo utiliza su propio resolver para evitar conflictos.

Puedes proporcionar dependent opciones a las asociaciones referenciadas para especificar cómo Mongoid gestiona los documentos asociados cuando se borra un documento. Puede especificar las siguientes opciones:

  • delete_all: Elimina todos los documentos secundarios sin ejecutar ninguna función de retorno de modelo.

  • destroy: elimina los documentos secundarios y ejecuta todos los callbacks del modelo.

  • nullifyEstablece la clave externa de los documentos secundarios en nil. El documento hijo podría quedar huérfano si solo lo referencia el padre.

  • restrict_with_exceptionAumenta una excepción si el documento secundario no está vacío.

  • restrict_with_error: Cancela la operación y retorna false si el documento hijo no está vacío.

Si no se especifican opciones dependent, Mongoid deja el documento secundario sin cambios cuando se elimina el documento principal. El documento secundario sigue haciendo referencia al documento principal eliminado, y si solo se hace referencia a través del principal, el documento secundario queda huérfano.

El siguiente ejemplo especifica dependent opciones en la clase Band:

class Band
include Mongoid::Document
has_many :albums, dependent: :delete_all
belongs_to :label, dependent: :nullify
end

Por defecto, Mongoid no guarda automáticamente documentos asociados de asociaciones no embebidas al guardar el documento padre. Esto puede resultar en referencias colgantes a documentos que no existen.

Puede usar la opción autosave en una asociación referenciada para guardar automáticamente los documentos asociados al guardar el documento principal. El siguiente ejemplo crea una clase Band con una clase Album asociada y especifica la opción autosave:

class Band
include Mongoid::Document
has_many :albums
end
class Album
include Mongoid::Document
belongs_to :band, autosave: true
end
band = Band.new
album = Album.create!(band: band)
# The band is persisted at this point.

Nota

Mongoid añade automáticamente la funcionalidad de guardado automático a una asociación que usa la opción accepts_nested_attributes_for.

No se necesita especificar la opción autosave para asociaciones incrustadas porque Mongoid guarda los documentos incrustados en el documento principal.

Puedes agregar la opción autobuild a las asociaciones uno a uno, como has_one y embeds_one, para instanciar automáticamente un nuevo documento al acceder a una asociación nil. El siguiente ejemplo añade la opción autobuild a una asociación en la clase Band:

class Band
include Mongoid::Document
embeds_one :label, autobuild: true
has_one :producer, autobuild: true
end

Cuando Mongoid toca un documento, actualiza el campo updated_at del documento a la fecha y hora actual. Puedes añadir la opción touch a cualquier belongs_to asociación para asegurar que Mongoid toque el documento padre cada vez que se actualice el documento hijo. El siguiente ejemplo añade la opción touch a una asociación en la clase Band:

class Band
include Mongoid::Document
field :name
belongs_to :label, touch: true
end

También puedes utilizar la opción touch para especificar otro campo en la asociación principal, como una string o un símbolo. Cuando Mongoid toca la asociación principal, establece tanto el campo updated_at como el campo especificado en la fecha y hora actuales.

El siguiente ejemplo instruye a Mongoid que toque el campo bands_updated_at:

class Band
include Mongoid::Document
belongs_to :label, touch: :bands_updated_at
end

Nota

En asociaciones embebidas, cuando se toca un documento incrustado, Mongoid toca a sus padres recursivamente. Por lo tanto, no es necesario agregar un atributo touch a una asociación embedded_in.

Mongoid no admite especificar campos adicionales para actualizar en embedded_in asociaciones.

Utiliza la opción counter_cache para almacenar la cantidad de objetos que pertenecen a un campo asociado. Cuando se especifica esta opción, Mongoid almacena un campo contador en el modelo principal asociado.

La opción counter_cache acepta uno de los dos valores siguientes para el nombre del campo de contador:

  • trueMongoid utiliza el nombre de campo de contador por defecto, en función del nombre de la relación. Por ejemplo, si la asociación se llama Band, el nombre del campo de contador por defecto es bands_count.

  • Un string o symbol: Mongoid usa ese valor directamente como el nombre del campo del contador.

Se puede proporcionar el contenedor para la caché del contador de una de las siguientes maneras:

  • Defina un campo explícito de contador en el modelo principal.

  • Incluya el módulo Mongoid::Attributes::Dynamic en el modelo principal, lo que permite a Mongoid crear el campo contador de forma dinámica en su primer uso.

Campo de contador explícito

En este enfoque, declara el campo contador directamente en el modelo principal. Esto es útil cuando se quiere un esquema fijo y tipos de campos explícitos.

El siguiente ejemplo declara un campo bands_count explícito en la clase Label y utiliza el nombre de campo de contador por defecto:

class Band
include Mongoid::Document
# Uses the default counter field name: bands_count
belongs_to :label, counter_cache: true
end
class Label
include Mongoid::Document
field :bands_count, type: Integer
has_many :bands
end

También puedes especificar un nombre de campo de contador personalizado pasando una string a la opción counter_cache:

class Band
include Mongoid::Document
belongs_to :label, counter_cache: :active_bands_count
end
class Label
include Mongoid::Document
field :active_bands_count, type: Integer
has_many :bands
end

Campo de Contador Dinámico

Si incluyes el módulo Mongoid::Attributes::Dynamic en el modelo padre, Mongoid puede crear y actualizar el campo de contador dinámicamente durante la ejecución. Esto es útil cuando quieres un esquema flexible y no quieres declarar explícitamente el campo del contador.

El siguiente ejemplo utiliza Mongoid::Attributes::Dynamic para permitir que Mongoid cree el campo de contador dinámicamente en la clase Label:

class Band
include Mongoid::Document
belongs_to:label, counter_cache: true
end
class Label
include Mongoid::Document
include Mongoid::Attributes::Dynamic
has_many :bands
end

Cuando utilizas el módulo Mongoid::Attributes::Dynamic, Mongoid utiliza las mismas reglas de nomenclatura para el campo contador que al declarar un campo contador explícito: se utiliza por defecto un nombre de campo *_count basado en la asociación, a menos que se especifique un nombre de campo personalizado con una string en la opción counter_cache.

Volver

Asociaciones incrustadas

En esta página