Menu Docs

Página inicial do DocsMongoid

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 ($)

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:

  1. Ao atribuir valores aos campos no tempo de execução, os valores são convertidos para o tipo especificado.

  2. 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.

  3. Ao consultar documentos, os parâmetros de consulta são convertidos no tipo especificado antes de serem enviados ao MongoDB.

  4. 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

  • BigDecimal

  • Mongoid::Boolean, que pode ser especificado simplesmente como Boolean no escopo de uma classe que incluiu Mongoid::Document.

  • Data

  • DateTime

  • Float

  • Hash

  • Integer

  • Objeto

  • BSON::ObjectId

  • Range

  • regexp

  • Set

  • String

  • Mongoid::StringifiedSymbol, que pode ser especificado simplesmente como StringifiedSymbol no escopo de uma classe que incluiu Mongoid::Document.

  • Símbolo

  • Hora

  • 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+.

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 como Time)

  • DateTime (os valores serão retornados como Time)

  • Range (os valores serão retornados como Hash)

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.

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

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

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.

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 que Mongoid.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.

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.

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.

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, enquanto BigDecimal não tem restrições em termos de alcance e precisão. BSON::Decimal128 tem um valor máximo de aproximadamente 10^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 um BSON::Decimal128, é recomendável armazená-los como uma cadeia de caracteres em vez de uma BSON::Decimal128. Você pode fazer isso definindo Mongoid.map_big_decimal_to_decimal128 como false. Se um valor que não cabe em BSON::Decimal128 for tentado ser armazenado como um, será gerado um erro.

  • BSON::Decimal128 é capaz de aceitar valores de NaN assinados, enquanto BigDecimal não é. Ao recuperar valores de NaN assinados a partir do banco de dados utilizando o tipo de campo BigDecimal, o NaN 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 de BSON::Decimal128 usando o tipo de campo BigDecimal 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.

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:

  1. A opção de configuração global do Mongoid.map_big_decimal_to_decimal128 pode ser configurada para false, e você pode continuar armazenando seus valores do BigDecimal como strings. Observe que você está abrindo mão das vantagens de armazenar valores BigDecimal como decimal128, como a possibilidade de fazer consulta e agregações com base no valor numérico do campo.

  2. A opção de configuração global Mongoid.map_big_decimal_to_decimal128 pode ser definida como true, e você pode converter todos os valores desse campo de strings para valores decimal128 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+.

  3. A opção de configuração global do Mongoid.map_big_decimal_to_decimal128 pode ser configurada para true, e você pode ter ambas as strings e valores do decimal128 para este campo. Desta forma, somente os valores de decimal128 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 de decimal128, entretanto, seu conjunto de dados migrará lentamente para todos os valores de decimal128, à medida que os valores de strings antigas são atualizados para decimal128 e novos valores de decimal128 são adicionados. Com esta configuração, você poderá continuar fazendo query de valores de BigDecimal 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.

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

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

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" }

É 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

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">

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.

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

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.

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.

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).

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.

O Mongoid oferece várias maneiras de personalizar o comportamento dos campos.

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}

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.

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!

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.

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 .

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"

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" => "₪" } }

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.

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.

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!" }

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

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.

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

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
←  Configuração do esquemaHerança →