449

How can I set default value in ActiveRecord?

I see a post from Pratik that describes an ugly, complicated chunk of code: 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

I have seen the following examples googling around:

  def initialize 
    super
    self.status = ACTIVE unless self.status
  end

and

  def after_initialize 
    return unless new_record?
    self.status = ACTIVE
  end

I've also seen people put it in their migration, but I'd rather see it defined in the model code.

Is there a canonical way to set default value for fields in ActiveRecord model?

Promise Preston
  • 24,334
  • 12
  • 145
  • 143
ryw
  • 9,375
  • 5
  • 27
  • 34
  • Looks like you answered the question yourself, in two different variants :) – Adam Byrtek Nov 30 '08 at 10:48
  • 26
    Note that the "standard" Ruby idiom for 'self.status = ACTIVE unless self.status' is 'self.status ||= ACTIVE' – Mike Woodhouse Nov 30 '08 at 14:48
  • 1
    Jeff Perrin's answer is much better than the one currently marked as accepted. default_scope is an unacceptable solution for setting default values, because it has the HUGE SIDE EFFECT of also changing the behavior of queries. – Lawrence Mar 11 '11 at 22:34
  • see also http://stackoverflow.com/questions/3975161/rails3-default-scope-and-default-column-value-in-migration – Viktor Trón Jun 14 '12 at 16:26
  • 2
    given all the upvotes to this question, I would say Ruby needs a setDefaultValue method for ActiveRecord – spartikus Dec 17 '14 at 18:34
  • best answer: https://stackoverflow.com/a/41292328/1536309 – Blair Anderson Jun 22 '17 at 00:08

29 Answers29

586

There are several issues with each of the available methods, but I believe that defining an after_initialize callback is the way to go for the following reasons:

  1. default_scope will initialize values for new models, but then that will become the scope on which you find the model. If you just want to initialize some numbers to 0 then this is not what you want.
  2. Defining defaults in your migration also works (in older versions of Rails this will not work when you just call Model.new).
  3. Overriding initialize can work, but don't forget to call super!
  4. Using a plugin like phusion's is getting a bit ridiculous. This is ruby, do we really need a plugin just to initialize some default values?
  5. Overriding after_initialize is deprecated as of Rails 3. When I override after_initialize in rails 3.0.3 I get the following warning in the console:

DEPRECATION WARNING: Base#after_initialize has been deprecated, please use Base.after_initialize :method instead. (called from /Users/me/myapp/app/models/my_model:15)

Therefore I'd say write an after_initialize callback, which lets you default attributes in addition to letting you set defaults on associations like so:

  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    

Now you have just one place to look for initialization of your models. I'm using this method until someone comes up with a better one.

Caveats:

  1. For boolean fields do:

    self.bool_field = true if self.bool_field.nil?

    See Paul Russell's comment on this answer for more details

  2. If you're only selecting a subset of columns for a model (ie; using select in a query like Person.select(:firstname, :lastname).all) you will get a MissingAttributeError if your init method accesses a column that hasn't been included in the select clause. You can guard against this case like so:

    self.number ||= 0.0 if self.has_attribute? :number

    and for a boolean column...

    self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?

    Also note that the syntax is different prior to Rails 3.2 (see Cliff Darling's comment below)

Eliot Sykes
  • 9,616
  • 6
  • 50
  • 64
Jeff Perrin
  • 8,094
  • 1
  • 23
  • 19
  • 7
    This definitely appears to be the best way to achieve this. Which is really odd and unfortunate. A sensible preferred method for establishing model attribute defaults upon creation seems like something Rails should already have built in. The only other (reliable) way, overriding `initialize`, just seems really convoluted for something that should be clear and well defined. I spent hours crawling through the documentation before searching here because I assumed this functionality was already there somewhere and I just wasn't aware of it. – seaneshbaugh Jul 23 '11 at 07:17
  • 117
    One note on this - if you have a boolean field that you want to default, don't do `self.bool_field ||= true`, as this'll force the field to true even if you explicitly initialise it to false. Instead do `self.bool_field = true if self.bool_field.nil?`. – Paul Russell Aug 20 '11 at 20:54
  • 2
    Regarding point #2, Model.new actually works (only for me ?) together with defaults defined in migrations, or more exactly with default values for table columns. But I recognize that Jeff's method based on the after_initialize callback is probably the best way to do. Just a question: does it work with dirty yet unsaved objects ? In your example, will Person.new.number_was return 0.0 ? – Laurent Farcy Apr 03 '12 at 11:54
  • One thing to follow up. Do we need to add `before_save` callback in case someone messed up the initialized data and input some invalid data, or rather use validation and let it fail on `save`? – O.O Aug 01 '12 at 22:52
  • 1
    If someone manually updates a field to an invalid value you should probably let the validation fail on save. – Jeff Perrin Aug 02 '12 at 20:48
  • 22
    Caution when using this approach combined with selecting specific columns with active record. In this case only the attributes specified in the query will be found in the object and the init code will throw a `MissingAttributeError`. You can add an extra check as shown: `self.number ||= 0.0 if self.has_attribute? :number` For booleans: `self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?`. This is Rails 3.2+ - for earlier, use `self.attributes.has_key?`, and you need to a string instead of a symbol. – Cliff Darling Oct 22 '12 at 15:54
  • The boolean example saved me a lot of time. Thank you. – shadowhorst Dec 19 '12 at 15:30
  • 7
    Doing this with associations will eager load those associations on lookup. Start `initialize` with `return if !new_record?` to avoid performance issues. – Kyle Macey Apr 02 '13 at 15:01
  • @KyleMacey Good point, but returning on `!new_record?` may not be wise if you migrate from a legacy db which may have some rows with nil values instead of their default ones due to improper previous migrations. Eventually, it would be better to wrap association defaults into a block of `if new_record?`... – hurikhan77 Jul 30 '13 at 10:44
  • Note: this method cannot be used together with `default:` on the schema, or the schema default will be used always. – Ciro Santilli OurBigBook.com Jan 14 '14 at 21:17
  • 1
    Why not simply use `after_create` filter? In your solution, `init` method will be called each time there is any access to the model. – Wojciech Bednarski Sep 20 '15 at 21:22
  • @WojciechBednarski AFAIK, `after_create` is called after `create` where ActiveRecord talks to the database, so if you update anything in `after_create`, either it is not persisted (maybe this is what you want?), or you need another database query. I would prefer `before_create` for that. – Franklin Yu Sep 23 '16 at 03:59
  • NB: This solution could create odd behavior, ie. doing `Person.where('number IS NULL').count` could return an amount of records and `Person.where('number IS NULL')` would return those records but with their number actually set to `0.0` which is rather counter-intuitive since you would expect them to be `nil`. – Magne May 22 '17 at 13:18
  • 2
    Point 2 no longer applies. I guess the Rails guys implemented it. When defining the `:default` in migrations then`Model.new` will now actually make the object with the default value. Verified working in Rails 4.1.16. – Magne May 22 '17 at 14:28
143

Rails 5+

You can use the attribute method within your models, eg.:

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

You can also pass a lambda to the default parameter. Example:

attribute :uuid, :string, default: -> { SecureRandom.uuid }

The second argument is the type and it can also be a custom type class instance, for example:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
Lucas Caton
  • 3,027
  • 1
  • 24
  • 34
  • 4
    ahhhhh this is the gem i was looking for! default can take a proc too, e.g. default: -> { Time.current.to_date } – schpet Sep 07 '17 at 21:11
  • 2
    Make sure to specify the type as the second argument, otherwise the type will be `Value` and no typecasting will be done. – null Nov 20 '17 at 17:13
  • 1
    to my delight, this also works with store_accessor, for example given `store_accessor :my_jsonb_column, :locale` you can then define `attribute :locale, :string, default: 'en'` – Ryan Romanchuk Aug 30 '19 at 22:40
  • Oh that's fantastic, I needed defaults to show in a form and this works great. Thanks Lucas. – Paul Watson Feb 27 '20 at 07:34
  • One can still set these to `nil`. If they cannot be `nil` DB `not null` + DB default + https://github.com/sshaw/keep_defaults are the way to go from my experience – sshaw Mar 03 '20 at 06:51
  • Using this attribute method, is there still any case needed to add default values to the migration, too? – Aboozar Rajabi Jul 09 '20 at 11:39
  • 2
    Whereas this is assume, if you have existing objects in the database that were created before this code was added (for example, if you are adding a new column that you want the default on) they will NOT get this value when you load them in. But, if you use the `after_initialize` method they will – phil Oct 20 '20 at 12:13
50

We put the default values in the database through migrations (by specifying the :default option on each column definition) and let Active Record use these values to set the default for each attribute.

IMHO, this approach is aligned with the principles of AR : convention over configuration, DRY, the table definition drives the model, not the other way around.

Note that the defaults are still in the application (Ruby) code, though not in the model but in the migration(s).

Laurent Farcy
  • 710
  • 4
  • 9
  • 3
    Another problem is when you want a default value for a foreign key. You can't hard-code an ID value into the foreign key field because on different DBs the ID might be different. – shmichael Aug 30 '10 at 09:50
  • 2
    yet another problem is, that this way you cannot initialize non-persistent accessors (attributes that are not db columns). – Viktor Trón May 13 '11 at 11:20
  • 2
    another problem is that you can't necessarily see all the default values in one place. they might be scattered through different migrations. – declan Jun 23 '11 at 16:18
  • 9
    declan, there's db/schema.rb – Benjamin Atkin Oct 31 '11 at 18:19
  • 7
    I would like to mention for future readers: at least from what I have read, this is contrary to the principles of AR. The logic for the models should rest within the model classes, and the databases should be as ignorant as possible. Default values to me constitutes specific logic about a model. – darethas Dec 07 '13 at 19:12
  • another problem is timing when you want to add new field with default values to the table which already runs over 1 million of records, i.e. updating existing records will take awhile... – wik Aug 22 '17 at 23:39
  • 1
    @wik I agree that it will take a while but what about the unforeseen consequences of not properly initializing your new columns for all the existing records? I usually manipulate the records in the database with some plain-SQL queries, especially in read-mode (SELECT). For debugging or reporting purposes. If the default value stays at the level of Ruby model, I won't get the right result. I feel like, whatever initialization technique you use, you should care about properly initializing existing records in the db when you introduce a new column. – Laurent Farcy Sep 04 '17 at 13:49
  • The case pointed by wik is just a theoretical scenario if you play exactly and strictly by the rules in a dumb way. Real world, you run the migration in dev, test env quickly as they shouldn't have millions of records. And in stag/prod you run a plan savvy-SQL script with nolocks and on maintenance window. Even so, such update shouldn't be so time costly. – Andre Figueiredo Jul 11 '19 at 21:49
  • For static default values this works, but one problem is when you want to use something like `now()`. AR automatically pulls static defaults in as values on new records, but not db function calls. `Check.new.number # => 0`. `Check.new.timestamp # => nil`. – lobati Feb 11 '21 at 19:12
42

Some simple cases can be handled by defining a default in the database schema but that doesn't handle a number of trickier cases including calculated values and keys of other models. For these cases I do this:

after_initialize :defaults

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

I've decided to use the after_initialize but I don't want it to be applied to objects that are found only those new or created. I think it is almost shocking that an after_new callback isn't provided for this obvious use case but I've made do by confirming whether the object is already persisted indicating that it isn't new.

Having seen Brad Murray's answer this is even cleaner if the condition is moved to callback request:

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
Joseph Lord
  • 6,446
  • 1
  • 28
  • 32
  • 4
    This is a really important point. I have to imagine that in most cases setting the default on a record is only to be done before persisting a new record, not when loading a persisted record. – Russell Silva Dec 17 '13 at 16:38
  • 2
    How about `:before_create`? – Franklin Yu Sep 23 '16 at 01:58
  • How does :before_create handle separate new and save calls? I would want to check that out and really understand before switching to it. – Joseph Lord Oct 05 '16 at 15:28
19

The after_initialize callback pattern can be improved by simply doing the following

after_initialize :some_method_goes_here, :if => :new_record?

This has a non-trivial benefit if your init code needs to deal with associations, as the following code triggers a subtle n+1 if you read the initial record without including the associated.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
Brad Murray
  • 419
  • 4
  • 9
16

The Phusion guys have some nice plugin for this.

Milan Novota
  • 15,506
  • 7
  • 54
  • 62
  • Note, this plugin allows the `:default` values in schema migrations to 'just work' with `Model.new`. – jchook Feb 02 '14 at 16:58
  • I can get the `:default` values in migrations to 'just work' with `Model.new`, contrary to what Jeff said in his post. Verified working in Rails 4.1.16. – Magne May 22 '17 at 14:25
8

I use the attribute-defaults gem

From the documentation: run sudo gem install attribute-defaults and add require 'attribute_defaults' to your app.

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"
aidan
  • 9,310
  • 8
  • 68
  • 82
8

An even better/cleaner potential way than the answers proposed is to overwrite the accessor, like this:

def status
  self['status'] || ACTIVE
end

See "Overwriting default accessors" in the ActiveRecord::Base documentation and more from StackOverflow on using self.

Community
  • 1
  • 1
peterhurford
  • 1,074
  • 1
  • 13
  • 21
  • Status will still be nil in the hash returned from `attributes`. Tested in rails 5.2.0. – spyle Jun 20 '18 at 02:54
8

Similar questions, but all have slightly different context: - How do I create a default value for attributes in Rails activerecord's model?

Best Answer: Depends on What You Want!

If you want every object to start with a value: use after_initialize :init

You want the new.html form to have a default value upon opening the page? use https://stackoverflow.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 

If you want every object to have a value calculated from user input: use before_save :default_values You want user to enter X and then Y = X+'foo'? use:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end
Community
  • 1
  • 1
Blair Anderson
  • 19,463
  • 8
  • 77
  • 114
8

Rails 6.1+

You can now use the attribute method on your model without setting a type.

attribute :status, default: ACTIVE

or

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

Note that feeding a default to attribute cannot reference the instance of the class (a lambda will execute in the context of the class, not the instance). So, if you need to set the default to a value dynamically based on the instance or associations, you're still going to have to use an alternative, such as an after_initialize callback. As stated previously, it's recommended to limit this to new records only to avoid n+1 queries if you reference associations.

after_initialize :do_something_that_references_instance_or_associations, if: :new_record?
staxim
  • 1,328
  • 14
  • 15
5

I've also seen people put it in their migration, but I'd rather see it defined in the model code.

Is there a canonical way to set default value for fields in ActiveRecord model?

The canonical Rails way, before Rails 5, was actually to set it in the migration, and just look in the db/schema.rb for whenever wanting to see what default values are being set by the DB for any model.

Contrary to what @Jeff Perrin answer states (which is a bit old), the migration approach will even apply the default when using Model.new, due to some Rails magic. Verified working in Rails 4.1.16.

The simplest thing is often the best. Less knowledge debt and potential points of confusion in the codebase. And it 'just works'.

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

Or, for column change without creating a new one, then do either:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

Or perhaps even better:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

Check the official RoR guide for options in column change methods.

The null: false disallows NULL values in the DB, and, as an added benefit, it also updates so that all pre-existing DB records that were previously null is set with the default value for this field as well. You may exclude this parameter in the migration if you wish, but I found it very handy!

The canonical way in Rails 5+ is, as @Lucas Caton said:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end
Magne
  • 16,401
  • 10
  • 68
  • 88
4

This is what constructors are for! Override the model's initialize method.

Use the after_initialize method.

John Topley
  • 113,588
  • 46
  • 195
  • 237
  • 2
    Normally you'd be correct but you should never override initialize in an ActiveRecord model as it might not always be called. You should use the `after_initialize` method instead. – Luke Redpath Nov 13 '09 at 00:34
  • Using a default_scope just to set a default is CERTAINLY wrong. after_initialize is the correct answer. – joaomilho Mar 11 '11 at 11:30
4

Sup guys, I ended up doing the following:

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

Works like a charm!

Tony
  • 49
  • 1
3

This has been answered for a long time, but I need default values frequently and prefer not to put them in the database. I create a DefaultValues concern:

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

And then use it in my models like so:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end
clem
  • 3,524
  • 3
  • 25
  • 41
2

I ran into problems with after_initialize giving ActiveModel::MissingAttributeError errors when doing complex finds:

eg:

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

"search" in the .where is hash of conditions

So I ended up doing it by overriding initialize in this way:

def initialize
  super
  default_values
end

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

The super call is necessary to make sure the object initializing correctly from ActiveRecord::Base before doing my customize code, ie: default_values

Juan Sosa
  • 5,262
  • 1
  • 35
  • 41
Sean
  • 21
  • 1
  • 1
    I like it. I needed to do `def initialize(*); super; default_values; end` in Rails 5.2.0. Plus, the default value is available, even in the `.attributes` hash. – spyle Jun 20 '18 at 03:00
1

The problem with the after_initialize solutions is that you have to add an after_initialize to every single object you look up out of the DB, regardless of whether you access this attribute or not. I suggest a lazy-loaded approach.

The attribute methods (getters) are of course methods themselves, so you can override them and provide a default. Something like:

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

Unless, like someone pointed out, you need to do Foo.find_by_status('ACTIVE'). In that case I think you'd really need to set the default in your database constraints, if the DB supports it.

Jeff Gran
  • 1,585
  • 15
  • 17
  • This solution and the proposed alternative don't work in my case: I have an STI class hierarchy where only one class has that attribute, and the correspondingcolumn that will be used in DB query conditions. – cmoran92 Mar 20 '19 at 14:00
1

I strongly suggest using the "default_value_for" gem: https://github.com/FooBarWidget/default_value_for

There are some tricky scenarios that pretty much require overriding the initialize method, which that gem does.

Examples:

Your db default is NULL, your model/ruby-defined default is "some string", but you actually want to set the value to nil for whatever reason: MyModel.new(my_attr: nil)

Most solutions here will fail to set the value to nil, and will instead set it to the default.

OK, so instead of taking the ||= approach, you switch to my_attr_changed?...

BUT now imagine your db default is "some string", your model/ruby-defined default is "some other string", but under a certain scenario, you want to set the value to "some string" (the db default): MyModel.new(my_attr: 'some_string')

This will result in my_attr_changed? being false because the value matches the db default, which in turn will fire your ruby-defined default code and set the value to "some other string" -- again, not what you desired.


For those reasons I don't think this can properly be accomplished with just an after_initialize hook.

Again, I think the "default_value_for" gem is taking the right approach: https://github.com/FooBarWidget/default_value_for

etipton
  • 164
  • 1
  • 5
1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
bbozo
  • 7,075
  • 3
  • 30
  • 56
Mike Breen
  • 2,610
  • 1
  • 19
  • 13
  • 2
    Mmmhh... seems ingenious at first, but after thinking a bit, I see a few problems. First, all the default values aren't in a single point, but scattered through the class (imagine searching for them or changing them). Second and worst, yo cannot put, later on, a null value (or even a false one!). – paradoja Nov 30 '08 at 14:22
  • why would you need to set a null value as default? you get that out of the box with AR without doing anything at all. As for false when using a boolean column then you're right, this is not the best approach. – Mike Breen Nov 30 '08 at 14:44
  • I can't speak for others coding habits I haven't had an issue because I don't scatter my getters/setters around a class file. Also, any modern text editor should make it easy to navigate to a method (shift-cmd-t in textmate). – Mike Breen Nov 30 '08 at 14:48
  • @paradoja - I take that back, I now see where it breaks down with using null also. Not necessarily using null as the default but if you actually wanted to change the value to null at some point. Good catch @paradoja, thanks. – Mike Breen Nov 30 '08 at 14:59
  • I use this method since it works well with dynamically generated attributes. – Dale Campbell Nov 02 '10 at 19:35
1

after_initialize method is deprecated, use the callback instead.

after_initialize :defaults

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

however, using :default in your migrations is still the cleanest way.

Greg
  • 4,509
  • 2
  • 29
  • 22
  • 4
    In Rails 3: **`after_initialize` method** is **NOT deprecated**. In fact, the **macro-style callback** you give an example of **IS deprecated**. Details: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find – Zabba Dec 28 '10 at 06:42
0

If the column happens to be a 'status' type column, and your model lends itself to the use of state machines, consider using the aasm gem, after which you can simply do

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

It still doesn't initialize the value for unsaved records, but it's a bit cleaner than rolling your own with init or whatever, and you reap the other benefits of aasm such as scopes for all your statuses.

Bad Request
  • 3,990
  • 5
  • 33
  • 37
0

Although doing that for setting default values is confusing and awkward in most cases, you can use :default_scope as well. Check out squil's comment here.

skalee
  • 12,331
  • 6
  • 55
  • 57
0

https://github.com/keithrowell/rails_default_value

class Task < ActiveRecord::Base
  default :status => 'active'
end
0

Here's a solution I've used that I was a little surprised hasn't been added yet.

There are two parts to it. First part is setting the default in the actual migration, and the second part is adding a validation in the model ensuring that the presence is true.

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

So you'll see here that the default is already set. Now in the validation you want to ensure that there is always a value for the string, so just do

 validates :new_team_signature, presence: true

What this will do is set the default value for you. (for me I have "Welcome to the Team"), and then it will go one step further an ensure that there always is a value present for that object.

Hope that helps!

kdweber89
  • 1,984
  • 2
  • 19
  • 29
0

I've found that using a validation method provides a lot of control over setting defaults. You can even set defaults (or fail validation) for updates. You even set a different default value for inserts vs updates if you really wanted to. Note that the default won't be set until #valid? is called.

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

Regarding defining an after_initialize method, there could be performance issues because after_initialize is also called by each object returned by :find : http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find

Kelvin
  • 20,119
  • 3
  • 60
  • 68
  • doesn't validation only happen before save? What if you wanted to show defaults before saving ? – nurettin Feb 18 '13 at 11:30
  • @nurettin That's a good point and I can see why you'd want that sometimes, but the OP didn't mention this as a requirement. You have to decide for yourself whether you want the overhead of setting the defaults on every instance, even if it's not saved. The alternative is to keep a dummy object around for the `new` action to reuse. – Kelvin Feb 19 '13 at 17:18
0
# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

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

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
shilovk
  • 11,718
  • 17
  • 75
  • 74
0

I had a similar challenge when working on a Rails 6 application.

Here's how I solved it:

I have a Users table and a Roles table. The Users table belongs to the Roles table. I also have an Admin and Student Models that inherit from the Users table.

It then required that I set a default value for the role whenever a user is created, say admin role that has an id = 1 or student role that has an id = 2.

class User::Admin < User
  before_save :default_values

  def default_values
    # set role_id to '1' except if role_id is not empty
    return self.role_id = '1' unless role_id.nil?
  end
end

This means that before an admin user is created/saved in the database the role_id is set to a default of 1 if it is not empty.

return self.role_id = '1' unless role_id.nil? 

is the same as:

return self.role_id = '1' unless self.role_id.nil?

and the same as:

self.role_id = '1' if role_id.nil?

but the first one is cleaner and more precise.

That's all.

I hope this helps

Promise Preston
  • 24,334
  • 12
  • 145
  • 143
0

Been using this for a while.

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: 'ID'
end
Yana Agun Siswanto
  • 1,932
  • 18
  • 30
-2

From the api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Use the before_validation method in your model, it gives you the options of creating specific initialisation for create and update calls e.g. in this example (again code taken from the api docs example) the number field is initialised for a credit card. You can easily adapt this to set whatever values you want

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

Surprised that his has not been suggested here

jamesc
  • 12,423
  • 15
  • 74
  • 113
  • before_validation will not set the defaults until the object is ready to be persisted. If the process needs to read the defaults before persisting then the values won't be ready. – mmell Aug 31 '12 at 16:10
  • You never set default values during validations check anyway. It's not even any Hack. Do it during initialisation – Sachin Feb 07 '17 at 04:49
-2

use default_scope in rails 3

api doc

ActiveRecord obscures the difference between defaulting defined in the database (schema) and defaulting done in the application (model). During initialization, it parses the database schema and notes any default values specified there. Later, when creating objects, it assigns those schema-specified default values without touching the database.

discussion

Community
  • 1
  • 1
Viktor Trón
  • 8,774
  • 4
  • 45
  • 48
  • if you use meta_where, default_scope may not work for assigning defaults to new AR objects due to a bug. – Viktor Trón Mar 01 '11 at 20:56
  • this meta_where issue has now been fixed [http://metautonomous.lighthouseapp.com/projects/53011/tickets/36-default_scope-does-not-work] – Viktor Trón Mar 02 '11 at 22:23
  • 3
    do NOT use `default_scope`. This will make all your queries add this condition to the field you've set. It's almost NEVER what you want. – brad Jun 14 '12 at 12:28
  • @brad, funny you mention, I totally agree, it is evil :). see my comment in http://stackoverflow.com/questions/10680845/owner-filteder-model-objects-on-rails-3. – Viktor Trón Jun 14 '12 at 16:26