update rails query left includes active ruby-on-rails rails-activerecord

ruby on rails - rails - ¿Cómo puedo configurar los valores por defecto en ActiveRecord?



rails update (25)

También he visto a personas ponerlo en su migración, pero prefiero verlo definido en el código del modelo.

¿Hay una forma canónica de establecer el valor predeterminado para los campos en el modelo ActiveRecord?

La forma canónica de Rails, antes de Rails 5, era en realidad establecerla en la migración, y simplemente buscar en db/schema.rb para ver qué valores predeterminados está configurando la DB para cualquier modelo.

Contrariamente a lo que dice la respuesta de @Jeff Perrin (que es un poco antiguo), el enfoque de migración incluso aplicará el valor predeterminado cuando se usa Model.new , debido a la magia de Rails. Trabajo verificado en rieles 4.1.16.

Lo más simple es a menudo lo mejor. Menos deudas de conocimiento y posibles puntos de confusión en la base de código. Y ''simplemente funciona''.

class AddStatusToItem < ActiveRecord::Migration def change add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" } end end

El null: false no permite valores NULL en la base de datos y, como beneficio adicional, también actualiza todos los registros de base de datos preexistentes y se establece con el valor predeterminado para este campo también. Puede excluir este parámetro en la migración si lo desea, ¡pero lo encontré muy útil!

La forma canónica en Rails 5+ es, como dijo @Lucas Caton:

class Item < ActiveRecord::Base attribute :scheduler_type, :string, default: ''hotseat'' end

¿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 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 una forma canónica de establecer el valor predeterminado para los campos en el modelo ActiveRecord?


Algunos casos simples pueden manejarse definiendo un valor predeterminado en el esquema de la base de datos, pero eso no controla un número de casos más complicados, incluidos los valores calculados y las claves de otros modelos. Para estos casos hago esto:

after_initialize :defaults def defaults unless persisted? self.extras||={} self.other_stuff||="This stuff" self.assoc = [OtherModel.find_by_name(''special'')] end end

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 sorprendente que no se proporcione una devolución de llamada after_new para este caso de uso obvio, pero he confirmado si el objeto ya ha persistido para indicar 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:

after_initialize :defaults, unless: :persisted? # ":if => :new_record?" is equivalent in this context def defaults self.extras||={} self.other_stuff||="This stuff" self.assoc = [OtherModel.find_by_name(''special'')] end


Aunque hacer eso para establecer valores predeterminados es confuso e incómodo en la mayoría de los casos, también puede usar :default_scope . Echa un vistazo a los comentarios de squil aquí .


Colocamos los valores predeterminados en la base de datos a través de las migraciones (especificando la opción :default en cada definición de columna) y permitimos 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: convención sobre 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 la (s) migración (es).


Descubrí que el uso de un método de validación proporciona mucho control sobre la configuración de los valores predeterminados. Incluso puede establecer valores predeterminados (o fallar la validación) para las actualizaciones. Incluso establece un valor predeterminado diferente para las inserciones frente a las actualizaciones si realmente lo desea. Tenga en cuenta que el valor predeterminado no se establecerá hasta que #válido? se llama.

class MyModel validate :init_defaults private def init_defaults if new_record? self.some_int ||= 1 elsif some_int.nil? errors.add(:some_int, "can''t be blank on update") end end end

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: guides.rubyonrails.org/…


Desde el api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Use el método before_validation en su modelo, le brinda las opciones para 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 adaptarlo fácilmente para establecer los valores que desee.

class CreditCard < ActiveRecord::Base # Strip everything but digits, so the user can specify "555 234 34" or # "5552-3434" or both will mean "55523434" before_validation(:on => :create) do self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number") end end class Subscription < ActiveRecord::Base before_create :record_signup private def record_signup self.signed_up_on = Date.today end end class Firm < ActiveRecord::Base # Destroys the associated clients and people when the firm is destroyed before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" } before_destroy { |record| Client.destroy_all "client_of = #{record.id}" } end

Sorprendió que su no haya sido sugerido aquí.


El método after_initialize está en desuso, use la devolución de llamada en su lugar.

after_initialize :defaults def defaults self.extras||={} self.other_stuff||="This stuff" end

sin embargo, usar : default en sus migraciones sigue siendo la forma más limpia.


El patrón de devolución de llamada after_initialize se puede mejorar simplemente haciendo lo siguiente

after_initialize :some_method_goes_here, :if => :new_record?

Esto tiene un beneficio no trivial si su código de inicio necesita tratar con asociaciones, ya que el siguiente código activa un n + 1 sutil si lee el registro inicial sin incluir el asociado.

class Account has_one :config after_initialize :init_config def init_config self.config ||= build_config end end


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

Los métodos de atributo (captadores) son, por supuesto, métodos en sí mismos, por lo que puede anularlos y proporcionar un valor predeterminado. Algo como:

Class Foo < ActiveRecord::Base # has a DB column/field atttribute called ''status'' def status (val = read_attribute(:status)).nil? ? ''ACTIVE'' : val end end

A menos que, como alguien señalado, tengas que 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 la base de datos lo admite.


En Rails 5+, puede usar el método de attribute en sus modelos, por ejemplo:

class Account < ApplicationRecord attribute :locale, :string, default: ''en'' end


Esto ha sido respondido durante mucho tiempo, pero necesito valores predeterminados con frecuencia y prefiero no ponerlos en la base de datos. Creo una preocupación de DefaultValues :

module DefaultValues extend ActiveSupport::Concern class_methods do def defaults(attr, to: nil, on: :initialize) method_name = "set_default_#{attr}" send "after_#{on}", method_name.to_sym define_method(method_name) do if send(attr) send(attr) else value = to.is_a?(Proc) ? to.call : to send("#{attr}=", value) end end private method_name end end end

Y luego usarlo en mis modelos así:

class Widget < ApplicationRecord include DefaultValues defaults :category, to: ''uncategorized'' defaults :token, to: -> { SecureRandom.uuid } end


Hay varios problemas con cada uno de los métodos disponibles, pero creo que la definición de una after_initialize llamada after_initialize es el camino a seguir por las siguientes razones:

  1. default_scope inicializará los valores para los nuevos modelos, pero luego se convertirá en el ámbito en el que encontrará el modelo. Si solo quieres inicializar algunos números a 0, esto no es lo que quieres.
  2. La definición de valores predeterminados en su migración también funciona parte del tiempo ... Como ya se ha mencionado, esto no funcionará cuando solo llame a Model.new.
  3. Anular la initialize puede funcionar, pero no olvides llamar super !
  4. Usar un plugin como phusion''s se está volviendo un poco ridículo. Esto es Ruby, ¿realmente necesitamos un complemento solo para inicializar algunos valores predeterminados?
  5. La after_initialize está en desuso a partir de Rails 3. Cuando after_initialize en rails 3.0.3, after_initialize la siguiente advertencia en la consola:

ADVERTENCIA DE DEPRECATION: Base # after_initialize ha sido desaprobado, por favor use el método Base.after_initialize: en su lugar. (Llamado desde / Users / me / myapp / app / models / my_model: 15)

Por lo tanto, diría que escriba una after_initialize llamada after_initialize , que le permite atributos predeterminados además de permitirle establecer valores predeterminados en asociaciones como:

class Person < ActiveRecord::Base has_one :address after_initialize :init def init self.number ||= 0.0 #will set the default value only if it''s nil self.address ||= build_address #let''s you set a default association end end

Ahora solo tiene un lugar donde buscar la inicialización de sus modelos. Estoy usando este método hasta que a alguien se le ocurra uno mejor.

Advertencias:

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

  2. Si solo está seleccionando un subconjunto de columnas para un modelo (es decir, al select en una consulta como Person.select(:firstname, :lastname).all ) obtendrá un MissingAttributeError si su método init accede a una columna que no tiene '' Se ha incluido en la cláusula de select . 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)


Lo primero es lo primero: no estoy en desacuerdo con la respuesta de Jeff. Tiene sentido cuando tu aplicación es pequeña y tu lógica simple. Estoy aquí tratando de dar una idea de cómo puede ser un problema al crear y mantener una aplicación más grande. No recomiendo usar este enfoque primero cuando se construye algo pequeño, sino tenerlo en cuenta como un enfoque alternativo:

Una pregunta aquí es si este valor predeterminado en los registros es lógica de negocios. Si lo es, sería prudente ponerlo en el modelo ORM. Dado que el campo que menciona ryw está activo , esto suena como una lógica de negocios. Por ejemplo, el usuario está activo.

¿Por qué me preocuparía poner las preocupaciones de negocios en un modelo de ORM?

  1. Se rompe el SRP . Cualquier clase heredada de ActiveRecord :: Base ya está haciendo muchas cosas diferentes, entre ellas la consistencia de los datos (validaciones) y la persistencia (guardar). Poner la lógica empresarial, por pequeña que sea, con AR :: Base breaks SRP.

  2. Es más lento para probar. Si quiero probar cualquier forma de lógica que suceda en mi modelo ORM, mis pruebas deben inicializar Rails para poder ejecutarse. Esto no será un gran problema al principio de su aplicación, pero se acumulará hasta que las pruebas de su unidad demoren mucho tiempo en ejecutarse.

  3. Romperá el SRP aún más en la línea y de manera concreta. ¿Dice que nuestro negocio ahora requiere que enviemos un correo electrónico a los usuarios cuando el artículo se active? Ahora estamos agregando la lógica del correo electrónico al modelo ORM del artículo, cuya responsabilidad principal es modelar un artículo. No debería importarle la lógica del correo electrónico. Este es un caso de efectos secundarios de negocios . Estos no pertenecen al modelo ORM.

  4. Es difícil diversificarse. He visto aplicaciones de Rails maduras con cosas como una base de datos init_type: string field, cuyo único propósito es controlar la lógica de inicialización. Esto está contaminando la base de datos para solucionar un problema estructural. Hay mejores maneras, creo.

La forma PORO: Si bien este es un código un poco más, le permite mantener sus modelos de ORM y su lógica de negocios por separado. El código aquí está simplificado, pero debería mostrar la idea:

class SellableItemFactory def self.new(attributes = {}) record = Item.new(attributes) record.active = true if record.active.nil? record end end

Luego, con esto en su lugar, la forma de crear un nuevo artículo sería

SellableItemFactory.new

Y mis pruebas ahora simplemente podrían verificar que ItemFactory establece activos en Artículo si no tiene un valor. No es necesaria la inicialización de Rails, no se rompe el SRP. Cuando la inicialización del elemento se vuelve más avanzada (por ejemplo, establecer un campo de estado, un tipo predeterminado, etc.), se puede agregar esto a ItemFactory. Si terminamos con dos tipos de valores predeterminados, podemos crear un nuevo BusinesCaseItemFactory para hacer esto.

NOTA: También podría ser beneficioso utilizar la inyección de dependencia aquí para permitir que la fábrica genere muchos elementos activos, pero lo omití por simplicidad. Aquí está: self.new (klass = Elemento, atributos = {})


Los chicos de Phusion tienen un buen plugin para esto.


Me encontré con problemas con after_initialize dando errores ActiveModel::MissingAttributeError al hacer ActiveModel::MissingAttributeError complejas:

p.ej:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"buscar" en el .where es hash de condiciones

Así que terminé haciéndolo anulando inicializar de esta manera:

def initialize super default_values end private def default_values self.date_received ||= Date.current end

La super llamada es necesaria para asegurarse de que el objeto se inicialice correctamente desde ActiveRecord::Base antes de personalizar mi código, es decir: valores_predeterminados


Preguntas similares, pero todas tienen un contexto ligeramente diferente: - ¿Cómo creo un valor predeterminado para los atributos en el modelo de Rails Activerecord?

La mejor respuesta: depende de lo que quieras!

Si desea que cada objeto comience con un valor: use after_initialize :init

¿Desea que el formulario new.html tenga un valor predeterminado al abrir la página? utilizar https://.com/a/5127684/1536309

class Person < ActiveRecord::Base has_one :address after_initialize :init def init self.number ||= 0.0 #will set the default value only if it''s nil self.address ||= build_address #let''s you set a default association end ... end

Si desea que cada objeto tenga un valor calculado a partir de la entrada del usuario: use before_save :default_values ¿Desea que el usuario ingrese X y luego Y = X+''foo'' ? utilizar:

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


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 db predeterminado es NULL, su modelo / ruby ​​definido por defecto es "alguna cadena", pero realmente desea establecer el valor en nil por cualquier motivo: 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 ||= , ¿ my_attr_changed? a my_attr_changed? ...

PERO ahora imagine que su valor predeterminado de db es "alguna cadena", su valor predeterminado por el modelo / ruby ​​es "alguna otra cadena", pero en un escenario determinado, desea establecer el valor en "alguna cadena" (el valor predeterminado de db): MyModel.new(my_attr: ''some_string'')

Esto resultará en my_attr_changed? siendo falso porque el valor coincide con el valor predeterminado de db, que a su vez activará el código predeterminado definido por ruby ​​y establecerá el valor en "alguna otra cadena", nuevamente, no en lo que deseaba.

Por esas razones, no creo que esto pueda lograrse correctamente con solo un gancho after_initialize.

Una vez más, creo que la gema "default_value_for" está adoptando el enfoque correcto: https://github.com/FooBarWidget/default_value_for


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 aasm , después de lo cual simplemente puede hacer

aasm column: "status" do state :available, initial: true state :used # transitions end

Aún no inicializa el valor de los registros no guardados, pero es un poco más limpio que rodar los suyos con init o lo que sea, y usted obtiene los otros beneficios de aasm, como los ámbitos para todos sus estados.


Sup chicos, terminé haciendo lo siguiente:

def after_initialize self.extras||={} self.other_stuff||="This stuff" end

¡Funciona de maravilla!



Yo uso la gema de attribute-defaults

Desde la documentación: ejecute sudo gem install attribute-defaults y agregue require ''attribute_defaults'' a su aplicación.

class Foo < ActiveRecord::Base attr_default :age, 18 attr_default :last_seen do Time.now end end Foo.new() # => age: 18, last_seen => "2014-10-17 09:44:27" Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"


usar default_scope en los rieles 3

api doc

ActiveRecord oculta la diferencia entre la configuración predeterminada definida en la base de datos (esquema) y la configuración predeterminada realizada en la aplicación (modelo). Durante la inicialización, analiza el esquema de la base de datos y anota los valores predeterminados especificados allí. Más adelante, al crear objetos, asigna los valores predeterminados especificados en el esquema sin tocar la base de datos.

discussion



¡Para esto son los constructores! Anular el método de initialize del modelo.

Utilice el método after_initialize .


class Item < ActiveRecord::Base def status self[:status] or ACTIVE end before_save{ self.status ||= ACTIVE } end