lib/riveter/attributes.rb in riveter-0.3.0 vs lib/riveter/attributes.rb in riveter-0.6.0
- old
+ new
@@ -24,13 +24,17 @@
end
end
module ClassMethods
def attr_string(name, options={}, &block)
+ options = {
+ :validate => true
+ }.merge(options)
+
converter = block_given? ? block : Converters.converter_for(:string)
- attr_reader_with_converter name, converter
+ attr_reader_with_converter name, converter, options
attr_writer name
add_attr(name, :string, converter, options)
end
@@ -63,12 +67,19 @@
[:min, :max].each do |limit|
limit_value = options.delete(limit)
define_method :"#{name}_#{limit}" do
- limit_value.respond_to?(:call) ? limit_value.call : limit_value
+ instance_variable_get("@#{name}_#{limit}") ||
+ (limit_value.respond_to?(:call) ? instance_exec(&limit_value) : limit_value)
end
+
+ # can manually assign the min/max
+ define_method :"#{name}_#{limit}=" do |value|
+ instance_variable_set("@#{name}_#{limit}", value)
+ end
+
end
converter = block_given? ? block : Converters.converter_for(:date)
# return from and to as range
@@ -103,11 +114,11 @@
end
# break down into parts
[:from, :to].each do |part|
- attr_reader_with_converter :"#{name}_#{part}", converter
+ attr_reader_with_converter :"#{name}_#{part}", converter, options
define_method :"#{name}_#{part}?" do
send(:"#{name}_#{part}").present?
end
@@ -125,65 +136,41 @@
date_from = send(:"#{name}_from")
date_to = send(:"#{name}_to")
date_from && date_to
end
- # return from and to as range in UTC
- define_method :"#{name}_utc" do
- date_from = send(:"#{name}_from")
- date_to = send(:"#{name}_to")
- if date_from && date_to
- if date_from == date_to
- date = date_from.to_utc_date
- DateTime.new(date.year, date.month, date.day, 0, 0, 0)..DateTime.new(date.year, date.month, date.day, 23, 59, 59)
- else
- date_from.to_utc_date..date_to.to_utc_date
- end
- else
- nil
- end
- end
-
end
def attr_boolean(name, options={}, &block)
options = {
:validate => true
}.merge(options)
converter = block_given? ? block : Converters.converter_for(:boolean)
- attr_reader_with_converter name, converter
+ attr_reader_with_converter name, converter, options
alias_method "#{name}?", name
attr_writer name
add_attr(name, :boolean, converter, options)
end
+ # NB: enum must respond to values
def attr_enum(name, enum, options={}, &block)
+ raise ArgumentError, 'enum' unless enum && enum.respond_to?(:values)
+
options = {
:enum => enum,
:validate => true
}.merge(options)
required = options[:required] == true
converter = block_given? ? block : Converters.converter_for(:enum, options)
- attr_reader_with_converter name, converter
+ attr_reader_with_converter name, converter, options
- # helpers
- # TODO: would be nicer to emulate an association
-
- define_singleton_method "#{name}_enum" do
- enum
- end
-
- define_singleton_method name.to_s.pluralize do
- enum.collection
- end
-
validates name,
:allow_blank => !required,
:allow_nil => !required,
:inclusion => { :in => enum.values } if options[:validate]
@@ -201,11 +188,11 @@
converter = block_given? ? block : Converters.converter_for(data_type)
define_method name do
array = instance_variable_get("@#{name}") || []
- array.map {|v| converter.call(v) }
+ array.map {|v| converter.call(v, options) }
end
attr_writer name
add_attr(name, :array, converter, options)
@@ -220,77 +207,58 @@
converter = block_given? ? block : Converters.converter_for(data_type)
define_method name do
hash = instance_variable_get("@#{name}") || {}
- hash.merge(hash) {|k, v| converter.call(v) }
+ hash.merge(hash) {|k, v| converter.call(v, options) }
end
attr_writer name
add_attr(name, :hash, converter, options)
end
- ##
- # FIXME: this doesn't work as expected
- #
- def attr_model(name, model_or_scope, options={}, &block)
+ def attr_object(name, options={}, &block)
options = {
- :model => model_or_scope,
- :validate => true,
- :find_by => :id
+ :validate => true
}.merge(options)
- 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
+ converter = block_given? ? block : Converters.converter_for(:object, options)
- # helpers
- define_singleton_method "#{name}_model" do
- model_or_scope
- end
+ attr_reader_with_converter name, converter, options
- attr_reader_with_converter name, converter
-
- # only add validation of the model instance if supported
- if model_or_scope.instance_methods.include?(:valid?) && options[:validate]
- validate :"validate_#{name}"
-
- # need a "custom" associated validation since
- # we don't reference active record...
- define_method :"validate_#{name}" 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)
+ add_attr(name, :object, converter, options)
end
- def attr_object(name, options={}, &block)
- converter = block_given? ? block : Converters.converter_for(:object, options)
+ def attr_class(name, classes, options={}, &block)
+ options = {
+ :validate => true,
+ :classes => classes
+ }.merge(options)
- attr_reader_with_converter name, converter
+ required = options[:required] == true
+ converter = block_given? ? block : Converters.converter_for(:class, options)
+ attr_reader_with_converter name, converter, options
+
+ validates name,
+ :allow_blank => !required,
+ :allow_nil => !required,
+ :inclusion => { :in => classes } if options[:validate]
+
attr_writer name
- add_attr(name, :object, converter, options)
+ add_attr(name, :class, converter, options)
end
private
- def attr_reader_with_converter(name, converter)
+ def attr_reader_with_converter(name, converter, options={})
define_method name do
- converter.call instance_variable_get("@#{name}")
+ converter.call(instance_variable_get("@#{name}"), options)
end
end
def attr_numeric(type, name, options={}, &block)
options = {
@@ -298,11 +266,11 @@
}.merge(options)
required = options[:required] == true
converter = block_given? ? block : Converters.converter_for(type)
- attr_reader_with_converter name, converter
+ attr_reader_with_converter name, converter, options
validates name,
:allow_blank => !required,
:allow_nil => !required,
:numericality => { :only => type } if options[:validate]
@@ -318,11 +286,11 @@
}.merge(options)
required = options[:required] == true
converter = block_given? ? block : Converters.converter_for(type)
- attr_reader_with_converter name, converter
+ attr_reader_with_converter name, converter, options
validates name,
:allow_blank => !required,
:allow_nil => !required,
:timeliness => { :type => type } if options[:validate]
@@ -331,39 +299,41 @@
add_attr(name, type, converter, options)
end
def add_attr(name, type, converter=nil, options={})
- self._attributes[name] = attribute_info = AttributeInfo.new(name, type, converter, options)
+ self._attributes[name] = attribute_info = AttributeInfo.new(self, name, type, converter, options)
validates name, :presence => true if attribute_info.required?
attribute_info
end
end
- class AttributeInfo < Struct.new(:name, :type, :converter, :options)
+ class AttributeInfo < Struct.new(:target, :name, :type, :converter, :options)
def required?
@required ||= (options[:required] == true)
end
def default
@default ||= options[:default]
- @default.respond_to?(:call) ? @default.call : @default
end
def default?
!self.default.nil?
end
+
+ def evaluate_default(scope)
+ default? && default.respond_to?(:call) ? scope.instance_exec(&default) : default
+ end
end
attr_reader :options
def initialize(params=nil, options={})
# assign default values
self.class._attributes.each do |name, attribute_info|
next unless attribute_info.default?
- value = attribute_info.default
- send("#{name}=", value.respond_to?(:call) ? value.call : value)
+ send("#{name}=", attribute_info.evaluate_default(self))
end
@options = options.freeze
# filter and clean params before applying
@@ -459,38 +429,38 @@
# supply a built-in converter for the given type
def self.converter_for(data_type, options={})
case data_type
when :string
- lambda {|v| v.to_s }
+ lambda {|v, opt| v.to_s }
when :boolean
- lambda {|v| v.to_b }
+ lambda {|v, opt| v.to_b }
when :integer
- lambda {|v| Integer(v) rescue v }
+ lambda {|v, opt| Integer(v) rescue v }
when :decimal, :float
- lambda {|v| Float(v) rescue v }
+ lambda {|v, opt| Float(v) rescue v }
when :date
- lambda {|v| Date.parse(v) rescue v }
+ lambda {|v, opt| Date.parse(v) rescue v }
when :time
- lambda {|v| Time.parse(v) rescue v }
+ lambda {|v, opt| Time.parse(v) rescue v }
when :enum
- lambda {|enum, v|
+ lambda {|enum, v, opt|
enum.values.include?(v) ? v : enum.value_for(v)
}.curry[options[:enum]]
- when :model
- lambda {|model, attrib, v|
- v.is_a?(model) ? v : model.find_by(attrib => v)
- }.curry[options[:model], options[:find_by]]
+ when :class
+ lambda {|models, v, opt|
+ models[v] || v
+ }.curry[options[:classes].inject({}) {|list, klass| list[klass.name] = klass; list }]
else # object etc...
- lambda {|v| v }
+ lambda {|v, opt| v }
end
end
end