ach_builder 0.0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|