module ACH module Record # Base class for all record entities (e.g. +ACH::File::Header+, # +ACH::File::Control+, +ACH::Record::Entry+, others). Any record # being declared should specify its fields, and optional default values. # Except for +ACH::Record::Dynamic+, any declared field within a record # should have corresponding rule defined under +ACH::Rule::Formatter+. # # == Example # # class Addenda < Record # fields :record_type, # :addenda_type_code, # :payment_related_info, # :addenda_sequence_num, # :entry_details_sequence_num # # defaults :record_type => 7 # end # # addenda = ACH::Addenda.new( # :addenda_type_code => '05', # :payment_related_info => 'PAYMENT_RELATED_INFO', # :addenda_sequence_num => 1, # :entry_details_sequence_num => 1 ) # addenda.to_s! # => "705PAYMENT_RELATED_INFO 00010000001" class Base include Validations include Constants # Raises when unknown field passed to ACH::Record::Base.fields method. class UnknownFieldError < ArgumentError # Initialize error with descriptive message. # # @param [Symbol] field # @param [String] class_name def initialize(field, class_name) super "Unrecognized field '#{field}' in class #{class_name}" end end # Raises when value of record's field is not specified and there is no # default value. class EmptyFieldError < ArgumentError # Initialize error with descriptive message. # # @param [Symbol] field # @param [ACH::Record::Base] record def initialize(field, record) super "Empty field '#{field}' for #{record}" end end # Specify the fields of the record. Order is important. All fields # must be declared in ACH::Formatter +RULES+. See class description # for example. # # @param [*Symbol] field_names # @return [Array<Symbol>] def self.fields(*field_names) return @fields if field_names.empty? @fields = field_names @fields.each{ |field| define_field_methods(field) } end # Set default values for fields. See class description for example. # # @param [Hash, nil] default_values # @return [Hash] def self.defaults(default_values = nil) return @defaults if default_values.nil? @defaults = default_values.freeze end # Define accessor methods for a given field name if it can be found # in the keys of {ACH::Formatter::RULES}. # # @param [Symbol] field # @raise [UnknownFieldError] def self.define_field_methods(field) raise UnknownFieldError.new(field, name) unless Formatter::RULES.key?(field) define_method(field) do |*args| args.empty? ? @fields[field] : (@fields[field] = args.first) end define_method("#{field}=") do |val| @fields[field] = val end end private_class_method :define_field_methods # Build a new instance of a record from its string representation. # # @param [String] string # @return [ACH::Record::Base] def self.from_s(string) field_matcher_regexp = Formatter.matcher_for(fields) new Hash[*fields.zip(string.match(field_matcher_regexp)[1..-1]).flatten] end # Initialize object with field values. If block is given, it will be # evaluated in context of isntance. # # @param [Hash] fields def initialize(fields = {}) defaults.each do |key, value| self.fields[key] = Proc === value ? value.call : value end self.fields.merge!(fields) instance_eval(&Proc.new) if block_given? end # Build a string from record object. # # @return [Stirng] # @raise [EmptyFieldError] def to_s! self.class.fields.map do |name| raise EmptyFieldError.new(name, self) if @fields[name].nil? Formatter.format name, @fields[name] end.join end # Return a hash where key is the field's name and value is the field's value. # # @return [Hash] def fields @fields ||= {} end # Return field default values defined in class. # # @return [Hash] def defaults self.class.defaults end private :defaults # Delegate bracket-assignment to +fields+. # # @param [Symbol] name # @param [String] val # @return [String] def []=(name, val) fields[name] = val end private :[]= end end end