web-dev-qa-db-fra.com

Comment créer une valeur par défaut pour les attributs dans le modèle Rails activerecord?

Je veux créer une valeur par défaut pour un attribut en le définissant dans ActiveRecord. Par défaut, chaque fois que l'enregistrement est créé, je souhaite définir une valeur par défaut pour l'attribut :status. J'ai essayé de faire ceci:

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

Mais lors de la création, je récupère toujours cette erreur de la base de données:

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

Par conséquent, je suppose que la valeur n'a pas été appliquée à l'attribut.

Quelle serait la manière élégante de faire cela dans Rails?

Merci beaucoup.

188
Joshua Partogi

Vous pouvez définir une option par défaut pour la colonne dans la migration.

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

OR

Vous pouvez utiliser un rappel, 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
285
Jim

Étant donné que j'ai rencontré ce problème il y a peu de temps et que les options pour Rails 3.0 sont légèrement différentes, je vais fournir une autre réponse à cette question.

Dans Rails 3.0, vous voulez faire quelque chose comme ceci:

class MyModel < ActiveRecord::Base
  after_initialize :default_values

  private
    def default_values
      self.name ||= "default value"
    end
end
190
BeepDog

Lorsque j'ai besoin de valeurs par défaut, c'est généralement pour les nouveaux enregistrements avant que la vue de la nouvelle action ne soit rendue. La méthode suivante définit les valeurs par défaut pour les nouveaux enregistrements uniquement afin qu'elles soient disponibles lors du rendu des formulaires. before_save et before_createtrop tard et ne fonctionnera pas si vous voulez que les valeurs par défaut apparaissent dans les champs de saisie.

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

Vous pouvez le faire sans écrire de code du tout :) Il vous suffit de définir la valeur par défaut de la colonne dans la base de données. Vous pouvez le faire dans vos migrations. Par exemple:

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

J'espère que ça t'as aidé.

77
Daniel Kristensen

La solution dépend de quelques choses.

La valeur par défaut dépend-elle d'autres informations disponibles au moment de la création? Pouvez-vous effacer la base de données avec des conséquences minimes?

Si vous avez répondu oui à la première question, vous souhaitez utiliser la solution de Jim

Si vous avez répondu oui à la deuxième question, vous voulez utiliser la solution de Daniel

Si vous avez répondu non aux deux questions, vous feriez probablement mieux d'ajouter et d'exécuter une nouvelle migration.

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

: string peut être remplacé par tout type reconnu par ActiveRecord :: Migration.

Le processeur étant bon marché, la redéfinition de Task dans la solution de Jim ne causera pas beaucoup de problèmes. Surtout dans un environnement de production. Cette migration est une bonne façon de procéder car elle est chargée et appelée beaucoup moins souvent.

23
EmFi

Je envisagerais d'utiliser les attr_defaults trouvés ici . Vos rêves les plus fous deviendront réalité.

12
PeppyHeppy

Juste renforcer réponse de Jim

En utilisant présence on peut faire

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

Pour les types de colonne, Rails prend en charge la boîte de dialogue - comme la chaîne de cette question -, la meilleure approche consiste à définir la colonne par défaut dans la base de données, comme indiqué par Daniel Kristensen. Rails introspectera la base de données et initialisera l'objet en conséquence. De plus, cela sécurise votre base de données en ajoutant une ligne en dehors de votre application Rails et en oubliant d’initialiser cette colonne.

Pour les types de colonne, Rails ne prend pas en charge les opérations par défaut - par exemple. Les colonnes ENUM - Rails ne pourront pas introspecter la valeur par défaut de la colonne. Dans ce cas, vous pas voulez utiliser after_initialize (il est appelé chaque fois qu'un objet est chargé depuis la base de données, ainsi que chaque fois qu'un objet est créé à l'aide de .new), before_create (car il survient après validation), ou before_save (car cela se produit également lors de la mise à jour, ce qui n’est généralement pas ce que vous voulez).

Au lieu de cela, vous voulez définir l'attribut dans une before_validation sur: create, comme suit:

before_validation :set_status_because_Rails_cannot, on: :create

def set_status_because_Rails_cannot
  self.status ||= 'P'
end
4
aec

Selon moi, deux problèmes doivent être résolus lorsque vous avez besoin d'une valeur par défaut.

  1. Vous avez besoin de la valeur présente lorsqu'un nouvel objet est initialisé. L'utilisation de after_initialize n'est pas appropriée car, comme indiqué, elle sera appelée lors des appels à #find, ce qui entraînera un impact négatif sur les performances.
  2. Vous devez conserver la valeur par défaut lors de l'enregistrement

Voici ma solution:

# 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

J'aimerais savoir pourquoi les gens pensent à cette approche.

4
Peter P.

J'ai trouvé un meilleur moyen de le faire maintenant:

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

Dans Ruby, un appel de méthode est autorisé à ne pas avoir de parenthèses. Par conséquent, je dois nommer la variable locale en quelque chose d'autre, sinon Ruby le reconnaîtra comme un appel de méthode.

0
Joshua Partogi