Default Value For plugin

Plugin details

Provides a way to specify default values for ActiveRecord models

Websitehttp://github.com/FooBarWidget/default_value_for/tree/master Repositorygit://github.com/FooBarWidget/default_value_for.git Author Hongli Lai Tags default LicenseUnknown

Documentation

Install the plugin:
ruby script/plugin install git://github.com/FooBarWidget/default_value_for.git

Introduction
================

The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner. For example:

  class User < ActiveRecord::Base
    default_value_for :name, "(no name)"
    default_value_for :last_seen do
      Time.now
    end
  end

  u = User.new
  u.name       # => "(no name)"
  u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008


Note: critics might be interested in the "When (not) to use default_value_for?" section. Please read on.

The default_value_for method
-----------------------------------------
The default_value_for method is available in all ActiveRecord model classes.

The first argument is the name of the attribute for which a default value should be set. This may either be a Symbol or a String.

The default value itself may either be passed as the second argument:

  default_value_for :age, 20


…or it may be passed as the return value of a block:

  default_value_for :age do
    if today_is_sunday?
      20
    else
      30
    end
  end


If you pass a value argument, then the default value is static and never changes. However, if you pass a block, then the default value is retrieved by calling the block. This block is called not once, but every time a new record is instantiated and default values need to be filled in.

The latter form is especially useful if your model has a UUID column. One can generate a new, random UUID for every newly instantiated record: 
  class User < ActiveRecord::Base
    default_value_for :uuid do
      UuidGenerator.new.generate_uuid
    end
  end

  User.new.uuid  # => "51d6d6846f1d1b5c9a...."
  User.new.uuid  # => "ede292289e3484cb88...."


Note that record is passed to the block as an argument, in case you need it for whatever reason:

  class User < ActiveRecord::Base
    default_value_for :uuid do |x|
      x   #  "529c91b8bbd3e..."

  user = User.find(user.id)
  # UUID remains unchanged because it's retrieved from the database!
  user.uuid   # => "529c91b8bbd3e..."


Mass-assignment
-------------------------------------
If a certain attribute is being assigned via the model constructor’s mass-assignment argument, that the default value for that attribute will not be filled in:

  user = User.new(:uuid => "hello")
  user.uuid   # => "hello"


However, if that attribute is protected by attr_protected or attr_accessible, then it will be filled in:

  class User < ActiveRecord::Base
    default_value_for :name, 'Joe'
    attr_protected :name
  end

  user = User.new(:name => "Jane")
  user.name   # => "Joe"


Inheritance
-------------------------------------
Inheritance works as expected. All default values are inherited by the child class:

  class User < ActiveRecord::Base
    default_value_for :name, 'Joe'
  end

  class SuperUser < User
  end

  SuperUser.new.name   # => "Joe"


Attributes that aren’t database columns
----------------------------------------
default_value_for also works with attributes that aren’t database columns. It works with anything for which there’s an assignment method:

  # Suppose that your 'users' table only has a 'name' column.
  class User < ActiveRecord::Base
    default_value_for :name, 'Joe'
    default_value_for :age, 20
    default_value_for :registering, true

    attr_accessor :age

    def registering=(value)
      @registering = true
    end
  end

  user = User.new
  user.age    # => 20
  user.instance_variable_get('@registering')    # => true


Caveats
------------------------------------------
A conflict can occur if your model class overrides the ‘initialize’ method, because this plugin overrides ‘initialize’ as well to do its job.

  class User < ActiveRecord::Base
    def initialize  #  'Name cannot be changed in constructor')
    end
  end


We recommend you to alias chain your initialize method in models where you use default_value_for:

  class User < ActiveRecord::Base
    default_value_for :age, 20

    def initialize_with_my_app
      initialize_without_my_app(:name => 'Name cannot be changed in constructor')
    end

    alias_method_chain :initialize, :my_app
  end


Also, stick with the following rules:
There is no need to alias_method_chain your initialize method in models that don’t use default_value_for.
Make sure that alias_method_chain is called after the last default_value_for occurance.


When (not) to use default_value_for?
===============================

You can also specify default values in the database schema. For example, you can specify a default value in a migration as follows:

  create_table :users do |t|
    t.string    :username,  :null => false, :default => 'default username'
    t.integer   :age,       :null => false, :default => 20
    t.timestamp :last_seen, :null => false, :default => Time.now
  end


This has the same effect as passing the default value as the second argument to default_value_for:

  user = User.new
  user.username   # => 'default username'
  user.age        # => 20
  user.timestamp  # => Mon Sep 22 18:31:47 +0200 2008


It’s recommended that you use this over default_value_for whenever possible.

However, it’s not possible to specify a schema default for serialized columns. With default_value_for, you can:

  class User < ActiveRecord::Base
    serialize :color
    default_value_for :color, [255, 0, 0]
  end


And if schema defaults don’t provide the flexibility that you need, then default_value_for is the perfect choice. For example, with default_value_for you could specify a per-environment default:

  class User < ActiveRecord::Base
    if RAILS_ENV == "development"
      default_value_for :is_admin, true
    end
  end


Or, as you’ve seen in an earlier example, you can use default_value_for to generate a default random UUID:

  class User < ActiveRecord::Base
    default_value_for :uuid do
      UuidGenerator.new.generate_uuid
    end
  end


Or you could use it to generate a timestamp that’s relative to the time at which the record is instantiated:

  class User < ActiveRecord::Base
    default_value_for :account_expires_at do
      3.years.from_now
    end
  end

  User.new.account_expires_at   # => Mon Sep 22 18:43:42 +0200 2008
  sleep(2)
  User.new.account_expires_at   # => Mon Sep 22 18:43:44 +0200 2008


Finally, it’s also possible to specify a default via an association:

  # Has columns: 'name' and 'default_price'
  class SuperMarket < ActiveRecord::Base
    has_many :products
  end

  # Has columns: 'name' and 'price'
  class Product < ActiveRecord::Base
    belongs_to :super_market

    default_value_for :price do |product|
      product.super_market.default_price
    end
  end

  super_market = SuperMarket.create(:name => 'Albert Zwijn', :default_price => 100)
  soap = super_market.products.create(:name => 'Soap')
  soap.price   # => 100



What about before_validate/before_save?
-------------------------------------------

True, before_validate and before_save does what we want if we’re only interested in filling in a default before saving. However, if one wants to be able to access the default value even before saving, then be prepared to write a lot of code. Suppose that we want to be able to access a new record’s UUID, even before it’s saved. We could end up with the following code:

  # In the controller
  def create
    @user = User.new(params[:user])
    @user.generate_uuid
    email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
    @user.save!
  end

  # Model
  class User < ActiveRecord::Base
    before_save :generate_uuid_if_necessary

    def generate_uuid
      self.uuid = ...
    end

    private
      def generate_uuid_if_necessary
        if uuid.blank?
          generate_uuid
        end
      end
  end


The need to manually call generate_uuid here is ugly, and one can easily forget to do that. Can we do better? Let’s see:

  # Controller
  def create
    @user = User.new(params[:user])
    email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
    @user.save!
  end

  # Model
  class User < ActiveRecord::Base
    before_save :generate_uuid_if_necessary

    def uuid
      value = read_attribute('uuid')
      if !value
        value = generate_uuid
        write_attribute('uuid', value)
      end
      value
    end

    # We need to override this too, otherwise User.new.attributes won't return
    # a default UUID value. I've never tested with User.create() so maybe we
    # need to override even more things.
    def attributes
      uuid
      super
    end

    private
      def generate_uuid_if_necessary
        uuid  # Reader method automatically generates UUID if it doesn't exist
      end
  end


That’s an awful lot of code. Using default_value_for is easier, don’t you think?

Further Documentation

There is currently no advanced documentation for this plugin.

New documentation

Edit plugin | (0 older versions) | Last edited by: hardway, about 1 month ago