开发者

Rails: How do I create a default value for attributes in Rails activerecord's model? [duplicate]

开发者 https://www.devze.com 2022-12-08 09:49 出处:网络
This question already has answers here: Rails: How can I set default values in ActiveRecord? (29 answers)
This question already has answers here: Rails: How can I set default values in ActiveRecord? (29 answers) Closed 8 years ago.

I want to create a default value for an attribute by defining it in ActiveRecord. By default everytime the reco开发者_StackOverflowrd is created, I want to have a default value for attribute :status. I tried to do this:

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

But upon creation I still retrieve this error from the database:

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

Therefore I presume the value was not applied to the attribute.

What would be the elegant way to do this in Rails?

Many thanks.


You can set a default option for the column in the migration

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

OR

You can use a 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 better for boolean fields (per @frontendbeauty)
  end
end


Because I encountered this issue just a little while ago, and the options for Rails 3.0 are a bit different, I'll provide another answer to this question.

In Rails 3.0 you want to do something like this:

class MyModel < ActiveRecord::Base
  after_initialize :default_values

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


When I need default values its usually for new records before the new action's view is rendered. The following method will set the default values for only new records so that they are available when rendering forms. before_save and before_create are too late and will not work if you want default values to show up in input fields.

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


You can do it without writing any code at all :) You just need to set the default value for the column in the database. You can do this in your migrations. For example:

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

Hope that helps.


The solution depends on a few things.

Is the default value dependent on other information available at creation time? Can you wipe the database with minimal consequences?

If you answered the first question yes, then you want to use Jim's solution

If you answered the second question yes, then you want to use Daniel's solution

If you answered no to both questions, you're probably better off adding and running a new migration.

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

:string can be replaced with any type that ActiveRecord::Migration recognizes.

CPU is cheap so the redefinition of Task in Jim's solution isn't going to cause many problems. Especially in a production environment. This migration is proper way of doing it as it is loaded it and called much less often.


I would consider using the attr_defaults found here. Your wildest dreams will come true.


Just strengthening Jim's answer

Using presence one can do

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


For column types Rails supports out of the box - like the string in this question - the best approach is to set the column default in the database itself as Daniel Kristensen indicates. Rails will introspect on the DB and initialize the object accordingly. Plus, that makes your DB safe from somebody adding a row outside of your Rails app and forgetting to initialize that column.

For column types Rails doesn't support out of the box - e.g. ENUM columns - Rails won't be able to introspect the column default. For these cases you do not want to use after_initialize (it is called every time an object is loaded from the DB as well as every time an object is created using .new), before_create (because it occurs after validation), or before_save (because it occurs upon update too, which is usually not what you want).

Rather, you want to set the attribute in a before_validation on: create, like so:

before_validation :set_status_because_rails_cannot, on: :create

def set_status_because_rails_cannot
  self.status ||= 'P'
end


As I see it, there are two problems that need addressing when needing a default value.

  1. You need the value present when a new object is initialized. Using after_initialize is not suitable because, as stated, it will be called during calls to #find which will lead to a performance hit.
  2. You need to persist the default value when saved

Here is my 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

I'd love to know why people think of this approach.


I found a better way to do it now:

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

In Ruby a method call is allowed to have no parentheses, therefore I should name the local variable into something else, otherwise Ruby will recognize it as a method call.

0

精彩评论

暂无评论...
验证码 换一张
取 消