ach_builder 0.0.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +11 -2
- data/MIT-LICENSE +1 -1
- data/README.md +19 -11
- data/Rakefile +44 -0
- data/ach_builder.gemspec +9 -5
- data/lib/ach/batch.rb +10 -32
- data/lib/ach/batch/builder.rb +60 -0
- data/lib/ach/batch/control.rb +18 -3
- data/lib/ach/batch/header.rb +21 -4
- data/lib/ach/component.rb +125 -43
- data/lib/ach/component/has_many_association.rb +155 -0
- data/lib/ach/constants.rb +15 -1
- data/lib/ach/file.rb +50 -44
- data/lib/ach/file/builder.rb +81 -0
- data/lib/ach/file/control.rb +15 -3
- data/lib/ach/file/header.rb +20 -3
- data/lib/ach/file/reader.rb +103 -0
- data/lib/ach/file/transmission_header.rb +77 -0
- data/lib/ach/formatter.rb +113 -76
- data/lib/ach/formatter/rule.rb +27 -0
- data/lib/ach/record.rb +13 -64
- data/lib/ach/record/addenda.rb +25 -0
- data/lib/ach/record/base.rb +109 -0
- data/lib/ach/record/dynamic.rb +58 -0
- data/lib/ach/record/entry.rb +49 -0
- data/lib/ach/record/tail.rb +10 -0
- data/lib/ach/validations.rb +5 -3
- data/lib/ach/version.rb +1 -1
- data/lib/ach_builder.rb +20 -33
- data/spec/batch_spec.rb +23 -11
- data/spec/componenet/has_many_association_spec.rb +111 -0
- data/spec/file_spec.rb +173 -81
- data/spec/formatter_spec.rb +9 -9
- data/spec/reader_spec.rb +55 -0
- data/spec/record/addenda_spec.rb +8 -0
- data/spec/record/base_spec.rb +53 -0
- data/spec/record/dynamic_spec.rb +74 -0
- data/spec/record/entry_spec.rb +7 -0
- data/spec/record/tail_spec.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/ach_file_factory.rb +70 -0
- data/spec/support/examples/well_fargo_empty.ach +2 -0
- data/spec/support/examples/well_fargo_with_data.ach +6 -0
- data/spec/support/helpers/ach_files_examples.rb +17 -0
- metadata +69 -26
- data/lib/ach/entry.rb +0 -31
- data/lib/ach/tail.rb +0 -6
- data/spec/entry_spec.rb +0 -7
- data/spec/record_spec.rb +0 -34
- data/spec/tail_spec.rb +0 -7
@@ -0,0 +1,77 @@
|
|
1
|
+
module ACH
|
2
|
+
# Hosts functionality required to append +TransmissionHeader+ to a file.
|
3
|
+
# +TransmissionHeader+ is optional and inherited from <tt>ACH::Record::Dynamic</tt>
|
4
|
+
# class, which means it may have variable number of fields with custom formatting.
|
5
|
+
# +TransmissionHeader+ may be defined only once per file. You may specify default
|
6
|
+
# value for custom fields during definition
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# class MyFile < ACH::File
|
11
|
+
# trasmission_header do
|
12
|
+
# starting '->1' => '<'
|
13
|
+
# receiver_name '->10'
|
14
|
+
# ending '->1' => '>'
|
15
|
+
# end
|
16
|
+
# # other definitions
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# file = MyFile.new do
|
20
|
+
# receiver_name 'MY PROVIDER'
|
21
|
+
# end
|
22
|
+
module File::TransmissionHeader
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
# Raised when (descendant of) ACH File tries to redeclare it's +TransmissionHeader+
|
26
|
+
class RedefinedTransmissionHeaderError < RuntimeError
|
27
|
+
def initialize
|
28
|
+
super "TransmissionHeader record may be defined only once"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Raised when +TransmissionHeader+ is declared with no fields in it
|
33
|
+
class EmptyTransmissionHeaderError < RuntimeError
|
34
|
+
def initialize
|
35
|
+
super "Transmission_header should declare it's fields"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
# Defines and declares +TransmissionHeader+ class within scope of +self+
|
41
|
+
def transmission_header(&block)
|
42
|
+
raise RedefinedTransmissionHeaderError if have_transmission_header?
|
43
|
+
|
44
|
+
klass = Class.new(Record::Dynamic, &block)
|
45
|
+
|
46
|
+
raise EmptyTransmissionHeaderError if klass.fields.nil? || klass.fields.empty?
|
47
|
+
|
48
|
+
const_set(:TransmissionHeader, klass)
|
49
|
+
@have_transmission_header = true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns +true+ if +TransmissionHeader+ is defined within scope of the class.
|
53
|
+
def have_transmission_header?
|
54
|
+
@have_transmission_header
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Helper instance method. Returns +true+ if +TransmissionHeader+ is defined within
|
59
|
+
# scope of it's class
|
60
|
+
def have_transmission_header?
|
61
|
+
self.class.have_transmission_header?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Builds +TransmissionHeader+ record for self. Yields it to +block+, if passed.
|
65
|
+
# Returns nil if no +TransmissionHeader+ is defined within scope of class.
|
66
|
+
def transmission_header(fields = {}, &block)
|
67
|
+
return nil unless have_transmission_header?
|
68
|
+
|
69
|
+
merged_fields = fields_for(self.class::TransmissionHeader).merge(fields)
|
70
|
+
|
71
|
+
@transmission_header ||= self.class::TransmissionHeader.new(merged_fields)
|
72
|
+
@transmission_header.tap do |head|
|
73
|
+
head.instance_eval(&block) if block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/ach/formatter.rb
CHANGED
@@ -1,84 +1,121 @@
|
|
1
1
|
module ACH
|
2
|
+
# Every field should be formatted with its own rule so Formatter take care about it.
|
3
|
+
# Rules are defined in ACH::RULES constant.
|
4
|
+
#
|
5
|
+
# == Rule Format
|
6
|
+
#
|
7
|
+
# Every rule can contain the next items:
|
8
|
+
# * _justification_ - "<-" or "->".
|
9
|
+
# * _width_ - a number of characters for a field.
|
10
|
+
# * _padding_ - specifying "-" will pad right-justified values with spaces instead of zeros.
|
11
|
+
# * _transformation_ - allows to call method on a string. Pipe must precedes a method name. Foe example: "|upcase".
|
12
|
+
# For real examples see RULES constants.
|
13
|
+
#
|
14
|
+
# == Usage Example
|
15
|
+
#
|
16
|
+
# ACH::Formatter.format(:customer_name, "LINUS TORVALDS") # => "LINUS TORVALDS "
|
17
|
+
# ACH::Formatter.format(:amount, 52) # => "0000000052"
|
18
|
+
# ACH::Formatter.customer_acct('1234567890') # => "1234567890 "
|
2
19
|
module Formatter
|
3
|
-
extend
|
4
|
-
|
20
|
+
extend ActiveSupport::Autoload
|
21
|
+
|
22
|
+
autoload :Rule
|
23
|
+
|
24
|
+
# Rules for formatting each field. See module documentation for examples.
|
5
25
|
RULES = {
|
6
|
-
:customer_name
|
7
|
-
:customer_acct
|
8
|
-
:amount
|
9
|
-
:bank_2
|
10
|
-
:transaction_type
|
11
|
-
:bank_15
|
12
|
-
:addenda
|
13
|
-
:trace_num
|
14
|
-
:transaction_code
|
15
|
-
:record_type
|
16
|
-
:bank_account
|
17
|
-
:routing_number
|
18
|
-
:priority_code
|
19
|
-
:immediate_dest
|
20
|
-
:immediate_origin
|
21
|
-
:date
|
22
|
-
:time
|
23
|
-
:file_id_modifier
|
24
|
-
:record_size
|
25
|
-
:blocking_factor
|
26
|
-
:format_code
|
27
|
-
:immediate_dest_name
|
28
|
-
:immediate_origin_name
|
29
|
-
:reference_code
|
30
|
-
:service_class_code
|
31
|
-
:company_name
|
32
|
-
:company_note_data
|
33
|
-
:company_id
|
34
|
-
:entry_class_code
|
35
|
-
:company_entry_descr
|
36
|
-
:effective_date
|
37
|
-
:settlement_date
|
38
|
-
:origin_status_code
|
39
|
-
:origin_dfi_id
|
40
|
-
:batch_number
|
41
|
-
:
|
42
|
-
:entry_hash
|
43
|
-
:total_debit_amount
|
44
|
-
:total_credit_amount
|
45
|
-
:authen_code
|
46
|
-
:bank_6
|
47
|
-
:batch_count
|
48
|
-
:block_count
|
49
|
-
:
|
50
|
-
:bank_39
|
51
|
-
:nines
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
26
|
+
:customer_name => '<-22',
|
27
|
+
:customer_acct => '<-15',
|
28
|
+
:amount => '->10',
|
29
|
+
:bank_2 => '<-2',
|
30
|
+
:transaction_type => '<-2',
|
31
|
+
:bank_15 => '<-15',
|
32
|
+
:addenda => '<-1',
|
33
|
+
:trace_num => '<-15',
|
34
|
+
:transaction_code => '<-2',
|
35
|
+
:record_type => '<-1',
|
36
|
+
:bank_account => '<-17',
|
37
|
+
:routing_number => '->9',
|
38
|
+
:priority_code => '->2',
|
39
|
+
:immediate_dest => '->10-',
|
40
|
+
:immediate_origin => '->10-',
|
41
|
+
:date => '<-6',
|
42
|
+
:time => '<-4',
|
43
|
+
:file_id_modifier => '<-1|upcase',
|
44
|
+
:record_size => '->3',
|
45
|
+
:blocking_factor => '->2',
|
46
|
+
:format_code => '<-1',
|
47
|
+
:immediate_dest_name => '<-23',
|
48
|
+
:immediate_origin_name => '<-23',
|
49
|
+
:reference_code => '<-8',
|
50
|
+
:service_class_code => '<-3',
|
51
|
+
:company_name => '<-16', # aka Individual Name
|
52
|
+
:company_note_data => '<-20',
|
53
|
+
:company_id => '<-10',
|
54
|
+
:entry_class_code => '<-3',
|
55
|
+
:company_entry_descr => '<-10',
|
56
|
+
:effective_date => '<-6',
|
57
|
+
:settlement_date => '<-3',
|
58
|
+
:origin_status_code => '<-1',
|
59
|
+
:origin_dfi_id => '<-8',
|
60
|
+
:batch_number => '->7',
|
61
|
+
:entry_addenda_count => '->6',
|
62
|
+
:entry_hash => '->10',
|
63
|
+
:total_debit_amount => '->12',
|
64
|
+
:total_credit_amount => '->12',
|
65
|
+
:authen_code => '<-19',
|
66
|
+
:bank_6 => '<-6',
|
67
|
+
:batch_count => '->6',
|
68
|
+
:block_count => '->6',
|
69
|
+
:file_entry_addenda_count => '->8',
|
70
|
+
:bank_39 => '<-39',
|
71
|
+
:nines => '<-94',
|
72
|
+
|
73
|
+
# Batch header
|
74
|
+
:desc_date => '<-6-', # company descriptive date
|
75
|
+
|
76
|
+
# Addenda Record
|
77
|
+
:addenda_type_code => '->2',
|
78
|
+
:payment_related_info => '<-80',
|
79
|
+
:addenda_sequence_num => '->4',
|
80
|
+
:entry_details_sequence_num => '->7'
|
81
|
+
}
|
82
|
+
|
83
|
+
# Returns +true+ if +field_name+ is one of the keys of +RULES+
|
84
|
+
def self.defined?(field_name)
|
85
|
+
RULES.key?(field_name)
|
61
86
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else
|
67
|
-
super
|
68
|
-
end
|
87
|
+
|
88
|
+
# Adds +field+ with corresponding +format+ to +RULES+
|
89
|
+
def self.define(field, format)
|
90
|
+
RULES[field] = format
|
69
91
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
92
|
+
|
93
|
+
# If missing method name is one of the defined rules, passes its name
|
94
|
+
# and the rest of arguments to the +format+ method
|
95
|
+
def self.method_missing(meth, *args)
|
96
|
+
self.defined?(meth) ? format(meth, *args) : super
|
97
|
+
end
|
98
|
+
|
99
|
+
# Formats +value+ using the rule defined by +field_name+ format
|
100
|
+
def self.format(field_name, value)
|
101
|
+
rule_for_field(field_name).call(value)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns ACH::Formatter::Rule rule, built from the corresponding format
|
105
|
+
# definition of a +field+
|
106
|
+
def self.rule_for_field(field)
|
107
|
+
compiled_rules[field] ||= Rule.new(RULES[field])
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns a hash of all rules that have been built at the moment of method call
|
111
|
+
def self.compiled_rules
|
112
|
+
@compiled_rules ||= {}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns a regular expression that can be used to split string into matched parts,
|
116
|
+
# that will correspond to passed +fields+ parameter. Used for ACH reading purposes
|
117
|
+
def self.matcher_for(fields)
|
118
|
+
/^#{fields.map{ |f| "(.{#{rule_for_field(f).length}})" }.join}$/
|
81
119
|
end
|
82
|
-
private :compile_rule
|
83
120
|
end
|
84
121
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ACH
|
2
|
+
# Parses string representation of rule and builds a +Proc+ based on it
|
3
|
+
class Formatter::Rule
|
4
|
+
# Captures formatting tokens from a rule string.
|
5
|
+
RULE_PARSER_REGEX = /^(<-|->)(\d+)(-)?(\|\w+)?$/
|
6
|
+
|
7
|
+
delegate :call, :[], :to => :@lambda
|
8
|
+
|
9
|
+
attr_reader :length
|
10
|
+
|
11
|
+
# Initializes instance with formatting data. Parses passed string for formatting
|
12
|
+
# values, such as width, justification, etc. As the result, builds a Proc object
|
13
|
+
# that will be used to format passed string according to formatting rule.
|
14
|
+
def initialize(rule)
|
15
|
+
just, width, pad, transf = rule.match(RULE_PARSER_REGEX)[1..-1]
|
16
|
+
@length = width.to_i
|
17
|
+
@padmethod = just == '<-' ? :ljust : :rjust
|
18
|
+
@padstr = @padmethod == :ljust ? ' ' : pad == '-' ? ' ' : '0'
|
19
|
+
@transform = transf[1..-1] if transf
|
20
|
+
|
21
|
+
@lambda = Proc.new do |val|
|
22
|
+
val = val.to_s
|
23
|
+
(@transform ? val.send(@transform) : val).send(@padmethod, @length, @padstr)[-@length..-1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/ach/record.rb
CHANGED
@@ -1,66 +1,15 @@
|
|
1
1
|
module ACH
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
super "Empty field '#{field}' for #{inspect}"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.fields *field_names
|
19
|
-
return @fields if field_names.empty?
|
20
|
-
@fields = field_names
|
21
|
-
@fields.each do |field|
|
22
|
-
raise UnknownField.new(field, name) unless Formatter::RULES.key?(field)
|
23
|
-
|
24
|
-
define_method(field) do |*args|
|
25
|
-
args.empty? ? fields[field] : (fields[field] = args.first)
|
26
|
-
end
|
27
|
-
|
28
|
-
define_method("#{field}=") do |val|
|
29
|
-
fields[field] = val
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.defaults default_values = nil
|
35
|
-
return @defaults if default_values.nil?
|
36
|
-
@defaults = default_values.freeze
|
37
|
-
end
|
38
|
-
|
39
|
-
def initialize fields = {}, &block
|
40
|
-
defaults.each do |key, value|
|
41
|
-
self.fields[key] = Proc === value ? value.call : value
|
42
|
-
end
|
43
|
-
self.fields.merge!(fields)
|
44
|
-
instance_eval(&block) if block
|
45
|
-
end
|
46
|
-
|
47
|
-
def defaults
|
48
|
-
self.class.defaults
|
49
|
-
end
|
50
|
-
|
51
|
-
def fields
|
52
|
-
@fields ||= {}
|
53
|
-
end
|
54
|
-
|
55
|
-
def []= name, val
|
56
|
-
fields[name] = val
|
57
|
-
end
|
58
|
-
|
59
|
-
def to_s!
|
60
|
-
self.class.fields.map do |name|
|
61
|
-
raise EmptyField.new(name) if @fields[name].nil?
|
62
|
-
Formatter.format name, @fields[name]
|
63
|
-
end.join
|
64
|
-
end
|
2
|
+
# Responsible for record-specific functionality for most record types,
|
3
|
+
# handled by several subclasses. Record types handled outside this module
|
4
|
+
# include: ACH::File::Header, ACH::File::Control, ACH::Batch::Header,
|
5
|
+
# ACH::Batch::Control
|
6
|
+
module Record
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :Addenda
|
10
|
+
autoload :Base
|
11
|
+
autoload :Dynamic
|
12
|
+
autoload :Entry
|
13
|
+
autoload :Tail
|
65
14
|
end
|
66
|
-
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ACH
|
2
|
+
module Record
|
3
|
+
# An ACH::Record::Addenda is an ACH::Record::Base record located inside of
|
4
|
+
# an ACH::Batch component. An addenda record should be preceded by an
|
5
|
+
# ACH::Record::Entry record.
|
6
|
+
#
|
7
|
+
# == Fields
|
8
|
+
#
|
9
|
+
# * record_type
|
10
|
+
# * addenda_type_code
|
11
|
+
# * payment_related_info
|
12
|
+
# * addenda_sequence_num
|
13
|
+
# * entry_details_sequence_num
|
14
|
+
class Addenda < Base
|
15
|
+
fields :record_type,
|
16
|
+
:addenda_type_code,
|
17
|
+
:payment_related_info,
|
18
|
+
:addenda_sequence_num,
|
19
|
+
:entry_details_sequence_num
|
20
|
+
|
21
|
+
defaults :record_type => BATCH_ADDENDA_RECORD_TYPE,
|
22
|
+
:addenda_type_code => 5
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module ACH
|
2
|
+
module Record
|
3
|
+
# Base class for all record entities (e.g. +ACH::File::Header+,
|
4
|
+
# +ACH::File::Control+, +ACH::Record::Entry+, others). Any record
|
5
|
+
# being declared should specify its fields, and optional default values.
|
6
|
+
# Except for +ACH::Record::Dynamic+, any declared field within a record
|
7
|
+
# should have corresponding rule defined under +ACH::Rule::Formatter+.
|
8
|
+
#
|
9
|
+
# == Example
|
10
|
+
#
|
11
|
+
# class Addenda < Record
|
12
|
+
# fields :record_type,
|
13
|
+
# :addenda_type_code,
|
14
|
+
# :payment_related_info,
|
15
|
+
# :addenda_sequence_num,
|
16
|
+
# :entry_details_sequence_num
|
17
|
+
#
|
18
|
+
# defaults :record_type => 7
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# addenda = ACH::Addenda.new(
|
22
|
+
# :addenda_type_code => '05',
|
23
|
+
# :payment_related_info => 'PAYMENT_RELATED_INFO',
|
24
|
+
# :addenda_sequence_num => 1,
|
25
|
+
# :entry_details_sequence_num => 1 )
|
26
|
+
# addenda.to_s! # => "705PAYMENT_RELATED_INFO 00010000001"
|
27
|
+
class Base
|
28
|
+
include Validations
|
29
|
+
include Constants
|
30
|
+
|
31
|
+
# Raises when unknown field passed to ACH::Record::Base.fields method.
|
32
|
+
class UnknownFieldError < ArgumentError
|
33
|
+
def initialize(field, class_name)
|
34
|
+
super "Unrecognized field '#{field}' in class #{class_name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Raises when value of record's field is not specified and there is no
|
39
|
+
# default value.
|
40
|
+
class EmptyFieldError < ArgumentError
|
41
|
+
def initialize(field, record)
|
42
|
+
super "Empty field '#{field}' for #{record}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Specifies fields of the record. Order is important. All fields
|
47
|
+
# must be declared in ACH::Formatter +RULES+. See class description
|
48
|
+
# for example
|
49
|
+
def self.fields(*field_names)
|
50
|
+
return @fields if field_names.empty?
|
51
|
+
@fields = field_names
|
52
|
+
@fields.each{ |field| define_field_methods(field) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets default values for fields. See class description for example
|
56
|
+
def self.defaults(default_values = nil)
|
57
|
+
return @defaults if default_values.nil?
|
58
|
+
@defaults = default_values.freeze
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.define_field_methods(field)
|
62
|
+
raise UnknownFieldError.new(field, name) unless Formatter::RULES.key?(field)
|
63
|
+
define_method(field) do |*args|
|
64
|
+
args.empty? ? @fields[field] : (@fields[field] = args.first)
|
65
|
+
end
|
66
|
+
define_method("#{field}=") do |val|
|
67
|
+
@fields[field] = val
|
68
|
+
end
|
69
|
+
end
|
70
|
+
private_class_method :define_field_methods
|
71
|
+
|
72
|
+
def self.from_s(string)
|
73
|
+
field_matcher_regexp = Formatter.matcher_for(fields)
|
74
|
+
new Hash[*fields.zip(string.match(field_matcher_regexp)[1..-1]).flatten]
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(fields = {}, &block)
|
78
|
+
defaults.each do |key, value|
|
79
|
+
self.fields[key] = Proc === value ? value.call : value
|
80
|
+
end
|
81
|
+
self.fields.merge!(fields)
|
82
|
+
instance_eval(&block) if block
|
83
|
+
end
|
84
|
+
|
85
|
+
# Builds a string from record object.
|
86
|
+
def to_s!
|
87
|
+
self.class.fields.map do |name|
|
88
|
+
raise EmptyFieldError.new(name, self) if @fields[name].nil?
|
89
|
+
Formatter.format name, @fields[name]
|
90
|
+
end.join
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a hash where key is field's name and value is field's value.
|
94
|
+
def fields
|
95
|
+
@fields ||= {}
|
96
|
+
end
|
97
|
+
|
98
|
+
def defaults
|
99
|
+
self.class.defaults
|
100
|
+
end
|
101
|
+
private :defaults
|
102
|
+
|
103
|
+
def []=(name, val)
|
104
|
+
fields[name] = val
|
105
|
+
end
|
106
|
+
private :[]=
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|