¿Cómo puedo establecer el valor predeterminado en ActiveRecord?
Veo una publicación de Pratik que describe un fragmento de código feo y complicado: 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
He visto los siguientes ejemplos buscando en Google:
def initialize
super
self.status = ACTIVE unless self.status
end
y
def after_initialize
return unless new_record?
self.status = ACTIVE
end
También he visto a personas ponerlo en su migración, pero prefiero verlo definido en el código del modelo.
¿Hay alguna forma canónica de establecer el valor predeterminado para los campos en el modelo ActiveRecord?
Respuestas:
Hay varios problemas con cada uno de los métodos disponibles, pero creo que definir una
after_initialize
devolución de llamada es el camino a seguir por las siguientes razones:default_scope
inicializará valores para nuevos modelos, pero luego se convertirá en el ámbito en el que encontrará el modelo. Si solo desea inicializar algunos números a 0, entonces esto no es lo que desea.initialize
puede funcionar, ¡pero no olvides llamarsuper
!after_initialize
está en desuso a partir de Rails 3. Cuando anuloafter_initialize
en rails 3.0.3, aparece la siguiente advertencia en la consola:Por lo tanto, diría que escriba una
after_initialize
devolución de llamada, que le permite atributos predeterminados además de permitirle establecer valores predeterminados en asociaciones de esta manera:Ahora solo tiene un lugar para buscar la inicialización de sus modelos. Estoy usando este método hasta que a alguien se le ocurra uno mejor.
Advertencias:
Para campos booleanos hacer:
self.bool_field = true if self.bool_field.nil?
Vea el comentario de Paul Russell sobre esta respuesta para más detalles.
Si solo está seleccionando un subconjunto de columnas para un modelo (es decir, utilizando
select
una consulta comoPerson.select(:firstname, :lastname).all
), obtendrá un mensajeMissingAttributeError
si suinit
método accede a una columna que no se ha incluido en laselect
cláusula. Puede protegerse contra este caso así:self.number ||= 0.0 if self.has_attribute? :number
y para una columna booleana ...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
También tenga en cuenta que la sintaxis es diferente a Rails 3.2 (vea el comentario de Cliff Darling a continuación)
fuente
initialize
, parece realmente complicada para algo que debe ser claro y bien definido. Pasé horas rastreando la documentación antes de buscar aquí porque asumí que esta funcionalidad ya estaba allí en algún lugar y simplemente no estaba al tanto.self.bool_field ||= true
, ya que esto forzará el campo a verdadero incluso si lo inicializa explícitamente a falso. En cambio hazloself.bool_field = true if self.bool_field.nil?
.MissingAttributeError
. Se puede añadir una verificación adicional como se muestra:self.number ||= 0.0 if self.has_attribute? :number
Para booleanos:self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
. Este es Rails 3.2+: para usarlo antes,self.attributes.has_key?
necesita una cadena en lugar de un símbolo.initialize
conreturn if !new_record?
evitar problemas de rendimiento.Rieles 5+
Puede usar el método de atributo dentro de sus modelos, por ejemplo:
También puede pasar una lambda al
default
parámetro. Ejemplo:fuente
Value
y no se realizará ninguna conversión de texto.store_accessor :my_jsonb_column, :locale
que luego puede definirattribute :locale, :string, default: 'en'
nil
. Si no pueden sernil
DBnot null
+ DB default + github.com/sshaw/keep_defaults son el camino a seguir desde mi experienciaPonemos los valores predeterminados en la base de datos a través de migraciones (especificando la
:default
opción en cada definición de columna) y dejamos que Active Record use estos valores para establecer el valor predeterminado para cada atributo.En mi humilde opinión, este enfoque está alineado con los principios de AR: la convención sobre la configuración, DRY, la definición de la tabla impulsa el modelo, no al revés.
Tenga en cuenta que los valores predeterminados todavía están en el código de la aplicación (Ruby), aunque no en el modelo sino en las migraciones.
fuente
Algunos casos simples pueden manejarse definiendo un valor predeterminado en el esquema de la base de datos, pero eso no maneja una cantidad de casos más complicados, incluidos los valores calculados y las claves de otros modelos. Para estos casos hago esto:
Decidí usar after_initialize pero no quiero que se aplique a los objetos que se encuentran solo aquellos nuevos o creados. Creo que es casi impactante que no se proporcione una devolución de llamada after_new para este caso de uso obvio, pero lo hice confirmando si el objeto ya persiste, lo que indica que no es nuevo.
Habiendo visto la respuesta de Brad Murray, esto es aún más claro si la condición se traslada a la solicitud de devolución de llamada:
fuente
:before_create
?El patrón de devolución de llamada after_initialize se puede mejorar simplemente haciendo lo siguiente
Esto tiene un beneficio no trivial si su código de inicio necesita tratar con asociaciones, ya que el siguiente código activa un sutil n + 1 si lee el registro inicial sin incluir el asociado.
fuente
Los chicos de Phusion tienen un buen complemento para esto.
fuente
:default
valores en las migraciones de esquemas 'solo funcionen'Model.new
.:default
valores de las migraciones 'solo funcionen'Model.new
, al contrario de lo que Jeff dijo en su publicación. Trabajo verificado en Rails 4.1.16.Una forma potencial aún mejor / más limpia que las respuestas propuestas es sobrescribir el descriptor de acceso, de esta manera:
Consulte "Sobrescribir los accesos predeterminados" en la documentación de ActiveRecord :: Base y más de StackOverflow sobre el uso de self .
fuente
attributes
. Probado en rieles 5.2.0.Yo uso la
attribute-defaults
gemaDe la documentación: ejecute
sudo gem install attribute-defaults
y agreguerequire 'attribute_defaults'
a su aplicación.fuente
Preguntas similares, pero todas tienen un contexto ligeramente diferente: - ¿Cómo creo un valor predeterminado para los atributos en el modelo de ActiveRecord de Rails?
La mejor respuesta: ¡ Depende de lo que quieras!
Si desea que cada objeto comience con un valor: use
after_initialize :init
¿Desea que el
new.html
formulario tenga un valor predeterminado al abrir la página? use https://stackoverflow.com/a/5127684/1536309Si desea que cada objeto tenga un valor calculado a partir de la entrada del usuario: use
before_save :default_values
¿Desea que el usuario ingreseX
y luegoY = X+'foo'
? utilizar:fuente
¡Para eso están los constructores! Anular elinitialize
método del modelo .Usa el
after_initialize
método.fuente
after_initialize
método en su lugar.Sup, chicos, terminé haciendo lo siguiente:
¡Funciona de maravilla!
fuente
Esto ha sido respondido durante mucho tiempo, pero necesito valores predeterminados con frecuencia y prefiero no ponerlos en la base de datos. Creo una
DefaultValues
inquietud:Y luego usarlo en mis modelos así:
fuente
La forma canónica de Rails, antes de Rails 5, era en realidad configurarlo en la migración, y solo mirar en el
db/schema.rb
cuando quiera ver qué valores predeterminados establece la base de datos para cualquier modelo.Al contrario de lo que dice la respuesta de @Jeff Perrin (que es un poco viejo), el enfoque de migración incluso aplicará el valor predeterminado cuando se usa
Model.new
, debido a la magia de Rails. Trabajo verificado en Rails 4.1.16.Lo más simple es a menudo lo mejor. Menos deuda de conocimiento y posibles puntos de confusión en la base de código. Y 'simplemente funciona'.
O, para el cambio de columna sin crear uno nuevo, haga lo siguiente:
O tal vez incluso mejor:
Consulte la guía oficial de RoR para ver las opciones en los métodos de cambio de columna.
No
null: false
permite valores NULL en la base de datos y, como beneficio adicional, también se actualiza para que todos los registros de bases de datos preexistentes que antes eran nulos se establezcan con el valor predeterminado para este campo también. Si lo desea, puede excluir este parámetro en la migración, ¡pero lo encontré muy útil!La forma canónica en Rails 5+ es, como dijo @Lucas Caton:
fuente
El problema con las soluciones after_initialize es que debe agregar after_initialize a cada objeto que busque fuera de la base de datos, independientemente de si accede a este atributo o no. Sugiero un enfoque cargado de pereza.
Los métodos de atributo (getters) son, por supuesto, métodos en sí mismos, por lo que puede anularlos y proporcionar un valor predeterminado. Algo como:
A menos que, como alguien señaló, debe hacer Foo.find_by_status ('ACTIVE'). En ese caso, creo que realmente necesitaría establecer el valor predeterminado en las restricciones de su base de datos, si el DB lo admite.
fuente
Me encontré con problemas al
after_initialize
darActiveModel::MissingAttributeError
errores al hacer hallazgos complejos:p.ej:
"buscar" en el
.where
hash de condicionesAsí que terminé haciéndolo anulando initialize de esta manera:
La
super
llamada es necesaria para asegurarse de que el objeto se inicialice correctamente desdeActiveRecord::Base
antes de hacer mi código personalizado, es decir: valores_predeterminadosfuente
def initialize(*); super; default_values; end
en Rails 5.2.0. Además, el valor predeterminado está disponible, incluso en el.attributes
hash.fuente
Aunque hacer eso para establecer los valores predeterminados es confuso e incómodo en la mayoría de los casos, también puede usarlo
:default_scope
. Mira el comentario de squil aquí .fuente
El método after_initialize está en desuso, use la devolución de llamada en su lugar.
sin embargo, usar : default en sus migraciones sigue siendo la forma más limpia.
fuente
after_initialize
método NO está en desuso . De hecho, la devolución de llamada de estilo macro que le da un ejemplo de IS está en desuso . Detalles: guides.rubyonrails.org/…Descubrí que usar un método de validación proporciona mucho control sobre la configuración predeterminada. Incluso puede establecer valores predeterminados (o fallar la validación) para las actualizaciones. Incluso puede establecer un valor predeterminado diferente para inserciones vs actualizaciones si realmente lo desea. Tenga en cuenta que el valor predeterminado no se establecerá hasta #valid? se llama.
Con respecto a la definición de un método after_initialize, podría haber problemas de rendimiento porque after_initialize también es llamado por cada objeto devuelto por: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find
fuente
new
acción se reutilice.Si la columna es una columna de tipo 'estado' y su modelo se presta para el uso de máquinas de estado, considere usar la gema de aasmo , después de lo cual simplemente puede hacer
Todavía no inicializa el valor de los registros no guardados, pero es un poco más limpio que rodar el tuyo
init
o lo que sea, y cosechas los otros beneficios del asasm, como los ámbitos para todos tus estados.fuente
https://github.com/keithrowell/rails_default_value
fuente
Recomiendo encarecidamente utilizar la gema "default_value_for": https://github.com/FooBarWidget/default_value_for
Hay algunos escenarios difíciles que requieren anular el método de inicialización, lo que hace esa gema.
Ejemplos:
Su valor predeterminado de db es NULL, su valor predeterminado definido por modelo / ruby es "alguna cadena", pero en realidad desea establecer el valor en nil por cualquier razón:
MyModel.new(my_attr: nil)
La mayoría de las soluciones aquí no podrán establecer el valor en nulo, y en su lugar lo establecerán en el valor predeterminado.
OK, entonces en lugar de tomar el
||=
enfoque, cambias amy_attr_changed?
...PERO ahora imagine que su valor predeterminado de db es "alguna cadena", su modelo / ruby definido por defecto es "alguna otra cadena", pero en un determinado escenario, desea establecer el valor en "alguna cadena" (el valor predeterminado de db):
MyModel.new(my_attr: 'some_string')
Esto dará como resultado
my_attr_changed?
ser falsa ya que el valor coincide con el valor por defecto db, que a su vez se disparará su código predeterminado rubí definido y establecer el valor a "alguna otra cadena" - de nuevo, no lo que usted desea.Por esas razones, no creo que esto se pueda lograr correctamente con solo un gancho after_initialize.
Nuevamente, creo que la gema "default_value_for" está tomando el enfoque correcto: https://github.com/FooBarWidget/default_value_for
fuente
Aquí hay una solución que he usado y me sorprendió un poco que aún no se haya agregado.
Hay dos partes en esto. La primera parte es establecer el valor predeterminado en la migración real, y la segunda parte es agregar una validación en el modelo para garantizar que la presencia sea verdadera.
Entonces verá aquí que el valor predeterminado ya está configurado. Ahora, en la validación, desea asegurarse de que siempre haya un valor para la cadena, así que solo haga
Lo que esto hará es establecer el valor predeterminado para usted. (para mí tengo "Bienvenido al equipo"), y luego irá un paso más allá y garantizará que siempre haya un valor presente para ese objeto.
¡Espero que ayude!
fuente
fuente
use default_scope en rails 3
doc api
ActiveRecord oculta la diferencia entre el valor predeterminado definido en la base de datos (esquema) y el valor predeterminado realizado en la aplicación (modelo). Durante la inicialización, analiza el esquema de la base de datos y anota cualquier valor predeterminado especificado allí. Más tarde, al crear objetos, asigna los valores predeterminados especificados en el esquema sin tocar la base de datos.
discusión
fuente
default_scope
. Esto hará que todas sus consultas agreguen esta condición al campo que ha establecido. Casi NUNCA es lo que quieres.De los documentos de la api http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Use el
before_validation
método en su modelo, le brinda las opciones de crear una inicialización específica para crear y actualizar llamadas, por ejemplo, en este ejemplo (nuevamente código tomado del ejemplo de api docs) el campo de número se inicializa para una tarjeta de crédito. Puede adaptar esto fácilmente para establecer los valores que deseeSorprendido de que el suyo no haya sido sugerido aquí
fuente