lib/simple_model/attributes.rb in simple_model-1.2.17 vs lib/simple_model/attributes.rb in simple_model-1.2.18

- old
+ new

@@ -1,30 +1,27 @@ module SimpleModel module Attributes include ExtendCore extend ActiveSupport::Concern include ActiveModel::AttributeMethods - + attr_accessor :attributes + def initialize(*attrs) attrs = attrs.extract_options! attrs = attributes_with_for_init(attrs) attrs = self.class.before_initialize.call(self,attrs) if self.class.before_initialize set(attrs) self.class.after_initialize.call(self) if self.class.after_initialize end - # Returns true if attribute has been initialized - def initialized?(attr) - attributes.key?(attr.to_sym) - end - def attributes @attributes ||= HashWithIndifferentAccess.new end - def attributes=attrs - @attributes = attrs + # Returns true if attribute has been initialized + def initialized?(attr) + self.attributes.key?(attr.to_sym) end def get(attr) self.send(attr) end @@ -37,12 +34,51 @@ self.send("#{attr.to_s}=",val) end end alias :set_attributes :set + def set_attribute(attr,val) + options = self.class.defined_attributes[attr] || {} + if allow_attribute_action?(self,val,options) + val = fetch_default_value(options[:default]) if (!options[:allow_blank] && options.key?(:default) && val.blank?) + val = options[:on_set].call(self,val) unless (!options.key?(:on_set) || (val.blank? && !options[:allow_blank]) ) + will_change = "#{attr}_will_change!".to_sym + self.send(will_change) if (initialized?(attr) && val != self.attributes[attr]) + self.attributes[attr] = val + options[:after_set].call(self,val) if options[:after_set] + end + end + + def get_attribute(attr) + val = self.attributes[attr] + options = self.class.defined_attributes[attr] || {} + if (options.key?(:default) && (!self.initialized?(attr) || (!options[:allow_blank] && val.blank?))) + val = self.attributes[attr] = fetch_default_value(options[:default]) + end + if options[:on_get] + options[:on_get].call(self,val) + else + val + end + end + + def get_attribute?(attr) + val = get_attribute(attr) + if val.respond_to?(:to_b) + val = val.to_b + else + val = !val.blank? if val.respond_to?(:blank?) + end + val + end + private + def attribute_defined?(attr) + self.class.attribute_defined?(attr) + end + def fetch_default_value(arg) return self.send(arg) if (arg.is_a?(Symbol) && self.respond_to?(arg)) arg end @@ -56,16 +92,16 @@ end end d end + # Only set default if there is a default value, initializing is allow and + # new attributes do not have a value to set and def allow_set_default?(d,k,v) - (v[:default] && v[:initialize] && (d[k].blank? && (self.class.alias_attributes[k].blank? || d.key?(self.class.alias_attributes[k]) && d[self.class.alias_attributes[k]].blank?))) + (v[:default] && (v[:initialize] != false) && (!d.key?(k) && !d.key?(self.class.alias_attributes[k]))) end - private - def allow_attribute_action?(obj,val,options) return true if (options[:if].blank? && options[:unless].blank?) b = true if options[:if].is_a?(Symbol) if options[:if] == :blank @@ -90,10 +126,35 @@ def attribute(name) attributes[name.to_sym] end module ClassMethods + DEFAULT_ATTRIBUTE_SETTINGS = {:attributes_method => :attributes, + :allow_blank => true, + :initialize => true + }.freeze + + AVAILABLE_ATTRIBUTE_METHODS = { + :has_attribute => {:alias => :has_attributes}, + :has_boolean => {:cast_to => :to_b, :alias => :has_booleans}, + :has_currency => {:cast_to => :to_d, :alias => :has_currencies}, + :has_date => {:cast_to => :to_date, :alias => :has_dates} , + :has_decimal => {:cast_to => :to_d, :alias => :has_decimals}, + :has_float => {:cast_to => :to_f, :alias => :has_floats}, + :has_int => {:cast_to => :to_i, :alias => :has_ints}, + :has_time => {:cast_to => :to_time, :alias => :has_times} + }.freeze + + AVAILABLE_ATTRIBUTE_METHODS.each do |method,method_options| + define_method(method) do |*attributes| + options = default_attribute_settings.merge(attributes.extract_options!) + options[:on_set] = lambda {|obj,val| val.send(method_options[:cast_to]) } if method_options[:cast_to] + create_attribute_methods(attributes,options) + end + module_eval("alias #{method_options[:alias]} #{method}") + end + # Creates a new instance where the attributes store is set to object # provided, which allows one to pass a session store hash or any other # hash-like object to be used for persistence. Typically used for modeling # session stores for authorization or shopping carts # EX: @@ -104,14 +165,14 @@ # end # helper_method :session_user # end # def new_with_store(session_hash) - new = self.new() - new.attributes = session_hash - new.set(new.send(:attributes_with_for_init,session_hash)) - new + nw = self.new() + nw.attributes = session_hash + nw.set(nw.send(:attributes_with_for_init,session_hash)) + nw end def alias_attributes @alias_attributes ||= HashWithIndifferentAccess.new end @@ -139,16 +200,11 @@ # * :default - the default value for the attribute, can be a symbol that is sent for a method # * :initialize - informations the object whether or not it should initialize the attribute with :default value, defaults to true # ** If :initialize is set to false you must set :allow_blank to false or it will never set the default value # * :allow_blank - when set to false, if an attributes value is blank attempts to set the default value, defaults to true def default_attribute_settings - @default_attribute_settings ||= {:attributes_method => :attributes, - :on_set => lambda {|obj,attr| attr}, - :on_get => lambda {|obj,attr| attr}, - :allow_blank => true, - :initialize => true - } + @default_attribute_settings ||= DEFAULT_ATTRIBUTE_SETTINGS end def default_attribute_settings=default_attribute_settings @default_attribute_settings = default_attribute_settings end @@ -157,90 +213,59 @@ # at once, so we must set @attribute_methods_generated to nil to allow the # re-run to occur ONLY IN RAILS 3.0. def add_defined_attribute(attr,options) self.defined_attributes[attr] = options @attribute_methods_generated = nil #if (ActiveModel::VERSION::MAJOR == 3 && ActiveModel::VERSION::MINOR == 0) - define_attribute_methods(self.defined_attributes.keys) + define_attribute_methods(defined_attributes_keys) end + # We don't want to call define_attribute_methods on methods defined in the parent class + def defined_attributes_keys + dak = self.defined_attributes.keys + dak = dak - self.superclass.defined_attributes.keys if self.superclass.respond_to?(:defined_attributes) + dak + end + # builds the setter and getter methods def create_attribute_methods(attributes,options) unless attributes.blank? attributes.each do |attr| - define_reader_with_options(attr,options) define_setter_with_options(attr,options) + define_reader_with_options(attr,options) + add_defined_attribute(attr,options) end end end def define_reader_with_options(attr,options) - add_defined_attribute(attr,options) - options = default_attribute_settings.merge(options) if options[:on_get].blank? define_method(attr) do - val = self.attributes[attr] - if (options.key?(:default) && (!self.initialized?(attr) || (!options[:allow_blank] && val.blank?))) - val = self.attributes[attr] = fetch_default_value(options[:default]) - end - options[:on_get].call(self,val) + get_attribute(attr) end define_method("#{attr.to_s}?") do - # return false unless initialized?(attr) THIS BROKE STUFF - val = self.send(attr) - if val.respond_to?(:to_b) - val = val.to_b - else - val = !val.blank? if val.respond_to?(:blank?) - end - val + get_attribute?(attr) end end # Creates setter methods for the provided attributes # On set, it will mark the attribute as changed if the attributes has been # initialized. def define_setter_with_options(attr,options) - add_defined_attribute(attr,options) - options = default_attribute_settings.merge(options) if (options[:on_set].blank? || options[:after_set].blank?) define_method("#{attr.to_s}=") do |val| - if allow_attribute_action?(self,val,options) - val = fetch_default_value(options[:default]) if (!options[:allow_blank] && options.key?(:default) && val.blank?) - val = options[:on_set].call(self,val) unless (val.blank? && !options[:allow_blank] ) - will_change = "#{attr}_will_change!".to_sym - self.send(will_change) if (initialized?(attr) && val != self.attributes[attr]) - self.attributes[attr] = val - options[:after_set].call(self,val) if options[:after_set] - end + set_attribute(attr,val) end end - AVAILABLE_ATTRIBUTE_METHODS = { - :has_attribute => {:alias => :has_attributes}, - :has_boolean => {:cast_to => :to_b, :alias => :has_booleans}, - :has_currency => {:cast_to => :to_d, :alias => :has_currencies}, - :has_date => {:cast_to => :to_date, :alias => :has_dates} , - :has_decimal => {:cast_to => :to_d, :alias => :has_decimals}, - :has_float => {:cast_to => :to_f, :alias => :has_floats}, - :has_int => {:cast_to => :to_i, :alias => :has_ints}, - :has_time => {:cast_to => :to_time, :alias => :has_times} - } - - AVAILABLE_ATTRIBUTE_METHODS.each do |method,method_options| - define_method(method) do |*attributes| - options = default_attribute_settings.merge(attributes.extract_options!) - options[:on_set] = lambda {|obj,val| val.send(method_options[:cast_to]) } if method_options[:cast_to] - create_attribute_methods(attributes,options) - end - module_eval("alias #{method_options[:alias]} #{method}") - end - # Creates alias setter and getter for the supplied attribute using the supplied alias # See spec for example. def alias_attribute(new_alias,attribute) alias_attributes[attribute] = new_alias define_method(new_alias) do self.send(attribute) end + define_method("#{new_alias}?") do + self.send("#{attribute}?") + end define_method("#{new_alias.to_s}=") do |*args, &block| self.send("#{attribute.to_s}=",*args, &block) end end @@ -278,18 +303,19 @@ # Must inherit super's defined_attributes and alias_attributes # Rails 3.0 does some weird stuff with ActiveModel::Dirty so we need a # hack to keep things working when a class inherits from a super that # has ActiveModel::Dirty included def inherited(base) - base.alias_attributes = self.alias_attributes.merge(base.alias_attributes) + base.defined_attributes = self.defined_attributes.dup + base.alias_attributes = self.alias_attributes.dup super # Rails 3.0 Hack if (ActiveModel::VERSION::MAJOR == 3 && ActiveModel::VERSION::MINOR == 0) base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' base.attribute_method_affix :prefix => 'reset_', :suffix => '!' end end - end + end # end ClassMethods # Rails 3.0 does some weird stuff with ActiveModel::Dirty so we need a # hack to keep things working when a class includes a module that has # ActiveModel::Dirty included def self.included(base)