18

Is it better to set default value in migration or in callback? It's difficult to delete (or set another) default value in migration, but in model it one more piece of code

0

4 Answers 4

33

Defining defaults in your migration has some disadvantages as well. This will not work when you just call Model.new.

I prefer to write an after_initialize callback, which lets me set default attributes:

class Model < ActiveRecord::Base
  after_initialize :set_defaults, unless: :persisted?
  # The set_defaults will only work if the object is new

  def set_defaults
    self.attribute  ||= 'some value'
    self.bool_field = true if self.bool_field.nil?
  end
end 
Sign up to request clarification or add additional context in comments.

5 Comments

Why not before_create? then you don't need to check :persisted?
@WojciechBednarski, because before_create will override existing value and we do not want to set default value if the object already has values. This is why we are setting default value only when the object is new and not persisted.
before_create will fire up only once in the lifetime of the model in DB row. before_update will fire up every time you alter the model in DB.
@SharvyAhmed thank you sir i noticed that your answer is validating from the model. one can also set the value directly from the create controller. what do you think of that approach?
@SharvyAhmed But using defaults in the migration does work with Model.new
29

In Rails 5, the attributes API allows specification of default values. The syntax is simple and allows you to change a default without a migration.

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

2 Comments

It's worth noting that Rails Attributes API will allow "", an empty string, with this the model as is.
It's great. The good point is that it also accepts proc! Is there any case that it needs to be added to the migration, too?
3

Piggy-backing on @w-hile answer — which is a great one, unless you have a bunch of things you want to make "settings" —

I grew sick and tired of polluting my models with a bunch of columns devoted to settings, so I wrote Setsy.

class User < ApplicationRecord

  include ::Setsy::DSL

  # each setting can be a hash, like posts_limit, or just a value
  DEFAULT_SETTINGS = {
    posts_limit: { value: 10 },
    favorite_color: 'blue'
  }.freeze

  setsy :settings, column: :settings_data, defaults: DEFAULT_SETTINGS do |conf|
    conf.reader :posts_limit_and_color do
      "posts limit is #{posts_limit} and color is #{favorite_color}"
    end
  end
end

And you can,

user = User.first
user.settings # {posts_limit: 10, favorite_color: 'blue'}
user.settings.posts_limit # 10
user.settings.posts_limit.default? # true 
user.assign_attributes(settings_data: { posts_limit: 15, favorite_color: 'blue' })
user.settings.posts_limit.default? # false 
user.settings.posts_limit.default # 10 

Comments

1

As a general rule, on the backend, enforce constraints in models and in the DB. It's like validation JS and not validating in the backend side (PHP, ROR, etc). Someone can modify your JS to pass the validation and since you didn't validate on the backend, your site may be compromised. Thus, validate always in both sides, at least if your app server gets compromise, the DB server may put up some defense.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.