nacha 0.1.8 → 0.1.12

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -3
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +41 -0
  5. data/README.md +1 -1
  6. data/exe/nacha +50 -20
  7. data/lib/nacha/aba_number.rb +17 -14
  8. data/lib/nacha/ach_date.rb +15 -8
  9. data/lib/nacha/field.rb +69 -58
  10. data/lib/nacha/has_errors.rb +12 -8
  11. data/lib/nacha/numeric.rb +13 -10
  12. data/lib/nacha/parser.rb +33 -16
  13. data/lib/nacha/parser_context.rb +4 -9
  14. data/lib/nacha/record/ack_entry_detail.rb +15 -8
  15. data/lib/nacha/record/adv_batch_control.rb +13 -6
  16. data/lib/nacha/record/adv_entry_detail.rb +3 -2
  17. data/lib/nacha/record/adv_file_control.rb +10 -9
  18. data/lib/nacha/record/adv_file_header.rb +12 -7
  19. data/lib/nacha/record/arc_entry_detail.rb +2 -2
  20. data/lib/nacha/record/base.rb +128 -108
  21. data/lib/nacha/record/batch_control.rb +13 -7
  22. data/lib/nacha/record/batch_header.rb +20 -11
  23. data/lib/nacha/record/batch_header_record_type.rb +1 -1
  24. data/lib/nacha/record/boc_entry_detail.rb +3 -2
  25. data/lib/nacha/record/ccd_addenda.rb +2 -2
  26. data/lib/nacha/record/ccd_entry_detail.rb +3 -2
  27. data/lib/nacha/record/cie_addenda.rb +2 -2
  28. data/lib/nacha/record/cie_entry_detail.rb +5 -3
  29. data/lib/nacha/record/ctx_addenda.rb +2 -2
  30. data/lib/nacha/record/ctx_corporate_entry_detail.rb +2 -2
  31. data/lib/nacha/record/dne_addenda.rb +2 -2
  32. data/lib/nacha/record/dne_entry_detail.rb +6 -4
  33. data/lib/nacha/record/enr_addenda.rb +3 -2
  34. data/lib/nacha/record/enr_entry_detail.rb +4 -3
  35. data/lib/nacha/record/fifth_iat_addenda.rb +8 -4
  36. data/lib/nacha/record/file_control.rb +9 -5
  37. data/lib/nacha/record/file_control_record_type.rb +1 -1
  38. data/lib/nacha/record/file_header.rb +13 -7
  39. data/lib/nacha/record/file_header_record_type.rb +1 -1
  40. data/lib/nacha/record/filler.rb +3 -3
  41. data/lib/nacha/record/filler_record_type.rb +3 -1
  42. data/lib/nacha/record/first_iat_addenda.rb +3 -2
  43. data/lib/nacha/record/fourth_iat_addenda.rb +7 -4
  44. data/lib/nacha/record/iat_batch_header.rb +5 -3
  45. data/lib/nacha/record/iat_entry_detail.rb +7 -4
  46. data/lib/nacha/record/iat_foreign_coorespondent_bank_information_addenda.rb +10 -6
  47. data/lib/nacha/record/iat_remittance_information_addenda.rb +3 -2
  48. data/lib/nacha/record/mte_addenda.rb +4 -3
  49. data/lib/nacha/record/mte_entry_detail.rb +5 -3
  50. data/lib/nacha/record/pop_entry_detail.rb +3 -2
  51. data/lib/nacha/record/pos_addenda.rb +6 -3
  52. data/lib/nacha/record/pos_entry_detail.rb +5 -3
  53. data/lib/nacha/record/ppd_addenda.rb +3 -2
  54. data/lib/nacha/record/ppd_entry_detail.rb +5 -3
  55. data/lib/nacha/record/rck_entry_detail.rb +3 -2
  56. data/lib/nacha/record/second_iat_addenda.rb +3 -2
  57. data/lib/nacha/record/seventh_iat_addenda.rb +3 -2
  58. data/lib/nacha/record/shr_addenda.rb +5 -3
  59. data/lib/nacha/record/shr_entry_detail.rb +3 -2
  60. data/lib/nacha/record/sixth_iat_addenda.rb +5 -3
  61. data/lib/nacha/record/tel_entry_detail.rb +5 -3
  62. data/lib/nacha/record/third_iat_addenda.rb +3 -2
  63. data/lib/nacha/record/trc_entry_detail.rb +3 -2
  64. data/lib/nacha/record/trx_addenda.rb +3 -2
  65. data/lib/nacha/record/trx_entry_detail.rb +5 -3
  66. data/lib/nacha/record/validations/field_validations.rb +26 -14
  67. data/lib/nacha/record/validations/record_validations.rb +2 -1
  68. data/lib/nacha/record/web_addenda.rb +3 -2
  69. data/lib/nacha/record/web_entry_detail.rb +5 -3
  70. data/lib/nacha/record/xck_entry_detail.rb +3 -2
  71. data/lib/nacha/version.rb +4 -1
  72. data/lib/nacha.rb +84 -20
  73. data/nacha.gemspec +13 -10
  74. metadata +38 -21
data/lib/nacha/parser.rb CHANGED
@@ -1,33 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'byebug'
3
4
  require 'nacha'
4
5
  require 'nacha/parser_context'
5
6
 
7
+ # Nacha Parser - deal with figuring out what record type a line is
6
8
  class Nacha::Parser
7
- DEFAULT_RECORD_TYPES = ['Nacha::Record::FileHeader',
8
- 'Nacha::Record::Filler'].freeze
9
+ DEFAULT_RECORD_TYPES = [
10
+ 'Nacha::Record::FileHeader',
11
+ 'Nacha::Record::AdvFileHeader',
12
+ 'Nacha::Record::Filler'
13
+ ].freeze
9
14
 
10
15
  attr_reader :context
11
16
 
12
17
  def initialize
13
18
  @context = Nacha::ParserContext.new
14
- reset!
15
19
  end
16
20
 
17
- def reset!; end
18
-
19
21
  def parse_file(file)
20
- @context.parser_started_at = Time.now
22
+ @context.parser_started_at = Time.now.utc
21
23
  @context.file_name = file
22
24
  parse_string(file.read)
23
25
  end
24
26
 
27
+ def detect_possible_record_types(line)
28
+ Nacha.ach_record_types.filter_map do |record_type|
29
+ record_type if record_type.matcher.match?(line)
30
+ end
31
+ end
32
+
25
33
  def parse_string(str)
26
34
  line_num = -1
27
35
  records = []
28
- @context.parser_started_at ||= Time.now
29
- str.scan(/(.{94}|(\A[^\n]+))/).each do |line|
30
- line = line.first.strip
36
+ @context.parser_started_at ||= Time.now.utc
37
+ str.scan(/(.{0,94})[\r\n]*/).each do |line|
38
+ line = line.compact.first.strip
39
+ next if line.empty? || line.start_with?('#') # Skip empty lines and comments
40
+
31
41
  line_num += 1
32
42
  @context.line_number = line_num
33
43
  @context.line_length = line.length
@@ -39,12 +49,13 @@ class Nacha::Parser
39
49
  def process(line, line_num, previous = nil)
40
50
  @context.line_errors = []
41
51
  parent = previous
42
- record = nil
43
52
 
44
53
  record_types = valid_record_types(parent)
54
+
45
55
  while record_types
46
- record = parse_by_types(line, record_types)
56
+ record = parse_first_by_types(line, record_types)
47
57
  break if record || !parent
58
+
48
59
  record.validate if record
49
60
  parent = parent.parent
50
61
  record_types = valid_record_types(parent)
@@ -67,11 +78,17 @@ class Nacha::Parser
67
78
  child.parent = parent
68
79
  end
69
80
 
70
- def parse_by_types(line, record_types)
71
- record_types.detect do |rt|
72
- record_type = Object.const_get(rt)
73
- record = record_type.parse(line) if record_type.matcher =~ line
74
- return record if record
81
+ def parse_first_by_types(line, record_types)
82
+ record_types.lazy.filter_map do |rt|
83
+ record_type = rt.is_a?(Class) ? rt : Object.const_get(rt)
84
+ record_type.parse(line) if record_type.matcher.match?(line)
85
+ end.first
86
+ end
87
+
88
+ def parse_all_by_types(line, record_types)
89
+ record_types.filter_map do |rt|
90
+ record_type = rt.is_a?(Class) ? rt : Object.const_get(rt)
91
+ record_type.parse(line) if record_type.matcher.match?(line)
75
92
  end
76
93
  end
77
94
  end
@@ -2,13 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Nacha::ParserContext
5
- attr_accessor :file_name
6
- attr_accessor :line_number
7
- attr_accessor :line_length
8
- attr_accessor :line_errors
9
- attr_accessor :parser_started_at
10
- attr_accessor :parser_ended_at # nil 'till the end of parsing
11
- attr_accessor :previous_record
5
+ attr_accessor :file_name, :line_number, :line_length, :line_errors,
6
+ :parser_started_at, :previous_record,
7
+ :parser_ended_at # nil 'till the end of parsing
12
8
  attr_reader :validated
13
9
 
14
10
  def initialize(opts = {})
@@ -16,7 +12,7 @@ class Nacha::ParserContext
16
12
  @line_number = opts[:line_number] || 0
17
13
  @line_length = opts[:line_length] || 0
18
14
  @current_line_errors = []
19
- @parser_started_at = Time.now
15
+ @parser_started_at = Time.now.utc
20
16
  @parser_ended_at = nil
21
17
  @previous_record = nil
22
18
  @validated = false
@@ -29,5 +25,4 @@ class Nacha::ParserContext
29
25
  def valid?
30
26
  @valid ||= errors.empty?
31
27
  end
32
-
33
28
  end
@@ -1,22 +1,29 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/detail_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/detail_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
9
9
  class AckEntryDetail < Nacha::Record::Base
10
10
  include DetailRecordType
11
+
11
12
  nacha_field :record_type_code, inclusion: 'M', contents: 'C6', position: 1..1
12
13
  nacha_field :transaction_code, inclusion: 'M', contents: 'Numeric', position: 2..3
13
- nacha_field :receiving_dfi_identification, inclusion: 'M', contents: 'TTTTAAAAC', position: 4..12
14
- nacha_field :dfi_account_number, inclusion: 'R', contents: 'Alphameric', position: 13..29
14
+ nacha_field :receiving_dfi_identification,
15
+ inclusion: 'M', contents: 'TTTTAAAAC', position: 4..12
16
+ nacha_field :dfi_account_number,
17
+ inclusion: 'R', contents: 'Alphameric', position: 13..29
15
18
  nacha_field :amount, inclusion: 'M', contents: '$$$$$$$$¢¢', position: 30..39
16
- nacha_field :originl_entry_trace_number, inclusion: 'M', contents: 'Numeric', position: 40..54
17
- nacha_field :receiving_company_name, inclusion: 'R', contents: 'Alphameric', position: 55..76
18
- nacha_field :discretionary_data, inclusion: 'O', contents: 'Alphameric', position: 77..78
19
- nacha_field :addenda_record_indicator, inclusion: 'M', contents: 'Numeric', position: 79..79
19
+ nacha_field :originl_entry_trace_number,
20
+ inclusion: 'M', contents: 'Numeric', position: 40..54
21
+ nacha_field :receiving_company_name,
22
+ inclusion: 'R', contents: 'Alphameric', position: 55..76
23
+ nacha_field :discretionary_data,
24
+ inclusion: 'O', contents: 'Alphameric', position: 77..78
25
+ nacha_field :addenda_record_indicator,
26
+ inclusion: 'M', contents: 'Numeric', position: 79..79
20
27
  nacha_field :trace_number, inclusion: 'M', contents: 'Numeric', position: 80..94
21
28
  end
22
29
  end
@@ -1,20 +1,27 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/batch_control_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/batch_control_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
9
+ # The AdvBatchControl record is used to control the batch of adv entry detail records.
9
10
  class AdvBatchControl < Nacha::Record::Base
11
+ include BatchControlRecordType
12
+
10
13
  nacha_field :record_type_code, inclusion: 'M', contents: 'C8', position: 1..1
11
14
  nacha_field :service_class_code, inclusion: 'M', contents: 'Numeric', position: 2..4
12
15
  nacha_field :entry_addenda_count, inclusion: 'M', contents: 'Numeric', position: 5..10
13
16
  nacha_field :entry_hash, inclusion: 'M', contents: 'Numeric', position: 11..20
14
- nacha_field :total_debit_entry_dollar_amount, inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 21..40
15
- nacha_field :total_credit_entry_dollar_amount, inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 41..60
16
- nacha_field :ach_operator_data, inclusion: 'O', contents: 'Alphameric', position: 61..79
17
- nacha_field :originating_dfi_identification, inclusion: 'M', contents: 'TTTAAAA', position: 80..87
17
+ nacha_field :total_debit_entry_dollar_amount,
18
+ inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 21..40
19
+ nacha_field :total_credit_entry_dollar_amount,
20
+ inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 41..60
21
+ nacha_field :ach_operator_data,
22
+ inclusion: 'O', contents: 'Alphameric', position: 61..79
23
+ nacha_field :originating_dfi_identification,
24
+ inclusion: 'M', contents: 'TTTAAAA', position: 80..87
18
25
  nacha_field :batch_number, inclusion: 'M', contents: 'Numeric', position: 88..94
19
26
  end
20
27
  end
@@ -1,13 +1,14 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/detail_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/detail_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
9
9
  class AdvEntryDetail < Nacha::Record::Base
10
10
  include DetailRecordType
11
+
11
12
  nacha_field :record_type_code, inclusion: 'M', contents: 'C6', position: 1..1
12
13
  nacha_field :transaction_code, inclusion: 'M', contents: 'Numeric', position: 2..3
13
14
  nacha_field :receiving_dfi_identification, inclusion: 'M', contents: 'TTTTAAAAC', position: 4..12
@@ -1,24 +1,25 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/file_control_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/file_control_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
9
9
  class AdvFileControl < Nacha::Record::Base
10
+ include FileControlRecordType
11
+
10
12
  nacha_field :record_type_code, inclusion: 'M', contents: 'C9', position: 1..1
11
13
  nacha_field :batch_count, inclusion: 'M', contents: 'Numeric', position: 2..7
12
14
  nacha_field :block_count, inclusion: 'M', contents: 'Numeric', position: 8..13
13
- nacha_field :entry_addenda_count, inclusion: 'M', contents: 'Numeric', position: 14..21
15
+ nacha_field :entry_addenda_count,
16
+ inclusion: 'M', contents: 'Numeric', position: 14..21
14
17
  nacha_field :entry_hash, inclusion: 'M', contents: 'Numeric', position: 22..31
15
- nacha_field :total_debit_entry_dollar_amount_in_file, inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 32..51
16
- nacha_field :total_credit_entry_dollar_amount_in_file, inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 52..71
18
+ nacha_field :total_debit_entry_dollar_amount_in_file,
19
+ inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 32..51
20
+ nacha_field :total_credit_entry_dollar_amount_in_file,
21
+ inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 52..71
17
22
  nacha_field :reserved, inclusion: 'M', contents: 'C', position: 72..94
18
-
19
- # def initialize
20
- # create_fields_from_definition
21
- # end
22
23
  end
23
24
  end
24
25
  end
@@ -1,26 +1,31 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/file_header_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/file_header_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
9
9
  class AdvFileHeader < Nacha::Record::Base
10
10
  include FileHeaderRecordType
11
+
11
12
  nacha_field :record_type_code, inclusion: 'M', contents: 'C1', position: 1..1
12
13
  nacha_field :priority_code, inclusion: 'R', contents: 'Numeric', position: 2..3
13
- nacha_field :immediate_destination, inclusion: 'M', contents: 'bTTTTAAAAC', position: 4..13
14
- nacha_field :immediate_origin, inclusion: 'M', contents: 'bTTTTAAAAC', position: 14..23
14
+ nacha_field :immediate_destination, inclusion: 'M',
15
+ contents: 'bTTTTAAAAC', position: 4..13
16
+ nacha_field :immediate_origin, inclusion: 'M',
17
+ contents: 'bTTTTAAAAC', position: 14..23
15
18
  nacha_field :file_creation_date, inclusion: 'M', contents: 'YYMMDD', position: 24..29
16
19
  nacha_field :file_creation_time, inclusion: 'M', contents: 'HHMM', position: 30..33
17
20
  nacha_field :file_id_modifier, inclusion: 'M', contents: 'A-Z0-9', position: 34..34
18
21
  nacha_field :record_size, inclusion: 'M', contents: 'C094', position: 35..37
19
22
  nacha_field :blocking_factor, inclusion: 'M', contents: 'C10', position: 38..39
20
23
  nacha_field :format_code, inclusion: 'M', contents: 'C1', position: 40..40
21
- nacha_field :immediate_destination_name, inclusion: 'M', contents: 'Alphameric', position: 41..63
22
- nacha_field :immediate_origin_name, inclusion: 'M', contents: 'Alphameric', position: 64..86
23
- nacha_field :reference_code, inclusion: 'M', contents: 'Alphameric', position: 87..94
24
+ nacha_field :immediate_destination_name, inclusion: 'M',
25
+ contents: 'Alphameric', position: 41..63
26
+ nacha_field :immediate_origin_name, inclusion: 'M',
27
+ contents: 'Alphameric', position: 64..86
28
+ nacha_field :reference_code, inclusion: 'M', contents: 'CADV FILE', position: 87..94
24
29
  end
25
30
  end
26
31
  end
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'nacha/record/base.rb'
5
- require 'nacha/record/detail_record_type.rb'
4
+ require 'nacha/record/base'
5
+ require 'nacha/record/detail_record_type'
6
6
 
7
7
  module Nacha
8
8
  module Record
@@ -4,42 +4,47 @@ require 'json'
4
4
  require 'nacha/field'
5
5
  require 'nacha/record/validations/field_validations'
6
6
 
7
+ # :reek:TooManyInstanceVariables, :reek:TooManyMethods
7
8
  module Nacha
8
9
  module Record
10
+ # Base class for all Nacha records.
9
11
  class Base
10
12
  include Validations::FieldValidations
11
13
 
12
- attr_accessor :children, :parent, :fields
13
- attr_reader :name, :validations, :errors, :original_input_line
14
- attr_accessor :line_number
14
+ attr_reader :children, :name, :validations, :original_input_line, :fields
15
+ # :reek:Attribute
16
+ attr_accessor :parent, :line_number
15
17
 
18
+ # :reek:ManualDispatch
16
19
  def initialize(opts = {})
17
20
  @children = []
21
+ @parent = nil
18
22
  @errors = []
19
23
  @original_input_line = nil
24
+ @line_number = nil
25
+ @dirty = false
26
+ @fields = {}
20
27
  create_fields_from_definition
21
- opts.each do |k, v|
22
- setter = "#{k}="
23
- next unless respond_to? setter
24
-
28
+ opts.each do |key, value|
29
+ setter = "#{key}="
25
30
  # rubocop:disable GitlabSecurity/PublicSend
26
- send(setter, v) unless v.nil?
31
+ public_send(setter, value) if value && respond_to?(setter)
27
32
  # rubocop:enable GitlabSecurity/PublicSend
28
33
  end
29
34
  end
30
35
 
31
36
  class << self
37
+ # :reek:LongParameterList, :reek:ManualDispatch
32
38
  def nacha_field(name, inclusion:, contents:, position:)
39
+ Nacha.add_ach_record_type(self)
33
40
  definition[name] = { inclusion: inclusion,
34
41
  contents: contents,
35
42
  position: position,
36
- name: name}
37
- validation_method = "valid_#{name}".to_sym
43
+ name: name }
44
+ validation_method = :"valid_#{name}"
38
45
  return unless respond_to?(validation_method)
39
46
 
40
- validations[name] ||= []
41
- validations[name] << validation_method
42
- Nacha.add_ach_record_type(self)
47
+ (validations[name] ||= []) << validation_method
43
48
  end
44
49
 
45
50
  def definition
@@ -51,90 +56,94 @@ module Nacha
51
56
  end
52
57
 
53
58
  def unpack_str
54
- @unpack_str ||= definition.values.collect do |d|
55
- Nacha::Field.unpack_str(d)
59
+ @unpack_str ||= definition.values.collect do |field_def|
60
+ Nacha::Field.unpack_str(field_def)
56
61
  end.join.freeze
57
62
  end
58
63
 
59
- def old_matcher
60
- @matcher ||=
61
- Regexp.new('\A' + definition.values.collect do |d|
62
- if d[:contents] =~ /\AC(.+)\z/ || d['contents'] =~ /\AC(.+)\z/
63
- Regexp.last_match(1)
64
- else
65
- '.' * (d[:position] || d['position']).size
66
- end
67
- end.join + '\z')
68
- end
69
-
70
- # A more strict matcher that accounts for numeric and date fields
71
- # and allows for spaces in those fields, but not alphabetic characters
72
- # Also matches strings that might not be long enough.
73
- #
74
- # Processes the definition in reverse order to allow later fields to be
75
- # skipped if they are not present in the input string.
64
+ # :reek:TooManyStatements
65
+ # rubocop:disable Layout/BlockAlignment,
66
+ # rubocop:disable Style/StringConcatenation:
76
67
  def matcher
77
- return @matcher if @matcher
78
-
79
- output_started = false
80
- skipped_output = false
81
- @matcher ||=
82
- Regexp.new('\A' + definition.values.reverse.collect do |d|
83
- if d[:contents] =~ /\AC(.+)\z/
68
+ @matcher ||= begin
69
+ output_started = false
70
+ skipped_output = false
71
+ Regexp.new('\A' + definition.values.reverse.collect do |field|
72
+ contents = field[:contents]
73
+ position = field[:position]
74
+ size = position.size
75
+ if contents =~ /\AC(.+)\z/
84
76
  last_match = Regexp.last_match(1)
85
- if last_match =~ /\A *\z/
77
+ if last_match.match?(/ */) && !output_started
86
78
  skipped_output = true
87
79
  ''
88
80
  else
89
81
  output_started = true
90
82
  last_match
91
83
  end
92
- elsif d[:contents] =~ /\ANumeric\z/
84
+ elsif contents.match?(/\ANumeric\z/) || contents.match?(/\AYYMMDD\z/)
93
85
  output_started = true
94
- '[0-9 ]' + "{#{(d[:position] || d['position']).size}}"
95
- elsif d[:contents] =~ /\AYYMMDD\z/
96
- if output_started
97
- '[0-9 ]' + "{#{(d[:position] || d['position']).size}}"
98
- else
99
- skipped_output = true
100
- ''
101
- end
86
+ '[0-9 ]' + "{#{size}}"
87
+ elsif output_started
88
+ '.' + "{#{size}}"
102
89
  else
103
- if output_started
104
- '.' + "{#{(d[:position] || d['position']).size}}"
105
- else
106
- skipped_output = true
107
- ''
108
- end
90
+ skipped_output = true
91
+ ''
109
92
  end
110
93
  end.reverse.join + (skipped_output ? '.*' : '') + '\z')
94
+ end
111
95
  end
112
-
96
+ # rubocop:enable Layout/BlockAlignment,
97
+ # rubocop:enable Style/StringConcatenation:
113
98
 
114
99
  def parse(ach_str)
115
- rec = new
100
+ rec = new(original_input_line: ach_str)
116
101
  ach_str.unpack(unpack_str).zip(rec.fields.values) do |input_data, field|
117
102
  field.data = input_data
118
103
  end
119
- rec.original_input_line = ach_str
120
104
  rec.validate
121
105
  rec
122
106
  end
123
- end # end class methods
124
107
 
108
+ def record_type
109
+ Nacha.record_name(self)
110
+ end
111
+
112
+ # :reek:ManualDispatch, :reek:TooManyStatements
113
+ def to_h
114
+ fields = definition.transform_values do |field_def|
115
+ {
116
+ inclusion: field_def[:inclusion],
117
+ contents: field_def[:contents],
118
+ position: field_def[:position].to_s
119
+ }
120
+ end
121
+
122
+ fields[:child_record_types] = child_record_types.to_a
123
+ fields[:child_record_types] ||= []
124
+ fields[:klass] = name.to_s
125
+
126
+ { record_type.to_sym => fields }
127
+ end
128
+
129
+ def to_json(*_args)
130
+ JSON.pretty_generate(to_h)
131
+ end
132
+ end
133
+
134
+ # :reek:FeatureEnvy
125
135
  def original_input_line=(line)
126
136
  @original_input_line = line.dup if line.is_a?(String)
127
137
  end
128
138
 
129
139
  def create_fields_from_definition
130
- @fields ||= {}
131
140
  definition.each_pair do |field_name, field_def|
132
141
  @fields[field_name.to_sym] = Nacha::Field.new(field_def)
133
142
  end
134
143
  end
135
144
 
136
145
  def record_type
137
- Nacha.record_name(self.class)
146
+ self.class.record_type
138
147
  end
139
148
 
140
149
  def human_name
@@ -144,14 +153,14 @@ module Nacha
144
153
  def to_h
145
154
  { nacha_record_type: record_type,
146
155
  metadata: {
156
+ klass: self.class.name,
147
157
  errors: errors,
148
158
  line_number: @line_number,
149
159
  original_input_line: original_input_line
150
- }
151
- }.merge(
152
- @fields.keys.map do |key|
153
- [key, @fields[key].to_json_output]
154
- end.to_h)
160
+ } }.merge(
161
+ @fields.keys.to_h do |key|
162
+ [key, @fields[key].to_json_output]
163
+ end)
155
164
  end
156
165
 
157
166
  def to_json(*_args)
@@ -164,17 +173,18 @@ module Nacha
164
173
  end.join
165
174
  end
166
175
 
167
- def to_html(opts = {})
176
+ def to_html(_opts = {})
168
177
  record_error_class = nil
169
178
 
170
- field_html = @fields.keys.collect do |key|
171
- record_error_class ||= 'error' if @fields[key].errors.any?
172
- @fields[key].to_html
179
+ field_html = @fields.values.collect do |field|
180
+ record_error_class ||= 'error' if field.errors.any?
181
+ field.to_html
173
182
  end.join
174
- "<div class=\"nacha-record tooltip #{record_type} #{record_error_class}\">" +
175
- "<span class=\"nacha-field\" data-name=\"record-number\">#{"%05d" % [line_number]}&nbsp;|&nbsp</span>" +
176
- field_html +
177
- "<span class=\"record-type\" data-name=\"record-type\">#{human_name}</span>" +
183
+ "<div class=\"nacha-record tooltip #{record_type} #{record_error_class}\">" \
184
+ "<span class=\"nacha-field\" data-name=\"record-number\">#{format('%05d',
185
+ line_number)}&nbsp;|&nbsp</span>" \
186
+ "#{field_html}" \
187
+ "<span class=\"record-type\" data-name=\"record-type\">#{human_name}</span>" \
178
188
  "</div>"
179
189
  end
180
190
 
@@ -188,19 +198,10 @@ module Nacha
188
198
 
189
199
  def validate
190
200
  # Run field-level validations first
191
- @fields.values.each(&:validate)
192
-
201
+ @fields.each_value(&:validate)
193
202
  # Then run record-level validations that might depend on multiple fields
194
- self.class.definition.keys.each do |field|
195
- next unless self.class.validations[field]
196
-
197
- # rubocop:disable GitlabSecurity/PublicSend
198
- field_data = send(field)
199
-
200
- self.class.validations[field].map do |validation_method|
201
- self.class.send(validation_method, field_data)
202
- end
203
- # rubocop:enable GitlabSecurity/PublicSend
203
+ self.class.definition.each_key do |field|
204
+ run_record_level_validations_for(field)
204
205
  end
205
206
  end
206
207
 
@@ -224,7 +225,8 @@ module Nacha
224
225
  # # Assuming transaction_code is "201" and DEBIT_TRANSACTION_CODES includes "201"
225
226
  # debit? #=> true
226
227
  #
227
- # # Assuming transaction_code is "100" and DEBIT_TRANSACTION_CODES does not include "100"
228
+ # # Assuming transaction_code is "100" and DEBIT_TRANSACTION_CODES
229
+ # # does not include "100"
228
230
  # debit? #=> false
229
231
  #
230
232
  # # Assuming transaction_code is nil
@@ -263,7 +265,8 @@ module Nacha
263
265
  # # Assuming transaction_code is "101" and CREDIT_TRANSACTION_CODES includes "101"
264
266
  # credit? #=> true
265
267
  #
266
- # # Assuming transaction_code is "200" and CREDIT_TRANSACTION_CODES does not include "200"
268
+ # # Assuming transaction_code is "200" and CREDIT_TRANSACTION_CODES
269
+ # # does not include "200"
267
270
  # credit? #=> false
268
271
  #
269
272
  # # Assuming transaction_code is nil
@@ -289,37 +292,54 @@ module Nacha
289
292
  end
290
293
 
291
294
  def errors
292
- (@errors + @fields.values.map { |field| field.errors }).flatten
295
+ (@errors + @fields.values.map(&:errors)).flatten
293
296
  end
294
297
 
295
-
296
298
  def add_error(err_string)
297
299
  @errors << err_string
298
300
  end
299
301
 
300
- def respond_to?(method_name, include_private = false)
301
- field_name = method_name.to_s.gsub(/=$/, '').to_sym
302
+ # :reek:TooManyStatements
303
+ def method_missing(method_name, *args, &block)
304
+ method = method_name.to_s
305
+ field_name = method.gsub(/=$/, '').to_sym
306
+ field = @fields[field_name]
307
+ return super unless field
302
308
 
303
- definition[field_name] || super
309
+ if method.end_with?('=')
310
+ assign_field_data(field, args)
311
+ else
312
+ field
313
+ end
304
314
  end
305
315
 
306
- def method_missing(method_name, *args, &block)
316
+ def respond_to_missing?(method_name, *)
307
317
  field_name = method_name.to_s.gsub(/=$/, '').to_sym
308
- is_assignment = (/[^=]*=$/o =~ method_name.to_s)
309
- if @fields[field_name]
310
- if is_assignment
311
- # @fields[field_name].send(:data=,*args)
312
- # rubocop:disable GitlabSecurity/PublicSend
313
- @fields[field_name].public_send(:data=, *args)
314
- # rubocop:enable GitlabSecurity/PublicSend
315
- @dirty = true
316
- else
317
- # @fields[field_name].data
318
- @fields[field_name]
319
- end
320
- else
321
- super
318
+ definition[field_name] || super
319
+ end
320
+
321
+ private
322
+
323
+ def assign_field_data(field, args)
324
+ # rubocop:disable GitlabSecurity/PublicSend
325
+ field.public_send(:data=, *args)
326
+ # rubocop:enable GitlabSecurity/PublicSend
327
+ @dirty = true
328
+ end
329
+
330
+ # :reek:TooManyStatements
331
+ def run_record_level_validations_for(field)
332
+ klass = self.class
333
+ validations = klass.validations[field]
334
+ return unless validations
335
+
336
+ # rubocop:disable GitlabSecurity/PublicSend
337
+ field_data = send(field)
338
+
339
+ validations.each do |validation_method|
340
+ klass.send(validation_method, field_data)
322
341
  end
342
+ # rubocop:enable GitlabSecurity/PublicSend
323
343
  end
324
344
  end
325
345
  end