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|