module ActiveRecord module Defaults def self.included(base) return if base.included_modules.include?(ActiveRecord::Defaults::InstanceMethods) base.extend ClassMethods base.send(:include, InstanceMethods) base.send :alias_method, :initialize_without_defaults, :initialize base.send :alias_method, :initialize, :initialize_with_defaults end module ClassMethods # Define default values for attributes on new records. Requires a hash of attribute => value pairs, or a single attribute with an associated block. # If the value is a block, it will be called to retrieve the default value. # If the value is a symbol, a method by that name will be called on the object to retrieve the default value. # # The following code demonstrates the different ways default values can be specified. Defaults are applied in the order they are defined. # # class Person < ActiveRecord::Base # defaults :name => 'My name', :city => lambda { 'My city' } # # default :birthdate do |person| # Date.today if person.wants_birthday_today? # end # # default :favourite_colour => :default_favourite_colour # # def default_favourite_colour # "Blue" # end # end # # The defaults and the default methods behave the same way. Use whichever is appropriate. # # The default values are only used if the key is not present in the given attributes. # # p = Person.new # p.name # "My name" # p.city # "My city" # # p = Person.new(:name => nil) # p.name # nil # p.city # "My city" # # == Default values for belongs_to associations # # Default values can also be specified for an association. For instance: # # class Student < ActiveRecord::Base # belongs_to :school # default :school => lambda { School.new } # end # # In this scenario, if a school_id was provided in the attributes hash, the default value for the association will be ignored: # # s = Student.new # s.school # => # # # s = Student.new(:school_id => nil) # s.school # => nil # # Similarly, if a default value is specified for the foreign key and an object for the association is provided, the default foreign key is ignored. def defaults(defaults, &block) default_objects = case when defaults.is_a?(Hash) defaults.map { |attribute, value| Default.new(attribute, value) } when defaults.is_a?(Symbol) && block Default.new(defaults, block) else raise "pass either a hash of attribute/value pairs, or a single attribute with a block" end write_inheritable_array :attribute_defaults, [*default_objects] end alias_method :default, :defaults end module InstanceMethods def initialize_with_defaults(attributes = nil) initialize_without_defaults(attributes) apply_default_attribute_values(attributes) yield self if block_given? end def apply_default_attribute_values(attributes) attribute_keys = (attributes || {}).keys.map!(&:to_s) if attribute_defaults = self.class.read_inheritable_attribute(:attribute_defaults) attribute_defaults.each do |default| next if attribute_keys.include?(default.attribute) # Ignore a default value for association_id if association has been specified reflection = self.class.reflections[default.attribute.to_sym] if reflection and reflection.macro == :belongs_to and attribute_keys.include?(reflection.primary_key_name) next end # Ignore a default value for association if association_id has been specified reflection = self.class.reflections.values.find { |r| r.respond_to?(:primary_key_name) && r.primary_key_name == default.attribute } if reflection and reflection.macro == :belongs_to and attribute_keys.include?(reflection.name.to_s) next end send("#{default.attribute}=", default.value(self)) end end end end class Default attr_reader :attribute def initialize(attribute, value) @attribute, @value = attribute.to_s, value end def value(record) if @value.is_a?(Symbol) record.send(@value) elsif @value.respond_to?(:call) @value.call(record) else @value end end end end end class ActiveRecord::Base include ActiveRecord::Defaults end