lib/riveter/attributes.rb in riveter-0.0.4 vs lib/riveter/attributes.rb in riveter-0.0.5

- old
+ new

@@ -23,126 +23,48 @@ self._attributes.keys end end module ClassMethods - def attr_string(name, options={}) - converter = Converters.converter_for(:string) + def attr_string(name, options={}, &block) + converter = block_given? ? block : Converters.converter_for(:string) - define_method name do - converter.call instance_variable_get("@#{name}") - end - + attr_reader_with_converter name, converter attr_writer name add_attr(name, :string, converter, options) end alias_method :attr_text, :attr_string - def attr_integer(name, options={}) - options = { - :validate => true - }.merge(options) - - required = options[:required] == true - converter = Converters.converter_for(:integer) - - define_method name do - converter.call instance_variable_get("@#{name}") - end - - validates name, - :allow_blank => !required, - :allow_nil => !required, - :numericality => { :only => :integer } if options[:validate] - - attr_writer name - - add_attr(name, :integer, converter, options) + def attr_integer(name, options={}, &block) + attr_numeric(:integer, name, options, &block) end - def attr_decimal(name, options={}) - options = { - :validate => true - }.merge(options) - - required = options[:required] == true - converter = Converters.converter_for(:decimal) - - define_method name do - converter.call instance_variable_get("@#{name}") - end - - validates name, - :allow_blank => !required, - :allow_nil => !required, - :numericality => true if options[:validate] - - attr_writer name - - add_attr(name, :decimal, converter, options) + def attr_decimal(name, options={}, &block) + attr_numeric(:decimal, name, options, &block) end - def attr_date(name, options={}) - options = { - :validate => true - }.merge(options) - - required = options[:required] == true - converter = Converters.converter_for(:date) - - define_method name do - converter.call instance_variable_get("@#{name}") - end - - validates name, - :allow_blank => !required, - :allow_nil => !required, - :timeliness => { :type => :date } if options[:validate] - - attr_writer name - - add_attr(name, :date, converter, options) + def attr_date(name, options={}, &block) + attr_date_or_time(:date, name, options, &block) end - def attr_time(name, options={}) - options = { - :validate => true - }.merge(options) - - required = options[:required] == true - converter = Converters.converter_for(:time) - - define_method name do - converter.call instance_variable_get("@#{name}") - end - - validates name, - :allow_blank => !required, - :allow_nil => !required, - :timeliness => { :type => :time } if options[:validate] - - attr_writer name - - add_attr(name, :time, converter, options) + def attr_time(name, options={}, &block) + attr_date_or_time(:time, name, options, &block) end alias :attr_datetime :attr_time - def attr_boolean(name, options={}) + def attr_boolean(name, options={}, &block) options = { :validate => true }.merge(options) required = options[:required] == true - converter = Converters.converter_for(:boolean) + converter = block_given? ? block : Converters.converter_for(:boolean) - define_method name do - converter.call instance_variable_get("@#{name}") - end - + attr_reader_with_converter name, converter alias_method "#{name}?", name validates name, :allow_blank => !required, :allow_nil => !required, @@ -151,22 +73,20 @@ attr_writer name add_attr(name, :boolean, converter, options) end - def attr_enum(name, enum, options={}) + def attr_enum(name, enum, options={}, &block) options = { :enum => enum, :validate => true }.merge(options) required = options[:required] == true - converter = Converters.converter_for(:enum, options) + converter = block_given? ? block : Converters.converter_for(:enum, options) - define_method name do - converter.call instance_variable_get("@#{name}") - end + attr_reader_with_converter name, converter # helpers # TODO: would be nicer to emulate an association define_singleton_method "#{name}_enum" do @@ -195,80 +115,147 @@ data_type = options.delete(:data_type) converter = block_given? ? block : Converters.converter_for(data_type) define_method name do - (instance_variable_get("@#{name}") || []).map {|item| converter.call(item) } + array = instance_variable_get("@#{name}") || [] + array.map {|v| converter.call(v) } end attr_writer name add_attr(name, :array, converter, options) end - def attr_hash(name, options={}) - converter = Converters.converter_for(:hash, options) + def attr_hash(name, options={}, &block) + options = { + :data_type => :integer, + :validate => true + }.merge(options) + data_type = options.delete(:data_type) - # FIXME: - # * need converter for values? - # * assumes the hash values are contiguous? - # + converter = block_given? ? block : Converters.converter_for(data_type) - attr_accessor name + define_method name do + hash = instance_variable_get("@#{name}") || {} + hash.merge(hash) {|k, v| converter.call(v) } + end + attr_writer name + add_attr(name, :hash, converter, options) end - def attr_model(name, model, options={}) + def attr_model(name, model_or_scope, options={}, &block) options = { - :model => model + :model => model_or_scope, + :validate => true, + :find_by => :id }.merge(options) - converter = Converters.converter_for(:model, options) - # FIXME: - # * need converter for values? - # * define methods to get the actual model instance? - # * assign model instance directly? - # + required = options[:required] == true + converter = if block_given? + block + elsif model_or_scope.respond_to?(:find_by) + Converters.converter_for(:model, options) + else + Converters.converter_for(:object, options) + end # helpers define_singleton_method "#{name}_model" do - model + model_or_scope end - define_method name do - converter.call instance_variable_get("@#{name}") + attr_reader_with_converter name, converter + + # only add validation of the model instance if supported + if model_or_scope.respond_to?(:valid?) && options[:validate] + + validate :"validate_#{name}_model" + + # need a "custom" associated validation since + # we don't reference active record... + define_method :"validate_#{name}_model" do + instance = self.send(name) + return unless required && instance.present? + self.errors.add(name, :invalid) unless instance.valid? + end + end attr_writer name add_attr(name, :model, converter, options) end - def attr_object(name, options={}) - converter = Converters.converter_for(:object, options) + def attr_object(name, options={}, &block) + converter = block_given? ? block : Converters.converter_for(:object, options) + attr_reader_with_converter name, converter + + attr_writer name + + add_attr(name, :object, converter, options) + end + + private + + def attr_reader_with_converter(name, converter) define_method name do converter.call instance_variable_get("@#{name}") end + end + def attr_numeric(type, name, options={}, &block) + options = { + :validate => true + }.merge(options) + + required = options[:required] == true + converter = block_given? ? block : Converters.converter_for(type) + + attr_reader_with_converter name, converter + + validates name, + :allow_blank => !required, + :allow_nil => !required, + :numericality => { :only => type } if options[:validate] + attr_writer name - add_attr(name, :object, converter, options) + add_attr(name, type, converter, options) end - private + def attr_date_or_time(type, name, options={}, &block) + options = { + :validate => true + }.merge(options) + required = options[:required] == true + converter = block_given? ? block : Converters.converter_for(type) + + attr_reader_with_converter name, converter + + validates name, + :allow_blank => !required, + :allow_nil => !required, + :timeliness => { :type => type } if options[:validate] + + attr_writer name + + add_attr(name, type, converter, options) + end + def add_attr(name, type, converter, options={}) self._attributes[name] = attribute_info = AttributeInfo.new(name, type, converter, options) validates name, :presence => true if attribute_info.required? attribute_info end end class AttributeInfo < Struct.new(:name, :type, :converter, :options) - def required? @required ||= (options[:required] == true) end def default @@ -276,11 +263,10 @@ end def default? !self.default.nil? end - end attr_reader :options def initialize(params=nil, options={}) @@ -390,46 +376,29 @@ lambda {|v| v.to_b } when :integer lambda {|v| Integer(v) rescue v } - when :decimal + when :decimal, :float lambda {|v| Float(v) rescue v } - when :hash - lambda {|v| v.to_hash rescue v } - when :date lambda {|v| Date.parse(v) rescue v } when :time lambda {|v| Time.parse(v) rescue v } when :enum lambda {|enum, v| - if v.blank? || v.nil? - nil - else - # FIXME: assuming enum values are integers! - v = Integer(v) rescue v - (v.is_a?(String) || v.is_a?(Symbol)) ? enum.value_for(v) : v.to_i - end + enum.values.include?(v) ? v : enum.value_for(v) }.curry[options[:enum]] - # FIXME: - # when :hash - # lambda {|v| v } - when :model - # FIXME: - lambda {|model, v| - # assuming v is an ID - # model.find(v) - # FIXME - v - }.curry[options[:model]] + lambda {|model, attrib, v| + model.find_by(attrib => v) + }.curry[options[:model], options[:find_by]] - else + else # object etc... lambda {|v| v } end end end