lib/ach/component.rb in ach_builder-0.2.1 vs lib/ach/component.rb in ach_builder-0.2.2
- old
+ new
@@ -17,11 +17,15 @@
autoload :HasManyAssociation
# Exception raised on attempt to assign a value to nonexistent field.
class UnknownAttributeError < ArgumentError
- def initialize field, obj
+ # Initialize error with field and component.
+ #
+ # @param [String] field
+ # @param [ACH::Component] obj
+ def initialize(field, obj)
super "Unrecognized attribute '#{field}' for #{obj}"
end
end
class_attribute :default_attributes
@@ -29,10 +33,13 @@
self.default_attributes = {}
self.after_initialize_hooks = []
attr_reader :attributes
+ # When inherited, clone class-related properties.
+ #
+ # @param [Class] klass
def self.inherited(klass)
klass.default_attributes = default_attributes.dup
klass.after_initialize_hooks = after_initialize_hooks.dup
end
@@ -44,37 +51,54 @@
# File was defined with default value for 'company_name', this value will be passed
# to every Batch component within file, and from every Batch to corresponding batch
# header record.
#
# Note that default values may be overwritten when building records.
+ #
+ # @param [Symbol] meth
+ # @param [*Object] args
def self.method_missing(meth, *args)
if Formatter.defined?(meth)
default_attributes[meth] = args.first
else
super
end
end
- def initialize(fields = {}, &block)
- @attributes = {}.merge(self.class.default_attributes)
+ # Initialize component with field values. If block is given, it is evaluated in context
+ # of component, allowing setting fields via method calls and declarations of nested
+ # components.
+ #
+ # @param [Hash] fields
+ # @raise [UnknownAttributeError]
+ def initialize(fields = {})
+ @attributes = self.class.default_attributes.dup
fields.each do |name, value|
raise UnknownAttributeError.new(name, self) unless Formatter.defined?(name)
@attributes[name] = value
end
after_initialize
- instance_eval(&block) if block
+ instance_eval(&Proc.new) if block_given?
end
+ # Missing messages are treated as accessor methods for a component if their
+ # message name is defined by {ACH::Formatter}.
+ #
+ # @param [Symbol] meth
+ # @param [*Object] args
+ # @return [String]
def method_missing(meth, *args)
if Formatter.defined?(meth)
args.empty? ? @attributes[meth] : (@attributes[meth] = args.first)
else
super
end
end
- def before_header # :nodoc:
+ # Hook that is called whenever component header record is created. To be
+ # overridden in subclasses.
+ def before_header
end
private :before_header
# Sets header fields if fields or block passed. Returns header record.
#
@@ -90,49 +114,67 @@
# end
#
# == Example 3
#
# header # => just returns a header object
- def header(fields = {}, &block)
+ def header(fields = {})
before_header
merged_fields = fields_for(self.class::Header).merge(fields)
@header ||= self.class::Header.new(merged_fields)
@header.tap do |head|
- head.instance_eval(&block) if block
+ head.instance_eval(&Proc.new) if block_given?
end
end
- def build_header(str) # :nodoc:
+ # Build a component (+File+ or +Batch+) related +Header+ record from a given string.
+ #
+ # @param [String] str
+ # @return [ACH::Record::Base]
+ def build_header(str)
@header = self.class::Header.from_s(str)
end
+ # Build a component-related +Control+ record.
+ #
+ # @return [ACH::Record::Base]
def control
@control ||= begin
klass = self.class::Control
fields = klass.fields.select{ |f| respond_to?(f) || attributes[f] }
klass.new Hash[*fields.zip(fields.map{ |f| send(f) }).flatten]
end
end
- def build_control(str) # :nodoc:
+ # Build a component-related +Control+ record from a given string.
+ #
+ # @param [String] str
+ # @return [ACH::Record::Base]
+ def build_control(str)
@control = self.class::Control.from_s(str)
end
-
+
+ # Return a set of fields, that is a subset of +attributes+ that can be used to
+ # initialize an instance of a +klass+. +Component+ uses +attributes+ itself.
+ #
+ # @param [Class] klass
+ # @return [Hash]
def fields_for(klass)
if klass < Component
attributes
else
attrs = attributes.find_all{ |k, v| klass.fields.include?(k) && attributes[k] }
Hash[*attrs.flatten]
end
end
- def after_initialize # :nodoc:
+ # Execute all +Proc+ objects contained in the +after_initialize_hooks+
+ # array in the context of the object.
+ def after_initialize
self.class.after_initialize_hooks.each{ |hook| instance_exec(&hook) }
end
- # Creates has many association.
+ # Creates a has_many association.
#
# == Example
#
# class File < Component
# has_many :batches
@@ -145,9 +187,12 @@
# file.batches # => [#<Batch ...>]
#
# The example above extends File with #batches and #batch instance methods:
# * #batch is used to add new instance of Batch.
# * #batches is used to get an array of batches which belong to file.
+ #
+ # @param [Symbol] plural_name
+ # @param [Hash] options
def self.has_many(plural_name, options = {})
association = HasManyAssociation.new(plural_name, options)
association_variable_name = "@#{plural_name}_association"
association.delegation_methods.each do |method_name|