Class: Nacha::Record::Base

Inherits:
Object
  • Object
show all
Includes:
Validations::FieldValidations
Defined in:
lib/nacha/record/base.rb

Overview

Base class for all Nacha records.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Validations::FieldValidations

included

Constructor Details

#initialize(opts = {}) ⇒ Base

:reek:ManualDispatch



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/nacha/record/base.rb', line 19

def initialize(opts = {})
  @children = []
  @parent = nil
  @errors = []
  @original_input_line = nil
  @line_number = nil
  @dirty = false
  @fields = {}
  create_fields_from_definition
  opts.each do |key, value|
    setter = "#{key}="
    # rubocop:disable GitlabSecurity/PublicSend
    public_send(setter, value) if value && respond_to?(setter)
    # rubocop:enable GitlabSecurity/PublicSend
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

:reek:TooManyStatements



306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/nacha/record/base.rb', line 306

def method_missing(method_name, *args, &block)
  method = method_name.to_s
  field_name = method.gsub(/=$/, '').to_sym
  field = @fields[field_name]
  return super unless field

  if method.end_with?('=')
    assign_field_data(field, args)
  else
    field
  end
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



14
15
16
# File 'lib/nacha/record/base.rb', line 14

def children
  @children
end

#fieldsObject (readonly)

Returns the value of attribute fields.



14
15
16
# File 'lib/nacha/record/base.rb', line 14

def fields
  @fields
end

#line_numberObject

:reek:Attribute



16
17
18
# File 'lib/nacha/record/base.rb', line 16

def line_number
  @line_number
end

#nameObject (readonly)

Returns the value of attribute name.



14
15
16
# File 'lib/nacha/record/base.rb', line 14

def name
  @name
end

#original_input_lineObject

Returns the value of attribute original_input_line.



14
15
16
# File 'lib/nacha/record/base.rb', line 14

def original_input_line
  @original_input_line
end

#parentObject

:reek:Attribute



16
17
18
# File 'lib/nacha/record/base.rb', line 16

def parent
  @parent
end

#validationsObject (readonly)

Returns the value of attribute validations.



14
15
16
# File 'lib/nacha/record/base.rb', line 14

def validations
  @validations
end

Class Method Details

.definitionObject



50
51
52
# File 'lib/nacha/record/base.rb', line 50

def definition
  @definition ||= {}
end

.matcherObject

:reek:TooManyStatements rubocop:disable Layout/BlockAlignment, rubocop:disable Style/StringConcatenation:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/nacha/record/base.rb', line 69

def matcher
  @matcher ||= begin
    output_started = false
    skipped_output = false
    Regexp.new('\A' + definition.values
                        .sort { |a,b| a[:position].first <=> b[:position].first }.reverse.collect do |field|
                 contents = field[:contents]
                 position = field[:position]
                 size = position.size
                 if contents =~ /\AC(.+)\z/
                   last_match = Regexp.last_match(1)
                   if last_match.match?(/  */) && !output_started
                     skipped_output = true
                     ''
                   else
                     output_started = true
                     last_match
                   end
                 elsif contents.match?(/\ANumeric\z/) || contents.match?(/\AYYMMDD\z/)
                   output_started = true
                   "[0-9 ]{#{size}}"
                 elsif output_started
                   ".{#{size}}"
                 else
                   skipped_output = true
                   ''
                 end
               end.reverse.join + (skipped_output ? '.*' : '') + '\z')
  end
end

.nacha_field(name, inclusion:, contents:, position:) ⇒ Object

:reek:LongParameterList, :reek:ManualDispatch



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/nacha/record/base.rb', line 38

def nacha_field(name, inclusion:, contents:, position:)
  Nacha.add_ach_record_type(self)
  definition[name] = { inclusion: inclusion,
                       contents: contents,
                       position: position,
                       name: name }
  validation_method = :"valid_#{name}"
  return unless respond_to?(validation_method)

  (validations[name] ||= []) << validation_method
end

.parse(ach_str) ⇒ Object

rubocop:enable Layout/BlockAlignment, rubocop:enable Style/StringConcatenation:



102
103
104
105
106
107
108
109
# File 'lib/nacha/record/base.rb', line 102

def parse(ach_str)
  rec = new(original_input_line: ach_str)
  ach_str.unpack(unpack_str).zip(rec.fields.values) do |input_data, field|
    field.data = input_data
  end
  rec.validate
  rec
end

.record_typeObject



111
112
113
# File 'lib/nacha/record/base.rb', line 111

def record_type
  Nacha.record_name(self)
end

.to_hObject

:reek:ManualDispatch, :reek:TooManyStatements



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/nacha/record/base.rb', line 116

def to_h
  fields = definition.transform_values do |field_def|
    {
      inclusion: field_def[:inclusion],
      contents: field_def[:contents],
      position: field_def[:position].to_s
    }
  end

  fields[:child_record_types] = child_record_types.to_a
  fields[:child_record_types] ||= []
  fields[:klass] = name.to_s

  { record_type.to_sym => fields }
end

.to_json(*_args) ⇒ Object



132
133
134
# File 'lib/nacha/record/base.rb', line 132

def to_json(*_args)
  JSON.pretty_generate(to_h)
end

.unpack_strObject



58
59
60
61
62
63
64
# File 'lib/nacha/record/base.rb', line 58

def unpack_str
  @unpack_str ||= definition.values
                    .sort { |a, b| a[:position].first <=> b[:position].first }
                    .collect do |field_def|
    Nacha::Field.unpack_str(field_def)
  end.join.freeze
end

.validationsObject



54
55
56
# File 'lib/nacha/record/base.rb', line 54

def validations
  @validations ||= {}
end

Instance Method Details

#add_error(err_string) ⇒ Object



301
302
303
# File 'lib/nacha/record/base.rb', line 301

def add_error(err_string)
  @errors << err_string
end

#create_fields_from_definitionObject



142
143
144
145
146
# File 'lib/nacha/record/base.rb', line 142

def create_fields_from_definition
  definition.each_pair do |field_name, field_def|
    @fields[field_name.to_sym] = Nacha::Field.new(field_def)
  end
end

#credit?Boolean

Note:

This method includes robust error handling. If a ‘NoMethodError` occurs (e.g., if `transaction_code` is undefinable or does not respond to `to_s` in an unexpected way) or a `NameError` occurs (e.g., if `transaction_code` or `CREDIT_TRANSACTION_CODES` is not defined in the current scope), the method gracefully rescues these exceptions and returns `false`. This default behavior ensures that an inability to determine the transaction type results in it being considered “not a credit”.

Checks if the current transaction code represents a credit transaction.

This method evaluates the ‘transaction_code` (which is expected to be an attribute or method available in the current context) against a predefined set of credit transaction codes.

Examples:

# Assuming transaction_code is "101" and CREDIT_TRANSACTION_CODES includes "101"
credit? #=> true

# Assuming transaction_code is "200" and CREDIT_TRANSACTION_CODES
# does not include "200"
credit? #=> false

# Assuming transaction_code is nil
credit? #=> false

See Also:



290
291
292
293
294
295
# File 'lib/nacha/record/base.rb', line 290

def credit?
  transaction_code &&
    CREDIT_TRANSACTION_CODES.include?(transaction_code.to_s)
rescue NoMethodError, NameError
  false
end

#debit?Boolean

Note:

This method includes robust error handling. If a ‘NoMethodError` occurs (e.g., if `transaction_code` is undefinable or does not respond to `to_s` in an unexpected way) or a `NameError` occurs (e.g., if `transaction_code` or `DEBIT_TRANSACTION_CODES` is not defined in the current scope), the method gracefully rescues these exceptions and returns `false`. This default behavior ensures that an inability to determine the transaction type results in it being considered “not a debit”.

Checks if the current transaction code represents a debit transaction.

This method evaluates the ‘transaction_code` (which is expected to be an attribute or method available in the current context) against a predefined set of debit transaction codes.

Examples:

# Assuming transaction_code is "201" and DEBIT_TRANSACTION_CODES includes "201"
debit? #=> true

# Assuming transaction_code is "100" and DEBIT_TRANSACTION_CODES
# does not include "100"
debit? #=> false

# Assuming transaction_code is nil
debit? #=> false

See Also:



250
251
252
253
254
255
# File 'lib/nacha/record/base.rb', line 250

def debit?
  transaction_code &&
    DEBIT_TRANSACTION_CODES.include?(transaction_code.to_s)
rescue NoMethodError, NameError
  false
end

#definitionObject



198
199
200
# File 'lib/nacha/record/base.rb', line 198

def definition
  self.class.definition
end

#errorsObject



297
298
299
# File 'lib/nacha/record/base.rb', line 297

def errors
  (@errors + @fields.values.map(&:errors)).flatten
end

#human_nameObject



152
153
154
# File 'lib/nacha/record/base.rb', line 152

def human_name
  @human_name ||= record_type.to_s.split('_').map(&:capitalize).join(' ')
end

#inspectObject



194
195
196
# File 'lib/nacha/record/base.rb', line 194

def inspect
  "#<#{self.class.name}> #{to_h}"
end

#record_typeObject



148
149
150
# File 'lib/nacha/record/base.rb', line 148

def record_type
  self.class.record_type
end

#respond_to_missing?(method_name) ⇒ Boolean



319
320
321
322
# File 'lib/nacha/record/base.rb', line 319

def respond_to_missing?(method_name, *)
  field_name = method_name.to_s.gsub(/=$/, '').to_sym
  definition[field_name] || super
end

#to_achObject



173
174
175
176
177
# File 'lib/nacha/record/base.rb', line 173

def to_ach
  @fields.keys.collect do |key|
    @fields[key].to_ach
  end.join
end

#to_hObject



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/nacha/record/base.rb', line 156

def to_h
  { nacha_record_type: record_type,
    metadata: {
      klass: self.class.name,
      errors: errors,
      line_number: @line_number,
      original_input_line: original_input_line
    } }.merge(
      @fields.keys.to_h do |key|
        [key, @fields[key].to_json_output]
      end)
end

#to_html(_opts = {}) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/nacha/record/base.rb', line 179

def to_html(_opts = {})
  record_error_class = nil

  field_html = @fields.values.collect do |field|
    record_error_class ||= 'error' if field.errors.any?
    field.to_html
  end.join
  "<div class=\"nacha-record tooltip #{record_type} #{record_error_class}\">" \
    "<span class=\"nacha-record-number\" data-name=\"record-number\">#{format('%05d',
      line_number)}&nbsp;|&nbsp</span>" \
    "#{field_html}" \
    "<span class=\"record-type\" data-name=\"record-type\">#{human_name}</span>" \
    "</div>"
end

#to_json(*_args) ⇒ Object



169
170
171
# File 'lib/nacha/record/base.rb', line 169

def to_json(*_args)
  JSON.pretty_generate(to_h)
end

#valid?Boolean

look for invalid fields, if none, then return true



212
213
214
215
# File 'lib/nacha/record/base.rb', line 212

def valid?
  validate
  errors.empty?
end

#validateObject



202
203
204
205
206
207
208
209
# File 'lib/nacha/record/base.rb', line 202

def validate
  # Run field-level validations first
  @fields.each_value(&:validate)
  # Then run record-level validations that might depend on multiple fields
  self.class.definition.each_key do |field|
    run_record_level_validations_for(field)
  end
end