Default Value For plugin
Plugin details
Documentation
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 documentationEdit plugin | (0 older versions) | Last edited by: hardway, about 1 month ago

