Como posso definir o valor padrão no ActiveRecord?
Eu vejo uma postagem do Pratik que descreve um pedaço feio e complicado de código: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model
class Item < ActiveRecord::Base
def initialize_with_defaults(attrs = nil, &block)
initialize_without_defaults(attrs) do
setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
!attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
setter.call('scheduler_type', 'hotseat')
yield self if block_given?
end
end
alias_method_chain :initialize, :defaults
end
Vi os seguintes exemplos pesquisando no Google:
def initialize
super
self.status = ACTIVE unless self.status
end
e
def after_initialize
return unless new_record?
self.status = ACTIVE
end
Também vi pessoas colocarem isso em sua migração, mas prefiro vê-lo definido no código do modelo.
Existe uma maneira canônica de definir o valor padrão para os campos no modelo ActiveRecord?
Respostas:
Existem vários problemas com cada um dos métodos disponíveis, mas acredito que definir um
after_initialize
retorno de chamada é o caminho a seguir pelos seguintes motivos:default_scope
inicializará valores para novos modelos, mas esse será o escopo no qual você encontra o modelo. Se você deseja inicializar alguns números para 0, isso não é que você deseja.initialize
pode funcionar, mas não se esqueça de ligar parasuper
!after_initialize
está obsoleta a partir do Rails 3. Quando eu substituoafter_initialize
no Rails 3.0.3, recebo o seguinte aviso no console:Portanto, eu diria para escrever um
after_initialize
retorno de chamada, que permite atributos padrão , além de definir padrões em associações como:Agora você tem apenas um lugar para procurar a inicialização de seus modelos. Estou usando esse método até que alguém crie um melhor.
Ressalvas:
Para campos booleanos, faça:
self.bool_field = true if self.bool_field.nil?
Veja o comentário de Paul Russell nesta resposta para obter mais detalhes
Se você estiver selecionando apenas um subconjunto de colunas para um modelo (por exemplo, usando
select
uma consulta comoPerson.select(:firstname, :lastname).all
), receberá umMissingAttributeError
se o seuinit
método acessar uma coluna que não foi incluída naselect
cláusula. Você pode se proteger contra esse caso da seguinte maneira:self.number ||= 0.0 if self.has_attribute? :number
e para uma coluna booleana ...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
Observe também que a sintaxe é diferente antes do Rails 3.2 (veja o comentário de Cliff Darling abaixo)
fonte
initialize
, parece realmente complicada para algo que deve ser claro e bem definido. Passei horas pesquisando a documentação antes de pesquisar aqui, porque presumi que essa funcionalidade já estava lá em algum lugar e simplesmente não estava ciente disso.self.bool_field ||= true
, pois isso forçará o campo a true, mesmo se você inicializar explicitamente para false. Em vez disso, façaself.bool_field = true if self.bool_field.nil?
.MissingAttributeError
. Você pode adicionar uma verificação extra, como mostrado:self.number ||= 0.0 if self.has_attribute? :number
Para booleans:self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
. Este é o Rails 3.2+ - para uso anterior,self.attributes.has_key?
e você precisa de uma string em vez de um símbolo.initialize
comreturn if !new_record?
para evitar problemas de desempenho.Rails 5+
Você pode usar o método de atributo em seus modelos, por exemplo:
Você também pode passar uma lambda para o
default
parâmetro Exemplo:fonte
Value
e nenhuma conversão de tipo será feita.store_accessor :my_jsonb_column, :locale
você pode definir #attribute :locale, :string, default: 'en'
nil
. Se eles não podem sernil
DBnot null
+ DB padrão + github.com/sshaw/keep_defaults são o caminho a percorrer da minha experiênciaColocamos os valores padrão no banco de dados através de migrações (especificando o
:default
opção em cada definição de coluna) e permitimos que o Active Record use esses valores para definir o padrão para cada atributo.IMHO, essa abordagem está alinhada com os princípios da AR: convenção sobre configuração, DRY, a definição da tabela impulsiona o modelo, e não o contrário.
Observe que os padrões ainda estão no código do aplicativo (Ruby), embora não no modelo, mas nas migrações.
fonte
Alguns casos simples podem ser tratados com a definição de um padrão no esquema do banco de dados, mas isso não lida com vários casos mais complicados, incluindo valores calculados e chaves de outros modelos. Para esses casos, faço o seguinte:
Decidi usar o after_initialize, mas não quero que ele seja aplicado a objetos que são encontrados apenas aqueles novos ou criados. Eu acho que é quase chocante que um retorno de chamada after_new não seja fornecido para este caso de uso óbvio, mas fiz isso confirmando se o objeto já está persistindo, indicando que não é novo.
Tendo visto a resposta de Brad Murray, isso é ainda mais limpo se a condição for movida para a solicitação de retorno de chamada:
fonte
:before_create
?O padrão de retorno de chamada after_initialize pode ser aprimorado simplesmente fazendo o seguinte
Isso tem um benefício não trivial se o seu código de inicialização precisar lidar com associações, pois o código a seguir aciona um n + 1 sutil se você ler o registro inicial sem incluir o associado.
fonte
Os caras do Phusion têm um plugin interessante para isso.
fonte
:default
valores nas migrações de esquema 'apenas trabalhem'Model.new
.:default
valores nas migrações para 'apenas trabalhar'Model.new
, ao contrário do que Jeff disse em seu post. Trabalho verificado no Rails 4.1.16.Uma maneira potencial ainda melhor / mais limpa do que as respostas propostas é substituir o acessador, assim:
Consulte "Substituindo acessadores padrão" na documentação do ActiveRecord :: Base e muito mais no StackOverflow ao usar self .
fonte
attributes
. Testado em trilhos 5.2.0.Eu uso a
attribute-defaults
gemaNa documentação: execute
sudo gem install attribute-defaults
e adicionerequire 'attribute_defaults'
ao seu aplicativo.fonte
Perguntas semelhantes, mas todas possuem um contexto um pouco diferente: - Como crio um valor padrão para atributos no modelo de registro ativo do Rails?
Melhor resposta: depende do que você quer!
Se você deseja que cada objeto comece com um valor: use
after_initialize :init
Deseja que o
new.html
formulário tenha um valor padrão ao abrir a página? use https://stackoverflow.com/a/5127684/1536309Se você deseja que cada objeto tenha um valor calculado a partir da entrada do usuário: use
before_save :default_values
Deseja que o usuário insiraX
e, em seguidaY = X+'foo'
? usar:fonte
É para isso que os construtores servem! Substitua oinitialize
método do modelo .Use o
after_initialize
métodofonte
after_initialize
método.Sup galera, acabei fazendo o seguinte:
Funciona como um encanto!
fonte
Isso foi respondido por um longo tempo, mas preciso dos valores padrão com frequência e prefiro não colocá-los no banco de dados. Eu crio uma
DefaultValues
preocupação:E depois usá-lo nos meus modelos da seguinte forma:
fonte
O caminho canônico do Rails, antes do Rails 5, era realmente configurá-lo na migração e apenas olhar no
db/schema.rb
sempre que desejar ver quais valores padrão estão sendo definidos pelo DB para qualquer modelo.Ao contrário do que afirma a resposta de @Jeff Perrin (que é um pouco antiga), a abordagem de migração aplicará o padrão ao usar
Model.new
, devido a alguma mágica do Rails. Trabalho verificado no Rails 4.1.16.A coisa mais simples é geralmente a melhor. Menos dívida de conhecimento e possíveis pontos de confusão na base de código. E 'simplesmente funciona'.
Ou, para alterar a coluna sem criar uma nova, faça o seguinte:
Ou talvez até melhor:
Consulte o guia oficial do RoR para obter opções nos métodos de alteração de coluna.
o
null: false
desaprova valores NULL no banco de dados e, como um benefício adicionado, também é atualizado para que todos os registros de banco de dados preexistentes anteriormente nulos sejam definidos com o valor padrão também para este campo. Você pode excluir esse parâmetro na migração, se desejar, mas achei muito útil!A maneira canônica no Rails 5+ é, como @Lucas Caton disse:
fonte
O problema com as soluções after_initialize é que você precisa adicionar um after_initialize a todos os objetos que você procura do banco de dados, independentemente de acessar ou não esse atributo. Sugiro uma abordagem preguiçosa.
Os métodos de atributo (getters) são obviamente métodos próprios, portanto você pode substituí-los e fornecer um padrão. Algo como:
A menos que, como alguém apontou, você precise executar Foo.find_by_status ('ACTIVE'). Nesse caso, acho que você realmente precisará definir o padrão nas restrições de seu banco de dados, se o banco de dados o suportar.
fonte
Corri para problemas com
after_initialize
dandoActiveModel::MissingAttributeError
erros ao fazer descobertas complexas:por exemplo:
"search" no
.where
hash de condiçõesEntão, acabei fazendo isso substituindo a inicialização desta maneira:
A
super
chamada é necessária para garantir que o objeto seja inicializado corretamenteActiveRecord::Base
antes de executar meu código de personalização, ou seja: default_valuesfonte
def initialize(*); super; default_values; end
no Rails 5.2.0. Além disso, o valor padrão está disponível, mesmo no.attributes
hash.fonte
Embora fazer isso para definir valores padrão seja confuso e complicado na maioria dos casos, você também pode usá-lo
:default_scope
. Confira o comentário do squil aqui .fonte
O método after_initialize foi descontinuado, use o retorno de chamada.
no entanto, usar : default em suas migrações ainda é a maneira mais limpa.
fonte
after_initialize
método NÃO está obsoleto . De fato, o retorno de chamada no estilo macro é um exemplo de IS descontinuado . Detalhes: Guides.rubyonrails.org/…Descobri que o uso de um método de validação fornece muito controle sobre a configuração de padrões. Você pode até definir padrões (ou falhar na validação) para atualizações. Você ainda define um valor padrão diferente para inserções versus atualizações, se realmente quiser. Observe que o padrão não será definido até #valid? é chamado.
Em relação à definição de um método after_initialize, pode haver problemas de desempenho porque o after_initialize também é chamado por cada objeto retornado por: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find
fonte
new
ação seja reutilizada.Se a coluna for do tipo 'status' e seu modelo se presta ao uso de máquinas de estado, considere usar a gema aasm , após a qual você pode simplesmente fazer
Ele ainda não inicializa o valor para registros não salvos, mas é um pouco mais limpo do que criar o seu próprio
init
ou qualquer outra coisa, e você colhe os outros benefícios do aasm, como escopos para todos os seus status.fonte
https://github.com/keithrowell/rails_default_value
fonte
Eu sugiro fortemente o uso da gema "default_value_for": https://github.com/FooBarWidget/default_value_for
Existem alguns cenários complicados que praticamente exigem a substituição do método de inicialização, o que essa gema exige.
Exemplos:
Seu padrão de banco de dados é NULL, seu modelo / padrão definido em ruby é "alguma string", mas você realmente deseja definir o valor como nulo por qualquer motivo:
MyModel.new(my_attr: nil)
A maioria das soluções aqui falhará em definir o valor como nulo e, em vez disso, o definirá como o padrão.
OK, então, em vez de usar a
||=
abordagem, você muda paramy_attr_changed?
...Mas agora imagine que o padrão do banco de dados é "alguma string", o padrão definido pelo modelo / ruby é "alguma outra string", mas em um determinado cenário, você deseja definir o valor como "alguma string" (o padrão do banco de dados):
MyModel.new(my_attr: 'some_string')
Isto irá resultar em
my_attr_changed?
ser falsa porque o valor corresponde ao db padrão, que por sua vez irá disparar seu código padrão definido pelo rubi e defina o valor para "alguma outra string" - mais uma vez, não o que você desejar.Por esses motivos, não acho que isso possa ser realizado adequadamente com apenas um gancho after_initialize.
Mais uma vez, acho que a gema "default_value_for" está adotando a abordagem correta: https://github.com/FooBarWidget/default_value_for
fonte
Aqui está uma solução que eu usei que fiquei um pouco surpresa ainda não foi adicionada.
Existem duas partes nele. A primeira parte é definir o padrão na migração real e a segunda parte é adicionar uma validação no modelo, garantindo que a presença seja verdadeira.
Então você verá aqui que o padrão já está definido. Agora, na validação, você deseja garantir que sempre haja um valor para a sequência, então faça
O que isso fará é definir o valor padrão para você. (para mim eu tenho "Bem-vindo à equipe"), e depois darei um passo adiante e garantirá que sempre exista um valor presente para esse objeto.
Espero que ajude!
fonte
fonte
use default_scope nos trilhos 3
api doc
O ActiveRecord obscurece a diferença entre o padrão definido no banco de dados (esquema) e o padrão feito no aplicativo (modelo). Durante a inicialização, ele analisa o esquema do banco de dados e anota quaisquer valores padrão especificados lá. Mais tarde, ao criar objetos, ele atribui esses valores padrão especificados no esquema sem tocar no banco de dados.
discussão
fonte
default_scope
. Isso fará com que todas as suas consultas adicionem essa condição ao campo que você definiu. É quase NUNCA o que você quer.Nos documentos da API http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Use o
before_validation
método em seu modelo, ele oferece as opções de criação de inicialização específica para chamadas de criação e atualização, por exemplo, neste exemplo (novamente código usado do exemplo da API api), o campo numérico é inicializado para um cartão de crédito. Você pode facilmente adaptar isso para definir os valores desejadosSurpreso que o dele não tenha sido sugerido aqui
fonte