# Copyright (c) 2008-2012 Phusion # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module DefaultValueFor class NormalValueContainer def initialize(value) @value = value end def evaluate(instance) if @value.duplicable? return @value.dup else return @value end end end class BlockValueContainer def initialize(block) @block = block end def evaluate(instance) if @block.arity == 0 return @block.call else return @block.call(instance) end end end module ClassMethods # Declares a default value for the given attribute. # # Sets the default value to the given options parameter unless the given options equal { :value => ... } # # The options can be used to specify the following things: # * value - Sets the default value. # * allows_nil (default: true) - Sets explicitly passed nil values if option is set to true. def default_value_for(attribute, options = {}, &block) value = options.is_a?(Hash) && options.stringify_keys.has_key?('value') ? options.stringify_keys['value'] : options allows_nil = options.is_a?(Hash) && options.stringify_keys.has_key?('allows_nil') ? options.stringify_keys['allows_nil'] : true if !method_defined?(:set_default_values) include(InstanceMethods) after_initialize :set_default_values if respond_to?(:class_attribute) class_attribute :_default_attribute_values class_attribute :_default_attribute_values_not_allowing_nil else class_inheritable_accessor :_default_attribute_values class_inheritable_accessor :_default_attribute_values_not_allowing_nil end extend(DelayedClassMethods) init_hash = true else methods = singleton_methods(false) init_hash = !methods.include?("_default_attribute_values") && !methods.include?(:_default_attribute_values) end if init_hash self._default_attribute_values = ActiveSupport::OrderedHash.new self._default_attribute_values_not_allowing_nil = [] end if block_given? container = BlockValueContainer.new(block) else container = NormalValueContainer.new(value) end _default_attribute_values[attribute.to_s] = container _default_attribute_values_not_allowing_nil << attribute.to_s unless allows_nil end def default_values(values) values.each_pair do |key, options| options = options.stringify_keys if options.is_a?(Hash) value = options.is_a?(Hash) && options.has_key?('value') ? options['value'] : options if value.kind_of? Proc default_value_for(key, options.is_a?(Hash) ? options : {}, &value) else default_value_for(key, options) end end end end module DelayedClassMethods def _all_default_attribute_values return _default_attribute_values unless superclass.respond_to?(:_default_attribute_values) superclass._all_default_attribute_values.merge(_default_attribute_values) end def _all_default_attribute_values_not_allowing_nil return _default_attribute_values_not_allowing_nil unless superclass.respond_to?(:_default_attribute_values_not_allowing_nil) result = superclass._all_default_attribute_values_not_allowing_nil.concat(_default_attribute_values_not_allowing_nil) result.uniq! result end end module InstanceMethods def initialize(attributes = nil, options = {}) @initialization_attributes = attributes.is_a?(Hash) ? attributes.stringify_keys : {} unless options[:without_protection] if respond_to?(:mass_assignment_options) && options.has_key?(:as) @initialization_attributes = sanitize_for_mass_assignment(@initialization_attributes, options[:as]) elsif respond_to?(:sanitize_for_mass_assignment) @initialization_attributes = sanitize_for_mass_assignment(@initialization_attributes) else @initialization_attributes = remove_attributes_protected_from_mass_assignment(@initialization_attributes) end end if ActiveRecord::VERSION::MAJOR > 3 || (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR > 0) super(attributes, options) else super(attributes) end end def set_default_values self.class._all_default_attribute_values.each do |attribute, container| next unless self.new_record? || self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) connection_default_value_defined = new_record? && respond_to?("#{attribute}_changed?") && !__send__("#{attribute}_changed?") next unless connection_default_value_defined || self.attributes[attribute].blank? # allow explicitly setting nil through allow nil option next if @initialization_attributes.is_a?(Hash) && @initialization_attributes.has_key?(attribute) && !self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) __send__("#{attribute}=", container.evaluate(self)) changed_attributes.delete(attribute) end end end end if defined?(Rails::Railtie) require 'default_value_for/railtie' else # Rails 2 initialization ActiveRecord::Base.extend(DefaultValueFor::ClassMethods) end