Overview
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.
Extensiones
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"
Nombres personalizados de asociaciones
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
Teclas personalizadas
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
Alcances personalizados
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.
validació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_manyembeds_onehas_manyhas_onehas_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
polimorfismo
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.
Tipos polimórficos personalizados
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.
Comportamiento dependiente
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 ennil. 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 retornafalsesi 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
Guardado automático de asociaciones referenciadas
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.
Autobuild
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
Toque
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.
Caché de contador
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 llamaBand, el nombre del campo de contador por defecto esbands_count.Un
stringosymbol: 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::Dynamicen 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.