¿Cómo creo un valor predeterminado para los atributos en el modelo de Rails activerecord?

Quiero crear un valor predeterminado para un atributo definiéndolo en ActiveRecord. De forma predeterminada, cada vez que se crea el registro, quiero tener un valor predeterminado para atributo :status . Traté de hacer esto:

 class Task < ActiveRecord::Base def status=(status) status = 'P' write_attribute(:status, status) end end 

Pero al momento de la creación, aún recupero este error de la base de datos:

 ActiveRecord::StatementInvalid: Mysql::Error: Column 'status' cannot be null 

Por lo tanto, supongo que el valor no se aplicó al atributo.

¿Cuál sería la manera más elegante de hacer esto en Rails?

Muchas gracias.

Puede establecer una opción predeterminada para la columna en la migración

 .... add_column :status, :string, :default => "P" .... 

O

Puede usar una callback, before_save

 class Task < ActiveRecord::Base before_save :default_values def default_values self.status ||= 'P' # note self.status = 'P' if self.status.nil? might be safer (per @frontendbeauty) end end 

Debido a que me encontré con este problema hace poco tiempo y las opciones para Rails 3.0 son un poco diferentes, daré otra respuesta a esta pregunta.

En Rails 3.0 quieres hacer algo como esto:

 class MyModel < ActiveRecord::Base after_initialize :default_values private def default_values self.name ||= "default value" end end 

Puede hacerlo sin escribir ningún código 🙂 Solo necesita establecer el valor predeterminado para la columna en la base de datos. Puedes hacer esto en tus migraciones. Por ejemplo:

 create_table :projects do |t| t.string :status, :null => false, :default => 'P' ... t.timestamps end 

Espero que ayude.

Cuando necesito valores predeterminados, generalmente es para nuevos registros antes de que se muestre la vista de la nueva acción. El siguiente método establecerá los valores predeterminados para solo los nuevos registros para que estén disponibles cuando se procesen los formularios. before_save y before_create son demasiado tarde y no funcionarán si desea que los valores predeterminados aparezcan en los campos de entrada .

 after_initialize do if self.new_record? # values will be available for new record forms. self.status = 'P' self.featured = true end end 

La solución depende de algunas cosas.

¿El valor predeterminado depende de otra información disponible en el momento de la creación? ¿Se puede borrar la base de datos con consecuencias mínimas?

Si respondió la primera pregunta sí, entonces quiere usar la solución de Jim

Si respondió la segunda pregunta sí, entonces quiere usar la solución de Daniel

Si contestó no a ambas preguntas, probablemente sea mejor que agregue y ejecute una nueva migración.

 class AddDefaultMigration < ActiveRecord::Migration def self.up change_column :tasks, :status, :string, :default => default_value, :null => false end end 

: la cadena se puede reemplazar con cualquier tipo que ActiveRecord :: Migration reconozca.

La CPU es barata, por lo que la redefinición de Tarea en la solución de Jim no causará muchos problemas. Especialmente en un entorno de producción. Esta migración es la forma correcta de hacerlo, ya que se carga y se invoca con mucha menos frecuencia.

Consideraría usar attr_defaults que se encuentran aquí . Tus sueños más salvajes se harán realidad.

Solo fortaleciendo la respuesta de Jim

Usando presencia uno puede hacer

 class Task < ActiveRecord::Base before_save :default_values def default_values self.status = status.presence || 'P' end end 

Para los tipos de columnas que Rails admite, como la cadena en esta pregunta, el mejor enfoque es establecer el valor predeterminado de columna en la base de datos como lo indica Daniel Kristensen. Rails realizará una introspección en el DB e inicializará el objeto en consecuencia. Además, eso hace que su DB esté segura de que alguien agregue una fila fuera de su aplicación Rails y se olvide de inicializar esa columna.

Para los tipos de columna, Rails no es compatible, p. Ej. Columnas ENUM. Rails no podrá realizar una introspección de la columna predeterminada. Para estos casos, no desea utilizar after_initialize (se llama cada vez que se carga un objeto desde el DB así como también cada vez que se crea un objeto usando .new), before_create (porque ocurre después de la validación) o before_save (porque ocurre al actualizar también, que generalmente no es lo que quieres).

Por el contrario, desea establecer el atributo en una validez previa en: crear, así:

 before_validation :set_status_because_rails_cannot, on: :create def set_status_because_rails_cannot self.status ||= 'P' end 

Según lo veo, hay dos problemas que deben abordarse cuando se necesita un valor predeterminado.

  1. Necesita el valor presente cuando se inicializa un nuevo objeto. El uso de after_initialize no es adecuado porque, como se dijo, se invocará durante las llamadas a #find, lo que dará lugar a un golpe de rendimiento.
  2. Debe conservar el valor predeterminado cuando se guarde

Aquí está mi solución:

 # the reader providers a default if nil # but this wont work when saved def status read_attribute(:status) || "P" end # so, define a before_validation callback before_validation :set_defaults protected def set_defaults # if a non-default status has been assigned, it will remain # if no value has been assigned, the reader will return the default and assign it # this keeps the default logic DRY status = status end 

Me encantaría saber por qué la gente piensa en este enfoque.

Encontré una mejor manera de hacerlo ahora:

 def status=(value) self[:status] = 'P' end 

En Ruby, una llamada a método no tiene paréntesis, por lo tanto, debería nombrar la variable local en otra cosa, de lo contrario, Ruby lo reconocerá como una llamada a método.

En su ejemplo aquí, está asignando ‘P’ a una variable local ‘estado’. Pruebe self.status = 'P' para este rey de las cosas. Aunque sería mejor utilizar after_initialize como se menciona en otras respuestas.