Rails: ActiveRecord default values
ActiveRecord is an amazing peice of software but one thing it lacks is an easy
mechanism for setting column defaults outside of the database and virtually no
mechanism for setting defaults via database functions. Basically there is no
easy way to do this
class Model < ActiveRecord::Base
defaults ‘time’ => ‘now()’
end
defaults ‘time’ => ‘now()’
end
The first thing we need is a generic mechanism for setting defaults. This
does nicely:
class ActiveRecord::Base
alias_method ‘__initialize__’, ‘initialize’
def initialize options = nil, &block
returning( __initialize__(options, &block) ) do
options ||= {}
options.to_options!
defaults = self.class.defaults || self.defaults || Hash.new
(defaults.keys - options.keys).each do |key|
value = defaults[key]
case value
when Proc
value = instance_eval &value
when Symbol
value = send value
end
send “#{ key }=”, value
end
end
end
def self.defaults *argv
@defaults = argv.shift.to_hash if argv.first
return @defaults if defined? @defaults
end
def defaults *argv
@defaults = argv.shift.to_hash if argv.first
return @defaults if defined? @defaults
end
end
alias_method ‘__initialize__’, ‘initialize’
def initialize options = nil, &block
returning( __initialize__(options, &block) ) do
options ||= {}
options.to_options!
defaults = self.class.defaults || self.defaults || Hash.new
(defaults.keys - options.keys).each do |key|
value = defaults[key]
case value
when Proc
value = instance_eval &value
when Symbol
value = send value
end
send “#{ key }=”, value
end
end
end
def self.defaults *argv
@defaults = argv.shift.to_hash if argv.first
return @defaults if defined? @defaults
end
def defaults *argv
@defaults = argv.shift.to_hash if argv.first
return @defaults if defined? @defaults
end
end
Now we can do this
class Model < ActiveRecord::Base
defaults ‘foo’ => 42
end
defaults ‘foo’ => 42
end
and this
class Model < ActiveRecord::Base
defaults ‘foo’ => lambda{ 42 }
end
defaults ‘foo’ => lambda{ 42 }
end
and this
class Model < ActiveRecord::Base
defaults ‘foo’ => :bar
def bar() 42 end
end
defaults ‘foo’ => :bar
def bar() 42 end
end
and this
class Model < ActiveRecord::Base
defaults ‘foo’ => lambda{ bar }
def bar() 42 end
end
defaults ‘foo’ => lambda{ bar }
def bar() 42 end
end
now, sprinkling in a little abuse of the rails’ source we do
class ActiveRecord::Base
module UnQuoted
def quoted_id() self end ### hackity hack, don’t talk back
end
DEFAULT = ‘DEFAULT’
DEFAULT.extend UnQuoted
DEFAULT.freeze
def pgsql(*argv, &block)
string = argv.join(’ ’)
string += “ #{ block.call }” if block
string.extend UnQuoted
string
end
def self.nextval seq
connection = ActiveRecord::Base.connection
lambda{
Integer(connection.execute(”select nextval(‘#{ seq }’)”)[0][0])
}
end
def nextval *a, &b
self.class.nextval *a, &b
end
end
module UnQuoted
def quoted_id() self end ### hackity hack, don’t talk back
end
DEFAULT = ‘DEFAULT’
DEFAULT.extend UnQuoted
DEFAULT.freeze
def pgsql(*argv, &block)
string = argv.join(’ ’)
string += “ #{ block.call }” if block
string.extend UnQuoted
string
end
def self.nextval seq
connection = ActiveRecord::Base.connection
lambda{
Integer(connection.execute(”select nextval(‘#{ seq }’)”)[0][0])
}
end
def nextval *a, &b
self.class.nextval *a, &b
end
end
and we’re all good to do things like
class Model < ActiveRecord::Base
### use the db default, even function
defaults ‘time’ => DEFAULT
end
and
### use the db default, even function
defaults ‘time’ => DEFAULT
end
class Model < ActiveRecord::Base
### use arbitrary sql for default value
defaults ‘time’ => pgsql(’now()’)
end
### use arbitrary sql for default value
defaults ‘time’ => pgsql(’now()’)
end
and
class Model < ActiveRecord::Base
### yank a value out of a sequence as the default
defaults ‘voter_id’ => nextval(’voter_id_seq’)
end
### yank a value out of a sequence as the default
defaults ‘voter_id’ => nextval(’voter_id_seq’)
end
All in all a lot of bang for not much code. Enjoy.