Página inicial do Docs → Mongoid
Definição de campo
Nesta página
- Tipos de campo
- Campos não digitados
- Tipo de campo: StringifiedSymbol
- Tipo de campo: símbolo
- Tipo de campo: Hash
- Tipo de campo: Hora
- Tipo de campo: Data
- Tipo de campo: DateTime
- Tipo de campo: Regexp
- Campos decimais grandes
- Como usar símbolos ou strings em vez de classes
- Especificando valores padrão do campo
- Especificando nomes de campo de armazenamento
- Aliases de campo
- Nomes reservados
- Redefinição de campo
- IDs personalizados
- Valores não convertíveis
- Personalização do comportamento do campo
- Getters e setters personalizados
- Tipos de campo personalizados
- Opções de campo personalizado
- Campos dinâmicos
- Caracteres especiais em nomes de campo
- Campos localizados
- Localizar opção de campo
:present
- Recuos
- Consultando
- Indexação
- Atributos somente leitura
- Campos de carimbo de hora
- Nomes de campo com pontos/períodos (
.
) e cifrões ($
)
Tipos de campo
O MongoDB armazena dados de documentos subjacentes usandotipos de BSON , e o Mongoid converte tipos BSON em tipos Ruby em tempo de execução em seu aplicativo. Por exemplo, um campo definido com type: :float
utilizará a classe Ruby Float
na memória e persistirá no banco de dados como o tipo BSON double
.
As definições do tipo de campo determinam como o Mongoid se comporta ao construir queries e recuperar/gravar campos de/para o banco de dados. Especificamente:
Ao atribuir valores aos campos no tempo de execução, os valores são convertidos para o tipo especificado.
Ao persistir dados no MongoDB, os dados são enviados em um tipo apropriado, permitindo uma manipulação de dados mais rica dentro do MongoDB ou por outras ferramentas.
Ao consultar documentos, os parâmetros de consulta são convertidos no tipo especificado antes de serem enviados ao MongoDB.
Ao recuperar documentos do banco de dados, os valores de campo são convertidos no tipo especificado.
Alterar as definições de campo em uma classe de modelo não altera os dados já armazenados no MongoDB. Para atualizar o tipo ou conteúdo de campos de documentos existentes, o campo deve ser salvo novamente no banco de dados. Observe que, devido ao fato de o Mongoid rastrear quais atributos em um modelo são alterados e salvar apenas os alterados, pode ser necessário escrever explicitamente um valor de campo ao alterar o tipo de um campo existente sem alterar os valores armazenados.
Considere uma classe simples para modelar uma pessoa em um aplicativo. Uma pessoa pode ter um nome, data de nascimento e peso. Podemos definir esses atributos em uma pessoa usando a macro field
.
class Person include Mongoid::Document field :name, type: String field :date_of_birth, type: Date field :weight, type: Float end
Os tipos válidos para campos são os seguintes:
Array
BSON::Binary
Mongoid::Boolean
, que pode ser especificado simplesmente comoBoolean
no escopo de uma classe que incluiuMongoid::Document
.Float
Integer
BSON::ObjectId
Range
Set
String
Mongoid::StringifiedSymbol, que pode ser especificado simplesmente como
StringifiedSymbol
no escopo de uma classe que incluiuMongoid::Document
.ActiveSupport::TimeWithZone
Mongoid também reconhece a string "Boolean"
como um alias para a classe Mongoid::Boolean
.
Para definir tipos de campos personalizados, consulte Tipos de campos personalizados abaixo.
Observação
A utilização dos tipos BSON::Int64
e BSON::Int32
como tipos de campo não é suportada. Salvar esses tipos no banco de dados funcionará conforme o esperado, no entanto, consultá-los retornará o tipo nativo Ruby Integer
. A consulta de campos do tipo BSON::Decimal128
retornará valores do tipo BSON::Decimal128
em BSON <=4 e valores do tipo BigDecimal
em BSON 5+.
Campos não digitados
Não especificar um tipo para um campo é o mesmo que especificar o tipo Object
. Esses campos não são digitados:
class Product include Mongoid::Document field :properties # Equivalent to: field :properties, type: Object end
Um campo não especificado pode armazenar valores de qualquer tipo que seja diretamente serializável para BSON. Isso é útil quando um campo pode conter valores de tipos diferentes (ou seja, é um campo do tipo variante) ou quando o tipo de valores não é conhecido antecipadamente:
product = Product.new(properties: "color=white,size=large") product.properties # => "color=white,size=large" product = Product.new(properties: {color: "white", size: "large"}) product.properties # => {:color=>"white", :size=>"large"}
Quando os valores são atribuídos ao campo, o Mongoid ainda executa a mongoização, mas usa a classe do valor em vez do tipo de campo para a lógica de mongoização.
product = Product.new(properties: 0..10) product.properties # The range 0..10, mongoized: # => {"min"=>0, "max"=>10}
Ao ler dados do banco de dados, o Mongoid não realiza nenhuma conversão de tipo em campos não digitados. Por esse motivo, embora seja possível escrever qualquer valor serializável BSON em um campo não digitado, os valores que exigem tratamento especial no lado da leitura do banco de dados geralmente não funcionarão corretamente em um campo não digitado. Entre os tipos de campo suportados pelo Mongoid, os valores dos seguintes tipos não devem ser armazenados em campos não digitados:
Date
(os valores serão retornados comoTime
)DateTime
(os valores serão retornados comoTime
)Range
(os valores serão retornados comoHash
)
Tipo de campo: StringifiedSymbol
O tipo de campo StringifiedSymbol
é o tipo de campo recomendado para armazenar valores que devem ser expostos como símbolos para aplicativos Ruby. Ao utilizar o tipo de campo Symbol
, o Mongoid padroniza para armazenar valores como símbolos BSON. Para mais informações sobre o tipo de símbolo BSON, consulte aqui. No entanto, o tipo de símbolo BSON é preterido e é difícil de trabalhar em linguagens de programação sem tipos de símbolos nativos, então o tipo StringifiedSymbol
permite o uso de símbolos enquanto garante interoperabilidade com outros drivers. O tipo StringifiedSymbol
armazena todos os dados no banco de dados como strings, enquanto expõe valores para o aplicativo como símbolos.
Um exemplo de uso é mostrado abaixo:
class Post include Mongoid::Document field :status, type: StringifiedSymbol end post = Post.new(status: :hello) # status is stored as "hello" on the database, but returned as a Symbol post.status # => :hello # String values can be assigned also: post = Post.new(status: "hello") # status is stored as "hello" on the database, but returned as a Symbol post.status # => :hello
Todos os valores que não de string se tornarão de strings após serem enviados para o banco de dados (via to_s
), e todos os valores serão convertidos em símbolos quando retornados para o aplicativo. Os valores que não podem ser convertidos diretamente em símbolos, como números inteiros e arrays, serão primeiro convertidos em strings e, em seguida, em símbolos antes de serem retornados ao aplicativo.
Por exemplo, definir um número inteiro como status
:
post = Post.new(status: 42) post.status # => :"42"
Se o tipo StringifiedSymbol
for aplicado a um campo que contém símbolos BSON, os valores serão armazenados como strings em vez de símbolos BSON na próxima gravação. Isso permite uma migração lenta e transparente de campos que atualmente armazenam strings ou símbolos BSON no banco de dados para o tipo de campo StringifiedSymbol
.
Tipo de campo: símbolo
Novos aplicativos devem usar o tipo de campo StringifiedSymbol para armazenar símbolos Ruby no banco de dados. O tipo de campo StringifiedSymbol
fornece compatibilidade máxima com outros aplicativos e linguagens de programação e tem o mesmo comportamento em todas as circunstâncias.
O Mongoid também fornece o tipo de campo Symbol
obsoleto para serializar símbolos Ruby para símbolos BSON. Como a especificação BSON preteriu o tipo de símbolo BSON, a joia bson
serializará os símbolos Ruby em cadeias de caracteres BSON quando usada sozinha. No entanto, para manter a compatibilidade com o conjuntos de dados mais antigos, a joia mongo
substitui esse comportamento para serializar símbolos Ruby como símbolos BSON. Isso é necessário para que seja possível especificar consultas para documentos que contenham símbolos BSON como campos.
Para substituir o comportamento padrão e configurar a joia mongo
(e, portanto, Mongoide também) para codificar valores de símbolo como strings, inclua o seguinte trecho de código em seu projeto:
class Symbol def bson_type BSON::String::BSON_TYPE end end
Tipo de campo: Hash
Ao usar um campo do tipo Hash, tenha cuidado ao aderir aos nomes de chave legais do MongoDB, caso contrário os valores não serão armazenados corretamente.
class Person include Mongoid::Document field :first_name field :url, type: Hash # will update the fields properly and save the values def set_vals self.first_name = 'Daniel' self.url = {'home_page' => 'http://www.homepage.com'} save end # all data will fail to save due to the illegal hash key def set_vals_fail self.first_name = 'Daniel' self.url = {'home.page' => 'http://www.homepage.com'} save end end
Tipo de campo: Hora
Time
os campos armazenam valores como instâncias do Time
no fuso horário configurado.
Date
e instâncias DateTime
são convertidas em instâncias Time
após a atribuição a um campo de Time
:
class Voter include Mongoid::Document field :registered_at, type: Time end Voter.new(registered_at: Date.today) # => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>
No exemplo acima, o valor foi interpretado como o início de hoje no horário local, pois o aplicativo não foi configurado para utilizar os horários UTC.
Observação
Quando o banco de dados contém um valor de string para um campo de Time
, o Mongoid analisa o valor de string usando Time.parse
que considera valores sem fusos horários como estando na hora local.
Tipo de campo: Data
O Mongoid permite a atribuição de valores de vários tipos para campos de Date
:
Date
- a data fornecida é armazenada como está.Time
,DateTime
,ActiveSupport::TimeWithZone
- o componente de data do valor é tomado no fuso horário do valor.String
- a data especificada na string é usada.Integer
,Float
- o valor é considerado um carimbo de data/hora UTC que é convertido para o fuso horário configurado (observe queMongoid.use_utc
não tem efeito sobre esta conversão), então a data é retirada do tempo resultante.
Em outras palavras, se uma data for especificada no valor, essa data será usada sem primeiro converter o valor para o fuso horário configurado.
Como a conversão de data e hora para data tem perdas (ela descarta o componente de hora), especialmente se um aplicativo opera com horas em diferentes fusos horários, recomenda-se converter explicitamente os objetos String
, Time
e DateTime
em objetos Date
antes de atribuir os valores aos campos do tipo Date
.
Observação
Quando o banco de dados contém um valor de string para um campo Date
, o Mongoid analisa o valor da string usando Time.parse
, descarta a parte de tempo do objeto Time
resultante e usa a parte de data. Time.parse
considera que os valores sem fuso horário estão no horário local.
Tipo de campo: DateTime
O MongoDB armazena todos os horários como carimbos de data/hora UTC. Ao atribuir um valor a um campo DateTime
ou ao consultar um campo DateTime
, o Mongoid converte o valor passado em um Time
UTC antes de enviá-lo ao servidor MongoDB.
Time
, objetos ActiveSupport::TimeWithZone
e DateTime
incorporam informações de fuso horário, e o valor persistente é o momento especificado no tempo, em UTC. Quando o valor é recuperado, o fuso horário no qual ele é retornado é definido pelas definições de fuso horário configuradas.
class Ticket include Mongoid::Document field :opened_at, type: DateTime end Time.zone = 'Berlin' ticket = Ticket.create!(opened_at: '2018-02-18 07:00:08 -0500') ticket.opened_at # => Sun, 18 Feb 2018 13:00:08 +0100 ticket # => #<Ticket _id: 5c13d4b9026d7c4e7870bb2f, opened_at: 2018-02-18 12:00:08 UTC> Time.zone = 'America/New_York' ticket.opened_at # => Sun, 18 Feb 2018 07:00:08 -0500 Mongoid.use_utc = true ticket.opened_at # => Sun, 18 Feb 2018 12:00:08 +0000
O Mongoid também suporta a conversão de números inteiros e flutuantes para DateTime
. Ao fazer isso, os inteiros/flutuantes são assumidos como carimbos de data/hora Unix (em UTC):
ticket.opened_at = 1544803974 ticket.opened_at # => Fri, 14 Dec 2018 16:12:54 +0000
Se uma string for utilizada como um valor de campo do DateTime
, o comportamento dependerá se a string inclui um fuso horário. Se nenhum fuso horário for especificado, o fuso horário Mongoide padrão será usado:
Time.zone = 'America/New_York' ticket.opened_at = 'Mar 4, 2018 10:00:00' ticket.opened_at # => Sun, 04 Mar 2018 15:00:00 +0000
Se um fuso horário for especificado, ele será respeitado:
ticket.opened_at = 'Mar 4, 2018 10:00:00 +01:00' ticket.opened_at # => Sun, 04 Mar 2018 09:00:00 +0000
Observação
Quando o banco de dados contém um valor de string para um campo de DateTime
, o Mongoid analisa o valor de string usando Time.parse
que considera valores sem fusos horários como estando na hora local.
Tipo de campo: Regexp
O MongoDB oferece suporte ao armazenamento de expressões regulares em documentos e consulta usando expressões regulares. Observe que o MongoDB usa expressões regulares compatíveis com Perl (PCRE) e o Ruby usa Onigmo, que é uma bifurcação do mecanismo de expressão regular Oniguruma. As duas implementações de expressão regular geralmente fornecem funcionalidade equivalente, mas têm várias diferenças de sintaxe importantes.
Quando um campo é declarado como do tipo Regexp, o Mongoid converte expressões regulares Ruby em expressões regulares BSON e armazena o resultado no MongoDB. A recuperação do campo do banco de dados produz uma instância BSON::Regexp::Raw
:
class Token include Mongoid::Document field :pattern, type: Regexp end token = Token.create!(pattern: /hello.world/m) token.pattern # => /hello.world/m token.reload token.pattern # => #<BSON::Regexp::Raw:0x0000555f505e4a20 @pattern="hello.world", @options="ms">
Use o método #compile
em BSON::Regexp::Raw
para retornar a expressão regular Ruby:
token.pattern.compile # => /hello.world/m
Observe que, se a expressão regular não era originalmente Ruby, chamar #compile
nela pode produzir uma expressão regular diferente. Por exemplo, o seguinte é um PCRE que corresponde a uma string que termina em "olá":
BSON::Regexp::Raw.new('hello$', 's') # => #<BSON::Regexp::Raw:0x0000555f51441640 @pattern="hello$", @options="s">
A compilação dessa expressão regular produz uma expressão regular Ruby que corresponde a cadeias de caracteres contendo "hello" antes de uma nova linha, além de strings que terminam em "hello":
BSON::Regexp::Raw.new('hello$', 's').compile =~ "hello\nworld" # => 0
Isso ocorre porque o significado de $
é diferente entre expressões regulares PCRE e Ruby.
Campos decimais grandes
O tipo de campo BigDecimal
é usado para armazenar números com maior precisão.
O tipo de campo do BigDecimal
armazena seus valores de duas maneiras diferentes no banco de dados, dependendo do valor da opção de configuração global do Mongoid.map_big_decimal_to_decimal128
. Se esse sinalizador for definido como falso (que é o padrão), o campo BigDecimal
será armazenado como uma string, caso contrário, será armazenado como um BSON::Decimal128
.
O tipo de campo BigDecimal
tem algumas limitações ao converter para e de um BSON::Decimal128
:
BSON::Decimal128
tem alcance e precisão limitados, enquantoBigDecimal
não tem restrições em termos de alcance e precisão.BSON::Decimal128
tem um valor máximo de aproximadamente10^6145
e um valor mínimo de aproximadamente-10^6145
, e tem um máximo de 34 bits de precisão. Ao tentar armazenar valores que não se encaixam em umBSON::Decimal128
, é recomendável armazená-los como uma cadeia de caracteres em vez de umaBSON::Decimal128
. Você pode fazer isso definindoMongoid.map_big_decimal_to_decimal128
comofalse
. Se um valor que não cabe emBSON::Decimal128
for tentado ser armazenado como um, será gerado um erro.BSON::Decimal128
é capaz de aceitar valores deNaN
assinados, enquantoBigDecimal
não é. Ao recuperar valores deNaN
assinados a partir do banco de dados utilizando o tipo de campoBigDecimal
, oNaN
não será assinado.BSON::Decimal128
preserva os zeros finais ao serem armazenados no banco de dados.BigDecimal
, no entanto, não mantém zeros à direita e, portanto, recuperar valores deBSON::Decimal128
usando o tipo de campoBigDecimal
pode resultar em uma perda de precisão.
Há uma advertência adicional ao armazenar um BigDecimal
em um campo sem tipo (p. ex., um campo digitado dinamicamente) e Mongoid.map_big_decimal_to_decimal128
é false
. Neste caso, o BigDecimal
é armazenado como uma string e, como um campo dinâmico está sendo usado, a query deste campo com um BigDecimal
não encontrará a string para esse BigDecimal
, pois a query está procurando por um BigDecimal
. Para fazer query para essa string, o BigDecimal
deve primeiro ser convertido em uma string com to_s
. Observe que isto não é um problema se o campo tem o tipo BigDecimal
.
Se você deseja evitar utilizar BigDecimal
completamente, pode configurar o tipo de campo como BSON::Decimal128
. Isso permitirá que você acompanhe os zeros à direita e os valores NaN
assinados.
Migração para o campo BigDecimal
apoiado por decimal128
Em uma futura versão principal do Mongoid, a opção de configuração global do Mongoid.map_big_decimal_to_decimal128
será padronizada para true
. Quando este sinalizador estiver ativado, os valores do BigDecimal
em queries não corresponderão às strings que já estão armazenadas no banco de dados; eles somente corresponderão aos valores do decimal128
que estão no banco de dados. Se você tiver um campo BigDecimal
com strings, você terá três opções:
A opção de configuração global do
Mongoid.map_big_decimal_to_decimal128
pode ser configurada parafalse
, e você pode continuar armazenando seus valores doBigDecimal
como strings. Observe que você está abrindo mão das vantagens de armazenar valoresBigDecimal
comodecimal128
, como a possibilidade de fazer consulta e agregações com base no valor numérico do campo.A opção de configuração global
Mongoid.map_big_decimal_to_decimal128
pode ser definida comotrue
, e você pode converter todos os valores desse campo de strings para valoresdecimal128
no banco de dados. Você deve fazer essa conversão antes de definir a opção de configuração global como verdadeira. Um exemplo de consulta para fazer isso é o seguinte:db.bands.updateMany({ "field": { "$exists": true } }, [ { "$set": { "field": { "$toDecimal": "$field" } } } ]) Esta consulta atualiza todos os documentos que têm o campo fornecido, definindo este campo para seu valor de
decimal128
correspondente. Observe que esta consulta funciona apenas no MongoDB 4.2+.A opção de configuração global do
Mongoid.map_big_decimal_to_decimal128
pode ser configurada paratrue
, e você pode ter ambas as strings e valores dodecimal128
para este campo. Desta forma, somente os valores dedecimal128
serão inseridos e atualizados no banco de dados no futuro. Observe que você ainda não poderá aproveitar todos os benefícios de usar apenas valores dedecimal128
, entretanto, seu conjunto de dados migrará lentamente para todos os valores dedecimal128
, à medida que os valores de strings antigas são atualizados paradecimal128
e novos valores dedecimal128
são adicionados. Com esta configuração, você poderá continuar fazendo query de valores deBigDecimal
da seguinte forma:Mongoid.map_big_decimal_to_decimal128 = true big_decimal = BigDecimal('2E9') Band.in(sales: [big_decimal, big_decimal.to_s]).to_a Esta consulta encontrará todos os valores que são um valor
decimal128
ou uma string que corresponde a esse valor.
Como usar símbolos ou strings em vez de classes
O Mongoid permite usar símbolos ou strings em vez de classes para especificar o tipo de campos, por exemplo:
class Order include Mongoid::Document field :state, type: :integer # Equivalent to: field :state, type: "integer" # Equivalent to: field :state, type: Integer end
Somente os tipos de campo padrão, conforme listados abaixo, podem ser especificados usando símbolos ou strings dessa maneira. O Mongoide reconhece as seguintes expansões:
:array
=>Array
:big_decimal
=>BigDecimal
:binary
=>BSON::Binary
:boolean
=>Mongoid::Boolean
:date
=>Date
:date_time
=>DateTime
:float
=>Float
:hash
=>Hash
:integer
=>Integer
:object_id
=>BSON::ObjectId
:range
=>Range
:regexp
=>Regexp
:set
=>Set
:string
=>String
:stringified_symbol
=>StringifiedSymbol
:symbol
=>Symbol
:time
=>Time
Especificando valores padrão do campo
Um campo pode ser configurado para ter um valor padrão. O valor padrão pode ser corrigido, como no exemplo a seguir:
class Order include Mongoid::Document field :state, type: String, default: 'created' end
O valor padrão também pode ser especificado como Proc
:
class Order include Mongoid::Document field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } end
Observação
Os valores padrão que não são instâncias Proc
são avaliados no tempo de carregamento da classe, o que significa que as duas definições a seguir não são equivalentes:
field :submitted_at, type: Time, default: Time.now field :submitted_at, type: Time, default: ->{ Time.now }
A segunda definição é provavelmente a desejada, o que faz com que a hora de envio seja definida como a hora atual no momento da instanciação do documento.
Para definir um padrão que depende do estado do documento, use self
dentro da instância Proc
que avaliaria para a instância do documento sendo operada em:
field :fulfill_by, type: Time, default: ->{ # Order should be fulfilled in 2 business hours. if (7..8).include?(self.submitted_at.hour) self.submitted_at + 4.hours elsif (9..3).include?(self.submitted_at.hour) self.submitted_at + 2.hours else (self.submitted_at + 1.day).change(hour: 11) end }
Ao definir um valor padrão como Proc
, o Mongoid aplicará o padrão depois que todos os outros atributos forem definidos e as associações forem inicializadas. Para que o padrão seja aplicado antes que os outros atributos sejam definidos, use a opção de campo pre_processed: true
:
field :fulfill_by, type: Time, default: ->{ Time.now + 3.days }, pre_processed: true
A opção pre_processed: true
também é necessária ao especificar um valor padrão personalizado por meio de um Proc
para o campo _id
, para garantir que o _id
esteja definido corretamente por meio de associações:
field :_id, type: String, default: -> { 'hello' }, pre_processed: true
Especificando nomes de campo de armazenamento
Uma das desvantagens de ter um banco de dados esquemático é que o MongoDB deve armazenar todas as informações de campo junto com cada documento, o que significa que ocupa muito espaço de armazenamento na RAM e no disco. Um padrão comum para limitar isso é para campos de nomes alternativos a um pequeno número de caracteres, mantendo o domínio no aplicativo expressivo. O Mongoid permite que você faça isso e faça referência aos campos no domínio através de seus nomes longos em getters, setters e critérios enquanto realiza a conversão para você.
class Band include Mongoid::Document field :n, as: :name, type: String end band = Band.new(name: "Placebo") band.attributes # { "n" => "Placebo" } criteria = Band.where(name: "Placebo") criteria.selector # { "n" => "Placebo" }
Aliases de campo
É possível definir nomes alternativos de campo. O valor será armazenado no campo de destino, mas pode ser acessado a partir do campo de destino ou do campo com nome alternativo:
class Band include Mongoid::Document field :name, type: String alias_attribute :n, :name end band = Band.new(n: 'Astral Projection') # => #<Band _id: 5fc1c1ee2c97a64accbeb5e1, name: "Astral Projection"> band.attributes # => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"} band.n # => "Astral Projection"
Os nomes alternativos podem ser removidos das classes de modelo utilizando o método unalias_attribute
.
class Band unalias_attribute :n end
Unaliasing id
unalias_attribute
pode ser usado para remover o nome alternativo id
predefinido. Isso é útil para armazenar valores diferentes nos campos id
e _id
:
class Band include Mongoid::Document unalias_attribute :id field :id, type: String end Band.new(id: '42') # => #<Band _id: 5fc1c3f42c97a6590684046c, id: "42">
Nomes reservados
A tentativa de definir um campo em um documento que entra em conflito com um nome de método reservado no Mongoid criará um erro. A lista de nomes reservados pode ser obtida invocando o método Mongoid.destructive_fields
.
Redefinição de campo
Por padrão, Mongoide permite redefinir campos em um modelo. Para gerar um erro quando um campo é redefinido, defina a opção de configuração duplicate_fields_exception
como true
.
Com a opção definida como verdadeiro, o exemplo a seguir gerará um erro:
class Person include Mongoid::Document field :name field :name, type: String end
Para definir o campo de qualquer forma, utilize a opção overwrite: true
:
class Person include Mongoid::Document field :name field :name, type: String, overwrite: true end
IDs personalizados
Por padrão, o Mongoid define o campo _id
em documentos para conter um valor BSON::ObjectId
que é gerado automaticamente pelo Mongoid.
É possível substituir a definição de campo _id
para alterar o tipo de valores _id
ou ter valores padrão diferentes:
class Band include Mongoid::Document field :name, type: String field :_id, type: String, default: ->{ name } end
É possível omitir totalmente o padrão:
class Band include Mongoid::Document field :_id, type: String end
Se o padrão _id
for omitido e nenhum valor _id
for fornecido pelo seu aplicativo, o Mongoid persistirá o documento sem o valor _id
. Nesse caso, se o documento for um documento de nível superior, um valor _id
será atribuído pelo servidor; se o documento for um documento incorporado, nenhum valor _id
será atribuído. O Mongoid não recuperará automaticamente esse valor, se atribuído, quando o documento for persistido - você deve obter o valor persistido (e o documento persistido completo) usando outros meios:
band = Band.create! => #<Band _id: , > band.id => nil band.reload # raises Mongoid::Errors::DocumentNotFound Band.last => #<Band _id: 5fc681c22c97a6791f324b99, >
A omissão de campos _id
é mais comum em documentos incorporados.
Mongoid também define o campo id
com nome alternativo de _id
. O nome alternativo id
pode ser removido se desejado (como para se integrar com sistemas que usam o campo id
para armazenar valor diferente de _id
.
Valores não convertíveis
No Mongoid 8, o Mongoid padronizou o tratamento de atribuições e leituras de valores "uncastable". Um valor é considerado "não atribuível" quando não pode ser coagido ao tipo de seu campo. Por exemplo, uma array seria um valor "não atribuível" para um campo de número inteiro.
Atribuição de valores não atribuíveis
A atribuição de valores não atribuíveis foi padronizada para atribuir nil
por padrão. Considere o seguinte exemplo:
class User include Mongoid::Document field :name, type: Integer end User.new(name: [ "hello" ])
Atribuir uma matriz a um campo do tipo Inteiro não funciona, pois uma matriz não pode ser forçada para um Inteiro. A atribuição de valores não atribuíveis para um campo fará com que um nil
seja escrito:
user = User.new(name: [ "Mike", "Trout" ]) # => #<User _id: 62b222d43282a47bf73e3264, name: nil>
Observe que os valores originais não atribuíveis serão armazenados no hash de attributes_before_type_cast
com seus nomes de campo:
user.attributes_before_type_cast["name"] # => ["Mike", "Trout"]
Observação
Observe que, para campos numéricos, qualquer classe que defina to_i
para campos inteiros, to_f
para flutuantes e to_d
para BigDecimals é convertível. Strings são a exceção e só chamarão o método to_*
correspondente se a string for numérica. Se uma classe definir apenas to_i
e não to_f
e estiver sendo atribuída a um campo Flutuante, isso não poderá ser convertido e o Mongoid não executará uma conversão em duas etapas (ou seja, to_i
e depois to_f
).
Lendo valores não atribuíveis
Quando os documentos no banco de dados contiverem valores de tipos diferentes de suas representações no Mongoid, se o Mongoid não puder forçá-los para o tipo correto, substituirá o valor por nil
. Considere o seguinte modelo e documento no banco de dados:
class User include Mongoid::Document field :name, type: Integer end
{ _id: ..., name: [ "Mike", "Trout" ] }
Ler este documento a partir do banco de dados resultará no campo de nome do modelo contendo nil
:
User.first.name # => nil
O valor do banco de dados da matriz de tipo não pode ser armazenado no atributo, pois a matriz não pode ser forçada para um Inteiro. Observe que os valores originais não atribuíveis serão armazenados no hash attributes_before_type_cast
com seus nomes de campo:
user.attributes_before_type_cast["name"] # => ["Mike", "Trout"]
Observação
Os métodos demongoize
em objetos contêineres (ou seja, Hash, Matriz) não foram alterados para permitir a persistência automática de atributos de contêiner mutados. Consulte MONGOID-2951 para uma discussão mais longa sobre este tópico.
Personalização do comportamento do campo
O Mongoid oferece várias maneiras de personalizar o comportamento dos campos.
Getters e setters personalizados
Você pode substituir getters e setters de campos para modificar os valores quando eles estão sendo acessados ou gravados. Os getters e setters usam o mesmo nome do campo. Use os métodos read_attribute
e write_attribute
dentro dos getters e setters para operar nos valores brutos dos atributos.
Por exemplo, o Mongoid fornece a opção de campo :default
para escrever um valor padrão no campo. Se você deseja ter um valor padrão de campo em seu aplicativo, mas não deseja persisti-lo, você pode substituir o getter da seguinte maneira:
class DistanceMeasurement include Mongoid::Document field :value, type: Float field :unit, type: String def unit read_attribute(:unit) || "m" end def to_s "#{value} #{unit}" end end measurement = DistanceMeasurement.new(value: 2) measurement.to_s # => "2.0 m" measurement.attributes # => {"_id"=>BSON::ObjectId('613fa0b0a15d5d61502f3447'), "value"=>2.0}
Para dar outro exemplo, um campo que converte strings vazias em valores ímpares pode ser implementado da seguinte forma:
class DistanceMeasurement include Mongoid::Document field :value, type: Float field :unit, type: String def unit=(value) if value.blank? value = nil end write_attribute(:unit, value) end end measurement = DistanceMeasurement.new(value: 2, unit: "") measurement.attributes # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil}
Tipos de campo personalizados
Você pode definir tipos personalizados no Mongoid e determinar como são serializados e desserializados. Neste exemplo, definimos um novo tipo de campo Point
, que podemos usar em nossa classe de modelo da seguinte maneira:
class Profile include Mongoid::Document field :location, type: Point end
Em seguida, crie uma classe Ruby para representar o tipo. Essa classe deve definir os métodos usados para serialização e desserialização do MongoDB da seguinte forma:
class Point attr_reader :x, :y def initialize(x, y) @x, @y = x, y end # Converts an object of this instance into a database friendly value. # In this example, we store the values in the database as array. def mongoize [ x, y ] end class << self # Takes any possible object and converts it to how it would be # stored in the database. def mongoize(object) case object when Point then object.mongoize when Hash then Point.new(object[:x], object[:y]).mongoize else object end end # Get the object as it was stored in the database, and instantiate # this custom class from it. def demongoize(object) Point.new(object[0], object[1]) end # Converts the object that was supplied to a criteria and converts it # into a query-friendly form. def evolve(object) case object when Point then object.mongoize else object end end end end
O método de instância mongoize
pega uma instância do seu objeto de tipo personalizado e o converte em uma representação de como ele será armazenado no banco de dados, ou seja, para passar para o driver Ruby do MongoDB. Em nosso exemplo acima, queremos armazenar nosso objeto Point
como Array
no formulário [ x, y ]
.
O método de classe mongoize
é semelhante ao método de instância, no entanto, ele deve manipular objetos de todos os tipos possíveis como entradas. O método mongoize
é utilizado ao chamar os métodos de configuração para campos do seu tipo personalizado.
point = Point.new(12, 24) venue = Venue.new(location: point) # This uses the Point#mongoize instance method. venue = Venue.new(location: [ 12, 24 ]) # This uses the Point.mongoize class method.
O método de classe demongoize
faz o inverso de mongoize
. Ele pega o objeto bruto do driver Rubi MongoDB e o converte em uma instância do seu tipo personalizado. Nesse caso, o driver do banco de dados retorna um Array
e instanciamos um Point
a partir dele. O método demongoize
é usado ao chamar os getters de campos para seu tipo personalizado. Observe que, no exemplo acima, desde demongoize
chamadas Point.new
, uma nova instância de Point
será gerada em cada chamada para o getter.
O Mongoid sempre chamará o método demongoize
em valores que foram recuperados do banco de dados, mas os aplicativos podem, em teoria, chamar demongoize
com entrada arbitrária. É recomendado que os aplicativos adicionem manipulação para entrada arbitrária em seus métodos do demongoize
. Podemos reescrever o método de desmongoize de Point
da seguinte forma:
def demongoize(object) if object.is_a?(Array) && object.length == 2 Point.new(object[0], object[1]) end end
Observe que demongoize
só criará uma nova Point
se receber uma matriz de comprimento 2 e retornará nil
caso contrário. Ambos os métodos mongoize
e demongoize
devem estar preparados para receber entrada arbitrária e devem retornar nil
em valores que são não atribuíveis para seu tipo personalizado. Consulte a seção sobre Valores não atribuíveis para obter mais detalhes.
Por fim, o método de classe evolve
é semelhante a mongoize
, mas é usado ao transformar objetos para uso nos critérios de consulta do Mongoid.
point = Point.new(12, 24) Venue.where(location: point) # This uses Point.evolve
O método evolve
também deve estar preparado para receber entradas arbitrárias; no entanto, diferentemente dos métodos mongoize
e demongoize
, ele deve retornar o valor inserido em valores que não podem ser convertidos para seu tipo personalizado. Consulte a seção sobre Valores não atribuíveis para obter mais detalhes.
Tipos de campo personalizado fantasma
O tipo de campo personalizado pode realizar conversões de valores de atributos visíveis ao usuário para os valores armazenados no banco de dados quando o tipo de valor de atributo visível ao usuário for diferente do tipo de campo declarado. Por exemplo, isso pode ser utilizado para implementar um mapeamento de uma enumeração para outra, para ter valores mais descritivos no aplicativo e valores mais compactos armazenados no banco de dados:
class ColorMapping MAPPING = { 'black' => 0, 'white' => 1, }.freeze INVERSE_MAPPING = MAPPING.invert.freeze class << self # Takes application-scope value and converts it to how it would be # stored in the database. Converts invalid values to nil. def mongoize(object) MAPPING[object] end # Get the value as it was stored in the database, and convert to # application-scope value. Converts invalid values to nil. def demongoize(object) INVERSE_MAPPING[object] end # Converts the object that was supplied to a criteria and converts it # into a query-friendly form. Returns invalid values as is. def evolve(object) MAPPING.fetch(object, object) end end end class Profile include Mongoid::Document field :color, type: ColorMapping end profile = Profile.new(color: 'white') profile.color # => "white" # Writes 0 to color field profile.save!
Opções de campo personalizado
Você pode definir opções personalizadas para a função de macro field
que estendem seu comportamento no momento em que as classes de modelo são carregadas.
Como exemplo, definiremos uma opção :max_length
que adicionará um validador de comprimento para o campo. Primeiro, declare a nova opção de campo em um inicializador, especificando sua função de manipulador como um bloco:
# in /config/initializers/mongoid_custom_fields.rb Mongoid::Fields.option :max_length do |model, field, value| model.validates_length_of field.name, maximum: value end
Em seguida, use-a como sua classe de modelo:
class Person include Mongoid::Document field :name, type: String, max_length: 10 end
Observe que a função do manipulador será invocada sempre que a opção for usada na definição do campo, mesmo que o valor da opção seja falso ou nulo.
Campos dinâmicos
Por padrão, o Mongoide exige que todos os campos que podem ser definidos em um documento sejam explicitamente definidos utilizando declarações do field
. O Mongoid também suporta a criação de campos em tempo real a partir de um hash arbitrário ou documentos armazenados no banco de dados. Quando um modelo utiliza campos não explicitamente definidos, esses campos são chamados de campos dinâmicos.
Para ativar campos dinâmicos, inclua um módulo Mongoid::Attributes::Dynamic
no modelo:
class Person include Mongoid::Document include Mongoid::Attributes::Dynamic end bob = Person.new(name: 'Bob', age: 42) bob.name # => "Bob"
É possível utilizar declarações do field
e campos dinâmicos na mesma classe de modelo. Os atributos para os quais há uma declaração field
serão tratados de acordo com a declaração field
, e os atributos restantes serão tratados como campos dinâmicos.
Os valores dos atributos nos campos dinâmicos devem ser definidos inicialmente passando o hash do atributo para o construtor, atribuição em massa via attributes=
, atribuição em massa via []=
, usando write_attribute
, ou já devem estar presentes no banco de dados.
# OK bob = Person.new(name: 'Bob') # OK bob = Person.new bob.attributes = {age: 42} # OK bob = Person.new bob['age'] = 42 # Raises NoMethodError: undefined method age= bob = Person.new bob.age = 42 # OK bob = Person.new # OK - string access bob.write_attribute('age', 42) # OK - symbol access bob.write_attribute(:name, 'Bob') # OK, initializes attributes from whatever is in the database bob = Person.find('123')
Se um atributo não estiver presente em um hash de atributos de uma instância de modelo específica, tanto o leitor quanto o gravador do campo correspondente não estarão definidos e, ao invocá-los, resultará em NoMethodError
:
bob = Person.new bob.attributes = {age: 42} bob.age # => 42 # raises NoMethodError bob.name # raises NoMethodError bob.name = 'Bob' # OK bob['name'] = 'Bob' bob.name # => "Bob"
Os atributos podem sempre ser lidos utilizando acesso de atributo em massa ou read_attribute
(isso se aplica a modelos que não usam campos dinâmicos também):
bob = Person.new(age: 42) # OK - string access bob['name'] # => nil # OK - symbol access bob[:name] # => nil # OK - string access bob['age'] # => 42 # OK - symbol access bob[:age] # => 42 # OK bob.attributes['name'] # => nil # OK bob.attributes['age'] # => 42 # Returns nil - keys are always strings bob.attributes[:age] # => nil # OK bob.read_attribute('name') # => nil # OK bob.read_attribute(:name) # => nil # OK - string access bob.read_attribute('age') # => 42 # OK - symbol access bob.read_attribute(:age) # => 42
Observação
Os valores retornados do método read_attribute
e aqueles armazenados no hash attributes
são os valores mongoized
.
Caracteres especiais em nomes de campo
O Mongoid permite que os nomes de campos dinâmicos incluam espaços e pontuação:
bob = Person.new('hello world' => 'MDB') bob.send('hello world') # => "MDB" bob.write_attribute("hello%world", 'MDB') bob[:"hello%world"] # => "MDB"
Campos localizados
O Mongoid suporta campos localizados através da joia I18n.
class Product include Mongoid::Document field :description, type: String, localize: true end
Ao informar o campo como localize
, o Mongoid armazenará o campo como um hash de pares local/valor, mas o acesso normal a ele se comportará como uma string.
I18n.default_locale = :en product = Product.new product.description = "Marvelous!" I18n.locale = :de product.description = "Fantastisch!" product.attributes # { "description" => { "en" => "Marvelous!", "de" => "Fantastisch!" }
Você pode obter e definir todas as traduções de uma só vez usando o método _translations
correspondente.
product.description_translations # { "en" => "Marvelous!", "de" => "Fantastisch!" } product.description_translations = { "en" => "Marvelous!", "de" => "Wunderbar!" }
Os campos localizados podem ser utilizados com qualquer tipo de campo. Por exemplo, eles podem ser usados com campos flutuantes para diferenças com a moeda:
class Product include Mongoid::Document field :price, type: Float, localize: true field :currency, type: String, localize: true end
Ao criar o modelo dessa forma, podemos separar o preço do tipo de moeda, o que permite que você use todas as funcionalidades relacionadas ao número no preço ao consultar ou agregar esse campo (desde que você indexe no hash de traduções armazenadas). Podemos criar uma instância deste modelo da seguinte maneira:
product = Product.new I18n.locale = :en product.price = 1.00 product.currency = "$" I18n.locale = :he product.price = 3.24 product.currency = "₪" product.attributes # => { "price" => { "en" => 1.0, "he" => 3.24 }, "currency" => { "en" => "$", "he" => "₪" } }
Localizar opção de campo :present
O Mongoid apoia a opção :present
ao criar um campo localizado:
class Product include Mongoid::Document field :description, localize: :present end
Esta opção remove valores blank
automaticamente (p. ex., aqueles verdadeiros para o método blank?
) a partir do hash de _translations
:
I18n.default_locale = :en product = Product.new product.description = "Marvelous!" I18n.locale = :de product.description = "Fantastisch!" product.description_translations # { "en" => "Marvelous!", "de" => "Fantastisch!" } product.description = "" product.description_translations # { "en" => "Marvelous!" }
Quando a string vazia é escrita para o locale :de
, a chave "de"
é removida do hash _translations
em vez de escrever a string vazia.
Recuos
A Mongoid se integra a contingências do i18n. Para usar as contingências, a respectiva funcionalidade deve ser explicitamente habilitada.
Em um aplicativo Rails, defina a configuração do config.i18n.fallbacks
para true
em seu ambiente e especifique os idiomas de contingência:
config.i18n.fallbacks = true config.after_initialize do I18n.fallbacks[:de] = [ :en, :es ] end
Em um aplicativo não Rails, inclua o módulo de contingências no backend I18n que você está usando e especifique as linguagens de contingência:
require "i18n/backend/fallbacks" I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) I18n.fallbacks[:de] = [ :en, :es ]
Quando as contingências estiverem habilitadas, se uma tradução não estiver presente no idioma ativo, as traduções serão pesquisadas nos idiomas da contingência:
product = Product.new I18n.locale = :en product.description = "Marvelous!" I18n.locale = :de product.description # "Marvelous!"
O Mongoid também define uma opção de :fallbacks
em campos, que pode ser usada para desativar a funcionalidade de contingência em um campo específico:
class Product include Mongoid::Document field :description, type: String, localize: true, fallbacks: false end product = Product.new I18n.locale = :en product.description = "Marvelous!" I18n.locale = :de product.description # nil
Observe que o padrão desta opção é true
.
Observação
No i18n 1.1, o comportamento das contingência mudou para sempre exigir uma lista explícita de localidades alternativas em vez de voltar para a localidade padrão quando nenhuma localidade alternativa tiver sido fornecida.
Consultando
Ao fazer query de campos localizados usando a API de critérios do Mongoid, o Mongoid alterará automaticamente os critérios para que correspondam à localidade atual.
# Match all products with Marvelous as the description. Locale is en. Product.where(description: "Marvelous!") # The resulting MongoDB query filter: { "description.en" : "Marvelous!" }
Indexação
Se você planeja fazer muitas consultas em campos localizados, deve indexar cada uma das localidades em que planeja fazer a pesquisa.
class Product include Mongoid::Document field :description, localize: true index "description.de" => 1 index "description.en" => 1 end
Atributos somente leitura
Você pode dizer ao Mongoid que certos atributos são somente leitura. Isso permitirá que documentos sejam criados com esses atributos, mas as alterações neles serão ignoradas ao usar métodos de atualização em massa, como update_attributes
:
class Band include Mongoid::Document field :name, type: String field :origin, type: String attr_readonly :name, :origin end band = Band.create(name: "Placebo") band.update_attributes(name: "Tool") # Filters out the name change.
Se você tentar atualizar ou remover explicitamente um atributo somente leitura por si mesmo, uma exceção do ReadonlyAttribute
será criada:
band.update_attribute(:name, "Tool") # Raises the error. band.remove_attribute(:name) # Raises the error.
As atribuições a atributos somente leitura usando seus configuradores serão ignoradas:
b = Band.create!(name: "The Rolling Stones") # => #<Band _id: 6287a3d5d1327a5292535383, name: "The Rolling Stones", origin: nil> b.name = "The Smashing Pumpkins" # => "The Smashing Pumpkins" b.name # => "The Rolling Stones"
Chamadas para operadores de persistência atômica, como bit
e inc
, persistirão a alterações nos campos somente leitura.
Campos de carimbo de hora
O Mongoid fornece um módulo de registro de data e hora em Mongoid::Timestamps
que pode ser incluído para obter o comportamento básico dos campos created_at
e updated_at
.
class Person include Mongoid::Document include Mongoid::Timestamps end
Você também pode optar por ter apenas carimbos de data/hora específicos para criação ou modificação.
class Person include Mongoid::Document include Mongoid::Timestamps::Created end class Post include Mongoid::Document include Mongoid::Timestamps::Updated end
Se você quiser desativar o carimbo de data/hora para chamadas específicas, use o método atemporal:
person.timeless.save Person.timeless.create!
Se você quiser campos de registro de data/hora mais curtos e com nomes alternativos para economizar espaço, você pode incluir as versões curtas dos módulos.
class Band include Mongoid::Document include Mongoid::Timestamps::Short # For c_at and u_at. end class Band include Mongoid::Document include Mongoid::Timestamps::Created::Short # For c_at only. end class Band include Mongoid::Document include Mongoid::Timestamps::Updated::Short # For u_at only. end
Nomes de campo com pontos/pontos finais (.
) e cifrão ($
)
Usar pontos (.
) em nomes de campos e iniciar um nome de campo com um cifrão ($
) não é recomendado, pois o Mongoid fornece suporte limitado para recuperar e operar nos documentos armazenados nesses campos.
Tanto o Mongoid quanto a linguagem de consulta do MongoDB (MQL) geralmente usam o caractere ponto (.
) para separar nomes de campos em um caminho de campo que atravessa documentos incorporados e palavras que começam com o cifrão ($
) como operadores. O MongoDB oferece suporte limitado para o uso de nomes de campo contendo pontos e começando com o cifrão para interoperabilidade com outros softwares; no entanto, como esse suporte está limitado a operadores específicos (por exemplo getField, setField) e exigindo o uso do pipeline de agregação para consultas e atualizações, os aplicativos devem evitar o uso de pontos nos nomes de campos e iniciar os nomes de campos com o cifrão, se possível.
O Mongoid, a partir da versão 8, agora permite que os usuários acessem campos que começam com cifrões e que contêm pontos/períodos. Eles podem ser acessados utilizando o método send
como segue:
class User include Mongoid::Document field :"first.last", type: String field :"$_amount", type: Integer end user = User.first user.send(:"first.last") # => Mike.Trout user.send(:"$_amount") # => 42650000
Também é possível utilizar o read_attribute
para acessar estes campos:
user.read_attribute("first.last") # => Mike.Trout
Devido às limitações do servidor, a atualização e a substituição de campos contendo pontos e cifrões exigem o uso de operadores especiais. Por esse motivo, chamar configuradores nesses campos é proibido e gerará um erro:
class User include Mongoid::Document field :"first.last", type: String field :"$_amount", type: Integer end user = User.new user.send(:"first.last=", "Shohei.Ohtani") # raises a InvalidDotDollarAssignment error user.send(:"$_amount=", 8500000) # raises a InvalidDotDollarAssignment error