lib/active_attr/attribute_defaults.rb
require "active_attr/attributes"
require "active_attr/chainable_initialization"
require "active_support/concern"
require "active_support/core_ext/object/duplicable"
module ActiveAttr
# AttributeDefaults allows defaults to be declared for your attributes
#
# Defaults are declared by passing the :default option to the attribute
# class method. If you need the default to be dynamic, pass a lambda, Proc,
# or any object that responds to #call as the value to the :default option
# and the result will calculated on initialization. These dynamic defaults
# can depend on the values of other attributes when those attributes are
# assigned using MassAssignment or BlockInitialization.
#
# @example Usage
# class Person
# include ActiveAttr::AttributeDefaults
#
# attribute :first_name, :default => "John"
# attribute :last_name, :default => "Doe"
# end
#
# person = Person.new
# person.first_name #=> "John"
# person.last_name #=> "Doe"
#
# @example Dynamic Default
# class Event
# include ActiveAttr::MassAssignment
# include ActiveAttr::AttributeDefaults
#
# attribute :start_date
# attribute :end_date, :default => lambda { start_date }
# end
#
# event = Event.new(:start_date => Date.parse("2012-01-01"))
# event.end_date.to_s #=> "2012-01-01"
#
# @since 0.5.0
module AttributeDefaults
extend ActiveSupport::Concern
include ChainableInitialization
include Attributes
# Applies the attribute defaults
#
# Applies all the default values to any attributes not yet set, avoiding
# any attribute setter logic, such as dirty tracking.
#
# @example Usage
# class Person
# include ActiveAttr::AttributeDefaults
#
# attribute :first_name, :default => "John"
#
# def reset!
# @attributes = {}
# apply_defaults
# end
# end
#
# person = Person.new(:first_name => "Chris")
# person.reset!
# person.first_name #=> "John"
#
# @param [Hash{String => Object}, #each] defaults The defaults to apply
#
# @since 0.5.0
def apply_defaults(defaults=attribute_defaults)
@attributes ||= {}
defaults.each do |name, value|
# instance variable is used here to avoid any dirty tracking in attribute setter methods
@attributes[name] = value unless @attributes.has_key? name
end
end
# Calculates the attribute defaults from the attribute definitions
#
# @example Usage
# class Person
# include ActiveAttr::AttributeDefaults
#
# attribute :first_name, :default => "John"
# end
#
# Person.new.attribute_defaults #=> {"first_name"=>"John"}
#
# @return [Hash{String => Object}] the attribute defaults
#
# @since 0.5.0
def attribute_defaults
attributes_map { |name| _attribute_default name }
end
# Applies attribute default values
#
# @since 0.5.0
def initialize(*)
super
apply_defaults
end
private
# Calculates an attribute default
#
# @private
# @since 0.5.0
def _attribute_default(attribute_name)
default = self.class.attributes[attribute_name][:default]
case
when default.respond_to?(:call) then instance_exec(&default)
when default.duplicable? then default.dup
else default
end
end
end
end