much-rails 0.1.3 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e37cec5104bf60569eb8a0310184ed80c9083fa3fa0f68fcd02d0ee1a25aee92
4
- data.tar.gz: e2cffa2635a4fe025913d78ce78f81dcaba12b232129969ed0a3646a6d8c9928
3
+ metadata.gz: 612174b3a0ff8ace3d3f203a69a69586be9e2d2a7946ab20c8934e07209f8e95
4
+ data.tar.gz: d74beef0988406dc358b7bbccd6f5d23dc47e8f2997616b1323af8467c16dfff
5
5
  SHA512:
6
- metadata.gz: cfd22f4cdd2dd60540e417606990bdcd13ac92cb1e83d6175462ac37aa066de6c478c0c06dcb27af6112ec35eb0fcd1c0828375cdbbc9e8e60e6f1d4a2249cce
7
- data.tar.gz: e7c12b68abe00c404d4ab361ea1a5f49150a8877bf30619b7f01b478190cd14a027f3fec543c0d604339993a5803d9340251d53989d780d692015960e40cb075
6
+ metadata.gz: ffe4bf14bdafb94b01c7078b70d0f01eda42e45aedc5dcc935d616e2fc5149c7b1901577af8da172c2df457da8cbeed9dbd30327276dca94ce214f808fac51be
7
+ data.tar.gz: 77a0d880a84289e969c99644429458a991c125287205b086953f73c683e1b730842d727e42621561c1b9d7cfb122de2c7f025951b7e7cafc852f50efce3a345a
@@ -46,6 +46,14 @@ module MuchRails
46
46
  add_instance_config :action, method_name: :action
47
47
  add_instance_config :layout, method_name: :layout
48
48
 
49
+ def add_save_service_validation_error(exception_class, &block)
50
+ MuchRails::SaveService::ValidationErrors.add(exception_class, &block)
51
+ end
52
+
53
+ def add_destroy_service_validation_error(exception_class, &block)
54
+ MuchRails::DestroyService::ValidationErrors.add(exception_class, &block)
55
+ end
56
+
49
57
  class ActionConfig
50
58
  attr_accessor :namespace
51
59
  attr_accessor :sanitized_exception_classes
@@ -18,12 +18,49 @@ module MuchRails::DestroyService
18
18
 
19
19
  around_call do |receiver|
20
20
  receiver.call
21
- rescue MuchRails::Records::ValidateDestroy::DestructionInvalid => ex
21
+ rescue *MuchRails::DestroyService::ValidationErrors.exception_classes => ex
22
22
  set_the_return_value_for_the_call_method(
23
- MuchRails::Result.failure(
24
- exception: ex,
25
- validation_errors: ex&.destruction_errors.to_h,
26
- ),
23
+ MuchRails::DestroyService::ValidationErrors.result_for(ex),
24
+ )
25
+ end
26
+ end
27
+
28
+ module ValidationErrors
29
+ def self.add(exception_class, &block)
30
+ service_validation_errors.add(exception_class, &block)
31
+ end
32
+
33
+ def self.exception_classes
34
+ service_validation_errors.exception_classes
35
+ end
36
+
37
+ def self.result_for(ex)
38
+ service_validation_errors.result_for(ex)
39
+ end
40
+
41
+ def self.service_validation_errors
42
+ @service_validation_errors ||=
43
+ MuchRails::ServiceValidationErrors
44
+ .new
45
+ .tap do |e|
46
+ e.add(MuchRails::Records::DestructionInvalid) do |ex|
47
+ MuchRails::DestroyService::FailureResult.new(
48
+ record: ex.record,
49
+ exception: ex,
50
+ validation_errors: ex.errors.to_h,
51
+ validation_error_messages: ex.error_full_messages.to_a,
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ module FailureResult
59
+ def self.new(exception:, validation_errors:, **kargs)
60
+ MuchResult.failure(
61
+ exception: exception,
62
+ validation_errors: validation_errors,
63
+ **kargs,
27
64
  )
28
65
  end
29
66
  end
@@ -27,6 +27,13 @@ class MuchRails::Railtie < Rails::Railtie
27
27
  # This should be `false` in all other envs so proper HTTP response
28
28
  # statuses are returned.
29
29
  config.action.raise_response_exceptions = Rails.env.development?
30
+
31
+ config.save_service_validation_error_exception_classes = [
32
+ ActiveRecord::RecordInvalid,
33
+ ]
34
+ config.destroy_service_validation_error_exception_classes = [
35
+ MuchRails::Records::DestructionInvalid,
36
+ ]
30
37
  end
31
38
  end
32
39
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/error"
3
4
  require "much-rails/mixin"
4
5
 
5
6
  module MuchRails; end
@@ -33,7 +34,7 @@ module MuchRails::Records::ValidateDestroy
33
34
 
34
35
  def destroy!(as: :base, validate: true)
35
36
  if validate && !destroyable?
36
- raise DestructionInvalid.new(self, field_name: as)
37
+ raise MuchRails::Records::DestructionInvalid.new(self, field_name: as)
37
38
  end
38
39
 
39
40
  # `_raise_record_not_destroyed` is from ActiveRecord. This logic was
@@ -61,22 +62,31 @@ module MuchRails::Records::ValidateDestroy
61
62
  raise NotImplementedError
62
63
  end
63
64
  end
65
+ end
66
+
67
+ class MuchRails::Records::DestructionInvalid < StandardError
68
+ attr_reader :record, :errors, :error_full_messages
64
69
 
65
- class DestructionInvalid < StandardError
66
- attr_reader :record, :destruction_errors
70
+ def initialize(record = nil, field_name: :base)
71
+ super(record&.destruction_error_messages.to_a.join("\n"))
67
72
 
68
- def initialize(record = nil, field_name: :base)
69
- super(record&.destruction_error_messages.to_a.join("\n"))
73
+ @record = record
70
74
 
71
- @record = record
75
+ messages = record&.destruction_error_messages.to_a
76
+ @errors =
77
+ if messages.any?
78
+ { field_name.to_sym => messages }
79
+ else
80
+ {}
81
+ end
72
82
 
73
- messages = record&.destruction_error_messages.to_a
74
- @destruction_errors =
75
- if messages.any?
76
- { field_name.to_sym => messages }
77
- else
78
- {}
83
+ @error_full_messages =
84
+ if field_name == :base
85
+ messages
86
+ else
87
+ messages.map do |m|
88
+ ActiveModel::Error.new(@record, field_name, m).full_message
79
89
  end
80
- end
90
+ end
81
91
  end
82
92
  end
@@ -4,6 +4,7 @@ require "active_record"
4
4
  require "much-rails/mixin"
5
5
  require "much-rails/result"
6
6
  require "much-rails/service"
7
+ require "much-rails/service_validation_errors"
7
8
 
8
9
  module MuchRails; end
9
10
 
@@ -17,14 +18,50 @@ module MuchRails::SaveService
17
18
 
18
19
  around_call do |receiver|
19
20
  receiver.call
20
- rescue ActiveRecord::RecordInvalid => ex
21
+ rescue *MuchRails::SaveService::ValidationErrors.exception_classes => ex
21
22
  set_the_return_value_for_the_call_method(
22
- MuchRails::Result.failure(
23
- record: ex.record,
24
- exception: ex,
25
- validation_errors: ex.record&.errors.to_h,
26
- validation_error_messages: ex.record&.errors&.full_messages.to_a,
27
- ),
23
+ MuchRails::SaveService::ValidationErrors.result_for(ex),
24
+ )
25
+ end
26
+ end
27
+
28
+ module ValidationErrors
29
+ def self.add(exception_class, &block)
30
+ service_validation_errors.add(exception_class, &block)
31
+ end
32
+
33
+ def self.exception_classes
34
+ service_validation_errors.exception_classes
35
+ end
36
+
37
+ def self.result_for(ex)
38
+ service_validation_errors.result_for(ex)
39
+ end
40
+
41
+ def self.service_validation_errors
42
+ @service_validation_errors ||=
43
+ MuchRails::ServiceValidationErrors
44
+ .new
45
+ .tap do |e|
46
+ e.add(ActiveRecord::RecordInvalid) do |ex|
47
+ MuchRails::SaveService::FailureResult.new(
48
+ record: ex.record,
49
+ exception: ex,
50
+ validation_errors: ex.record&.errors.to_h,
51
+ validation_error_messages:
52
+ ex.record&.errors&.full_messages.to_a,
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ module FailureResult
60
+ def self.new(exception:, validation_errors:, **kargs)
61
+ MuchResult.failure(
62
+ exception: exception,
63
+ validation_errors: validation_errors,
64
+ **kargs,
28
65
  )
29
66
  end
30
67
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+
5
+ class MuchRails::ServiceValidationErrors
6
+ attr_reader :hash
7
+
8
+ def initialize
9
+ @hash = {}
10
+ end
11
+
12
+ def add(exception_class, &block)
13
+ unless exception_class < Exception
14
+ raise(ArgumentError, "#{exception_class} is not an Exception")
15
+ end
16
+
17
+ @hash[exception_class] = block
18
+ end
19
+
20
+ def exception_classes
21
+ @hash.keys
22
+ end
23
+
24
+ def result_for(ex)
25
+ result_proc = nil
26
+ exception_class = ex.class
27
+ loop do
28
+ result_proc = @hash[exception_class]
29
+ break unless result_proc.nil?
30
+
31
+ exception_class =
32
+ if exception_class.superclass.nil?
33
+ raise ArgumentError, "#{ex.class} hasn't been configured"
34
+ else
35
+ exception_class.superclass
36
+ end
37
+ end
38
+
39
+ result_proc.call(ex)
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MuchRails
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -44,45 +44,189 @@ module MuchRails::DestroyService
44
44
  end
45
45
  end
46
46
 
47
- class DestructionInvalidErrorSetupTests < ReceiverTests
48
- desc "with a MuchRails::Records::ValidateDestroy::DestructionInvalid error"
47
+ class ReceiverInitTests < ReceiverTests
48
+ desc "when init"
49
+ subject{ receiver_class.new(exception: exception) }
50
+
51
+ let(:exception){ nil }
52
+ end
53
+
54
+ class ReceiverInitAroundCallCallbackTests < ReceiverInitTests
55
+ desc "around_call callback"
56
+ setup do
57
+ Assert.stub(
58
+ MuchRails::DestroyService::ValidationErrors,
59
+ :exception_classes,
60
+ ){ exception_classes }
61
+ Assert.stub_on_call(
62
+ MuchRails::DestroyService::ValidationErrors,
63
+ :result_for,
64
+ ) do |call|
65
+ @result_for_call = call
66
+ validation_error_result
67
+ end
68
+ end
69
+
70
+ let(:exception){ exceptions.sample }
71
+ let(:exceptions) do
72
+ [
73
+ RuntimeError.new(Factory.string),
74
+ ArgumentError.new(Factory.string),
75
+ MuchRails::Records::DestructionInvalid.new(FakeRecord.new),
76
+ ]
77
+ end
78
+ let(:exception_classes){ exceptions.map(&:class) }
79
+ let(:validation_error_result) do
80
+ MuchResult.failure(error: result_error_message)
81
+ end
82
+ let(:result_error_message){ Factory.string }
83
+
84
+ should "rescue raised exceptions and "\
85
+ "use the ValidationErrors to build a result" do
86
+ result = subject.call
87
+
88
+ assert_that(result.failure?).is_true
89
+ assert_that(result.error).equals(result_error_message)
90
+ end
91
+ end
92
+
93
+ class ValidationErrorsTests < UnitTests
94
+ desc "ValidationErrors"
95
+ subject{ unit_class::ValidationErrors }
96
+
97
+ should have_imeths :add, :exception_classes, :result_for
98
+ should have_imeths :service_validation_errors
99
+
100
+ should "know its ServiceValidationErrors" do
101
+ assert_that(subject.service_validation_errors)
102
+ .is_an_instance_of(MuchRails::ServiceValidationErrors)
103
+ assert_that(subject.service_validation_errors.exception_classes)
104
+ .includes(MuchRails::Records::DestructionInvalid)
105
+ end
106
+ end
107
+
108
+ class ValidationErrorsAddTests < ValidationErrorsTests
109
+ desc ".add"
110
+
111
+ setup do
112
+ Assert.stub_on_call(subject.service_validation_errors, :add) do |call|
113
+ @add_call = call
114
+ end
115
+ end
116
+
117
+ let(:exception_class){ StandardError }
118
+ let(:block){ proc{ MuchResult.failure } }
119
+
120
+ should "call #add on its ServiceValidationErrors" do
121
+ subject.add(exception_class, &block)
122
+ assert_that(@add_call.args).equals([exception_class])
123
+ assert_that(@add_call.block).is(block)
124
+ end
125
+ end
126
+
127
+ class ValidationErrorsExceptionClassesTests < ValidationErrorsTests
128
+ desc ".exception_classes"
129
+
49
130
  setup do
50
- Assert.stub(exception, :record){ record }
131
+ Assert.stub(
132
+ subject.service_validation_errors,
133
+ :exception_classes,
134
+ ){ exception_classes }
135
+ end
136
+
137
+ let(:exception_classes) do
138
+ [
139
+ StandardError,
140
+ ArgumentError,
141
+ ]
51
142
  end
52
143
 
53
- let(:exception) do
54
- MuchRails::Records::ValidateDestroy::DestructionInvalid.new
144
+ should "call #exception_classes on its ServiceValidationErrors" do
145
+ assert_that(subject.exception_classes).is(exception_classes)
55
146
  end
56
147
  end
57
148
 
58
- class ExceptionWithDestructionErrorMessagesTests <
59
- DestructionInvalidErrorSetupTests
60
- desc "with an exception record that has destruction_error_messages"
149
+ class ValidationErrorsResultForTests < ValidationErrorsTests
150
+ desc ".result_for"
61
151
 
62
- let(:record){ @fake_record ||= FakeRecord.new }
152
+ setup do
153
+ Assert.stub_on_call(
154
+ subject.service_validation_errors,
155
+ :result_for,
156
+ ) do |call|
157
+ @result_for_call = call
158
+ result_for_result
159
+ end
160
+ end
63
161
 
64
- should "return a failure result with the exception and validation_errors" do
65
- result = subject.call(exception: exception)
162
+ let(:exception){ StandardError.new(Factory.string) }
163
+ let(:result_for_result){ MuchResult.failure }
164
+
165
+ should "call #result_for on its ServiceValidationErrors" do
166
+ assert_that(subject.result_for(exception)).is(result_for_result)
167
+ assert_that(@result_for_call.args).equals([exception])
168
+ end
169
+ end
170
+
171
+ class ValidationErrorsResultForDestructionInvalidTests < ValidationErrorsTests
172
+ desc "when .result_for is passed an MuchRails::Records::DestructionInvalid"
173
+
174
+ let(:exception){ MuchRails::Records::DestructionInvalid.new(record) }
175
+ let(:record){ FakeRecord.new }
176
+
177
+ should "return a failure result with the record and validation errors" do
178
+ result = subject.result_for(exception)
66
179
 
67
180
  assert_that(result.failure?).is_true
68
181
  assert_that(result.exception).equals(exception)
69
- assert_that(result.validation_errors).equals(exception.destruction_errors)
182
+ assert_that(result.validation_errors).equals(exception.errors.to_h)
183
+ assert_that(result.validation_error_messages)
184
+ .equals(exception.error_full_messages.to_a)
70
185
  end
71
186
  end
72
187
 
73
- class ExceptionWithoutDestructionErrorMessagesTests <
74
- DestructionInvalidErrorSetupTests
75
- desc "with an exception record that has no destruction_error_messages"
188
+ class FailureResultTests < UnitTests
189
+ desc "FailureResult"
190
+ subject{ unit_class::FailureResult }
191
+
192
+ setup do
193
+ Assert.stub_tap_on_call(MuchResult, :failure) do |_, call|
194
+ @much_result_failure_call = call
195
+ end
196
+ end
76
197
 
77
- let(:record){ nil }
198
+ let(:exception){ StandardError.new(Factory.string) }
199
+ let(:validation_errors){ { Factory.symbol => Factory.string } }
200
+ let(:custom_value){ Factory.string }
78
201
 
79
- should "return a failure result with the exception and empty "\
80
- "validation_errors" do
81
- result = subject.call(exception: exception)
202
+ should have_imeths :new
82
203
 
204
+ should "use MuchResult.failure to build a result" do
205
+ result =
206
+ subject.new(
207
+ exception: exception,
208
+ validation_errors: validation_errors,
209
+ custom: custom_value,
210
+ )
83
211
  assert_that(result.failure?).is_true
84
- assert_that(result.exception).equals(exception)
85
- assert_that(result.validation_errors).equals({})
212
+ assert_that(@much_result_failure_call.kargs)
213
+ .equals(
214
+ exception: exception,
215
+ validation_errors: validation_errors,
216
+ custom: custom_value,
217
+ )
218
+ end
219
+
220
+ should "raise an error without an exception or validation errors" do
221
+ assert_that{
222
+ subject.new(validation_errors: validation_errors)
223
+ }.raises(ArgumentError)
224
+ end
225
+
226
+ should "raise an error without an exception or validation errors" do
227
+ assert_that{
228
+ subject.new(exception: exception)
229
+ }.raises(ArgumentError)
86
230
  end
87
231
  end
88
232
 
@@ -23,10 +23,51 @@ module MuchRails
23
23
  desc ".config"
24
24
  subject{ unit_class.config }
25
25
 
26
- should have_imeths :action
26
+ should have_imeths :action, :layout
27
+ should have_imeths :add_save_service_validation_error
28
+ should have_imeths :add_destroy_service_validation_error
27
29
 
28
30
  should "be configured as expected" do
29
31
  assert_that(subject.action).is_not_nil
32
+ assert_that(subject.layout).is_not_nil
33
+ end
34
+ end
35
+
36
+ class ConfigServiceValidationErrorTests < ConfigTests
37
+ setup do
38
+ Assert.stub_on_call(
39
+ MuchRails::SaveService::ValidationErrors,
40
+ :add,
41
+ ) do |call|
42
+ @save_service_validation_errors_add_call = call
43
+ end
44
+ Assert.stub_on_call(
45
+ MuchRails::DestroyService::ValidationErrors,
46
+ :add,
47
+ ) do |call|
48
+ @destroy_service_validation_errors_add_call = call
49
+ end
50
+ end
51
+
52
+ let(:exception_class){ StandardError }
53
+ let(:block){ proc{ MuchResult.failure } }
54
+
55
+ should "know how to add an exception class "\
56
+ "to the save service validation errors" do
57
+ subject.add_save_service_validation_error(exception_class, &block)
58
+ assert_that(@save_service_validation_errors_add_call.args)
59
+ .equals([exception_class])
60
+ assert_that(@save_service_validation_errors_add_call.block)
61
+ .is(block)
62
+ end
63
+
64
+ should "know how to add an exception class "\
65
+ "to the destroy service validation errors" do
66
+ subject.add_destroy_service_validation_error(exception_class, &block)
67
+ assert_that(@destroy_service_validation_errors_add_call.args)
68
+ .equals([exception_class])
69
+ assert_that(@destroy_service_validation_errors_add_call.block)
70
+ .is(block)
30
71
  end
31
72
  end
32
73
 
@@ -34,7 +34,7 @@ module MuchRails::Records::AlwaysDestroyable
34
34
 
35
35
  assert_that(subject.destroy).is_true
36
36
 
37
- # won't raise MuchRails::Records::ValidateDestroy::DestructionInvalid
37
+ # won't raise MuchRails::Records::DestructionInvalid
38
38
  subject.destroy!
39
39
 
40
40
  assert_that(subject.destroyable?).is_true
@@ -32,7 +32,7 @@ module MuchRails::Records::NotDestroyable
32
32
  assert_that(subject.destroy).is_false
33
33
 
34
34
  assert_that(->{ subject.destroy! })
35
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
35
+ .raises(MuchRails::Records::DestructionInvalid)
36
36
 
37
37
  assert_that(subject.destroyable?).is_false
38
38
  end
@@ -62,17 +62,17 @@ module MuchRails::Records::ValidateDestroy
62
62
 
63
63
  exception =
64
64
  assert_that(->{ subject.destroy! })
65
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
65
+ .raises(MuchRails::Records::DestructionInvalid)
66
66
  assert_that(exception.message)
67
67
  .equals("TEST DESTRUCTION ERROR1\nTEST DESTRUCTION ERROR2")
68
- assert_that(exception.destruction_errors)
68
+ assert_that(exception.errors)
69
69
  .equals(base: subject.destruction_error_messages.to_a)
70
70
  assert_that(subject.super_destroy_called).is_true
71
71
 
72
72
  exception =
73
73
  assert_that(->{ subject.destroy!(as: :thing) })
74
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
75
- assert_that(exception.destruction_errors)
74
+ .raises(MuchRails::Records::DestructionInvalid)
75
+ assert_that(exception.errors)
76
76
  .equals(thing: subject.destruction_error_messages.to_a)
77
77
 
78
78
  subject.super_destroy_called = nil
@@ -138,9 +138,85 @@ module MuchRails::Records::ValidateDestroy
138
138
  end
139
139
  end
140
140
 
141
+ class DestructionInvalidTests < UnitTests
142
+ desc "MuchRails::Records::DestructionInvalid"
143
+ subject{ exception_class }
144
+
145
+ let(:exception_class){ MuchRails::Records::DestructionInvalid }
146
+
147
+ let(:record){ FakeRecordClass.new }
148
+
149
+ should "be configured as expected" do
150
+ assert_that(subject < StandardError).is_true
151
+ end
152
+ end
153
+
154
+ class DestructionInvalidInitTests < DestructionInvalidTests
155
+ desc "when init"
156
+ subject{ exception_class.new(record) }
157
+
158
+ should have_readers :record, :errors, :error_full_messages
159
+
160
+ should "know its attributes when destruction errors exist" do
161
+ record.destruction_errors_exist = true
162
+ record.validate_destroy
163
+
164
+ assert_that(subject.message)
165
+ .equals(record.destruction_error_messages.to_a.join("\n"))
166
+ assert_that(subject.record).is(record)
167
+ assert_that(subject.errors).equals(
168
+ base: record.destruction_error_messages.to_a,
169
+ )
170
+ assert_that(subject.error_full_messages)
171
+ .equals(record.destruction_error_messages.to_a)
172
+ end
173
+
174
+ should "know its attributes when destruction errors don't exist" do
175
+ assert_that(subject.message).equals("")
176
+ assert_that(subject.record).is(record)
177
+ assert_that(subject.errors).equals({})
178
+ assert_that(subject.error_full_messages).equals([])
179
+ end
180
+ end
181
+
182
+ class DestructionInvalidInitWithFieldNameTests < DestructionInvalidTests
183
+ desc "when init with a field name"
184
+ subject{ exception_class.new(record, field_name: field_name) }
185
+
186
+ let(:field_name){ Factory.string }
187
+
188
+ should "know its attributes when destruction errors exist" do
189
+ record.destruction_errors_exist = true
190
+ record.validate_destroy
191
+
192
+ assert_that(subject.message)
193
+ .equals(record.destruction_error_messages.to_a.join("\n"))
194
+ assert_that(subject.record).is(record)
195
+ assert_that(subject.errors).equals(
196
+ field_name.to_sym => record.destruction_error_messages.to_a,
197
+ )
198
+ assert_that(subject.error_full_messages)
199
+ .equals(
200
+ record
201
+ .destruction_error_messages
202
+ .map do |m|
203
+ ActiveModel::Error.new(record, field_name, m).full_message
204
+ end,
205
+ )
206
+ end
207
+
208
+ should "know its attributes when destruction errors don't exist" do
209
+ assert_that(subject.message).equals("")
210
+ assert_that(subject.record).is(record)
211
+ assert_that(subject.errors).equals({})
212
+ assert_that(subject.error_full_messages).equals([])
213
+ end
214
+ end
215
+
141
216
  require "active_record"
142
217
 
143
218
  class FakeRecordBaseClass
219
+ extend ActiveRecord::Translation
144
220
  # Include ActiveRecord::Persistence to test the `destroy!` logic
145
221
  # (the `_raise_record_not_destroyed` method) that we had to re-implement in
146
222
  # the MuchRails::Records::ValidateDestroy.
@@ -150,6 +226,10 @@ module MuchRails::Records::ValidateDestroy
150
226
 
151
227
  attr_accessor :destruction_errors_exist
152
228
 
229
+ def self.base_class?
230
+ true
231
+ end
232
+
153
233
  def destroy
154
234
  @super_destroy_called = true
155
235
  end
@@ -44,49 +44,209 @@ module MuchRails::SaveService
44
44
  end
45
45
  end
46
46
 
47
- class RecordInvalidErrorSetupTests < ReceiverTests
48
- desc "with an ActiveRecord::RecordInvalid error"
47
+ class ReceiverInitTests < ReceiverTests
48
+ desc "when init"
49
+ subject{ receiver_class.new(exception: exception) }
50
+
51
+ let(:exception){ nil }
52
+ end
53
+
54
+ class ReceiverInitAroundCallCallbackTests < ReceiverInitTests
55
+ desc "around_call callback"
49
56
  setup do
50
- Assert.stub(exception, :record){ record }
57
+ Assert.stub(
58
+ MuchRails::SaveService::ValidationErrors,
59
+ :exception_classes,
60
+ ){ exception_classes }
61
+ Assert.stub_on_call(
62
+ MuchRails::SaveService::ValidationErrors,
63
+ :result_for,
64
+ ) do |call|
65
+ @result_for_call = call
66
+ validation_error_result
67
+ end
51
68
  end
52
69
 
53
- let(:exception){ ActiveRecord::RecordInvalid.new }
70
+ let(:exception){ exceptions.sample }
71
+ let(:exceptions) do
72
+ [
73
+ RuntimeError.new(Factory.string),
74
+ ArgumentError.new(Factory.string),
75
+ ActiveRecord::RecordInvalid.new(FakeRecord.new),
76
+ ]
77
+ end
78
+ let(:exception_classes){ exceptions.map(&:class) }
79
+ let(:validation_error_result) do
80
+ MuchResult.failure(error: result_error_message)
81
+ end
82
+ let(:result_error_message){ Factory.string }
83
+
84
+ should "rescue raised exceptions and "\
85
+ "use the ValidationErrors to build a result" do
86
+ result = subject.call
87
+
88
+ assert_that(result.failure?).is_true
89
+ assert_that(result.error).equals(result_error_message)
90
+ end
54
91
  end
55
92
 
56
- class ExceptionWithRecordErrorsTests < RecordInvalidErrorSetupTests
57
- desc "with an exception that has record errors"
93
+ class ValidationErrorsTests < UnitTests
94
+ desc "ValidationErrors"
95
+ subject{ unit_class::ValidationErrors }
58
96
 
59
- let(:record){ @fake_record ||= FakeRecord.new }
97
+ should have_imeths :add, :exception_classes, :result_for
98
+ should have_imeths :service_validation_errors
99
+
100
+ should "know its ServiceValidationErrors" do
101
+ assert_that(subject.service_validation_errors)
102
+ .is_an_instance_of(MuchRails::ServiceValidationErrors)
103
+ assert_that(subject.service_validation_errors.exception_classes)
104
+ .includes(ActiveRecord::RecordInvalid)
105
+ end
106
+ end
60
107
 
61
- should "return a failure result with the exception and record errors" do
62
- result = subject.call(exception: exception)
108
+ class ValidationErrorsAddTests < ValidationErrorsTests
109
+ desc ".add"
63
110
 
64
- assert_that(result.failure?).is_true
65
- assert_that(result.exception).equals(exception)
66
- assert_that(result.validation_errors)
67
- .equals(some_field: %w[ERROR1 ERROR2])
68
- assert_that(result.validation_error_messages)
69
- .equals(["some_field ERROR1", "some_field ERROR2"])
111
+ setup do
112
+ Assert.stub_on_call(subject.service_validation_errors, :add) do |call|
113
+ @add_call = call
114
+ end
115
+ end
116
+
117
+ let(:exception_class){ StandardError }
118
+ let(:block){ proc{ MuchResult.failure } }
119
+
120
+ should "call #add on its ServiceValidationErrors" do
121
+ subject.add(exception_class, &block)
122
+ assert_that(@add_call.args).equals([exception_class])
123
+ assert_that(@add_call.block).is(block)
124
+ end
125
+ end
126
+
127
+ class ValidationErrorsExceptionClassesTests < ValidationErrorsTests
128
+ desc ".exception_classes"
129
+
130
+ setup do
131
+ Assert.stub(
132
+ subject.service_validation_errors,
133
+ :exception_classes,
134
+ ){ exception_classes }
135
+ end
136
+
137
+ let(:exception_classes) do
138
+ [
139
+ StandardError,
140
+ ArgumentError,
141
+ ]
142
+ end
143
+
144
+ should "call #exception_classes on its ServiceValidationErrors" do
145
+ assert_that(subject.exception_classes).is(exception_classes)
146
+ end
147
+ end
148
+
149
+ class ValidationErrorsResultForTests < ValidationErrorsTests
150
+ desc ".result_for"
151
+
152
+ setup do
153
+ Assert.stub_on_call(
154
+ subject.service_validation_errors,
155
+ :result_for,
156
+ ) do |call|
157
+ @result_for_call = call
158
+ result_for_result
159
+ end
160
+ end
161
+
162
+ let(:exception){ StandardError.new(Factory.string) }
163
+ let(:result_for_result){ MuchResult.failure }
164
+
165
+ should "call #result_for on its ServiceValidationErrors" do
166
+ assert_that(subject.result_for(exception)).is(result_for_result)
167
+ assert_that(@result_for_call.args).equals([exception])
70
168
  end
71
169
  end
72
170
 
73
- class ExceptionWithoutRecordErrorsTests < RecordInvalidErrorSetupTests
74
- desc "with an exception that has no record errors"
171
+ class ValidationErrorsResultForRecordInvalidTests < ValidationErrorsTests
172
+ desc "when .result_for is passed an ActiveRecord::RecordInvalid"
173
+
174
+ let(:exception){ ActiveRecord::RecordInvalid.new(record) }
175
+ let(:record){ FakeRecord.new }
75
176
 
76
- let(:record){ nil }
177
+ let(:no_record_exception){ ActiveRecord::RecordInvalid.new }
178
+
179
+ should "return a failure result with the record and validation errors" do
180
+ result = subject.result_for(exception)
181
+
182
+ assert_that(result.failure?).is_true
183
+ assert_that(result.exception).equals(exception)
184
+ assert_that(result.validation_errors).equals(record.errors.to_h)
185
+ assert_that(result.validation_error_messages)
186
+ .equals(record.errors.full_messages.to_a)
187
+ end
77
188
 
78
189
  should "return a failure result with the exception and empty "\
79
190
  "record errors" do
80
- result = subject.call(exception: exception)
191
+ result = subject.result_for(no_record_exception)
81
192
 
82
193
  assert_that(result.failure?).is_true
83
- assert_that(result.exception).equals(exception)
194
+ assert_that(result.exception).equals(no_record_exception)
84
195
  assert_that(result.validation_errors).equals({})
85
196
  assert_that(result.validation_error_messages).equals([])
86
197
  end
87
198
  end
88
199
 
200
+ class FailureResultTests < UnitTests
201
+ desc "FailureResult"
202
+ subject{ unit_class::FailureResult }
203
+
204
+ setup do
205
+ Assert.stub_tap_on_call(MuchResult, :failure) do |_, call|
206
+ @much_result_failure_call = call
207
+ end
208
+ end
209
+
210
+ let(:exception){ StandardError.new(Factory.string) }
211
+ let(:validation_errors){ { Factory.symbol => Factory.string } }
212
+ let(:custom_value){ Factory.string }
213
+
214
+ should have_imeths :new
215
+
216
+ should "use MuchResult.failure to build a result" do
217
+ result =
218
+ subject.new(
219
+ exception: exception,
220
+ validation_errors: validation_errors,
221
+ custom: custom_value,
222
+ )
223
+ assert_that(result.failure?).is_true
224
+ assert_that(@much_result_failure_call.kargs)
225
+ .equals(
226
+ exception: exception,
227
+ validation_errors: validation_errors,
228
+ custom: custom_value,
229
+ )
230
+ end
231
+
232
+ should "raise an error without an exception or validation errors" do
233
+ assert_that{
234
+ subject.new(validation_errors: validation_errors)
235
+ }.raises(ArgumentError)
236
+ end
237
+
238
+ should "raise an error without an exception or validation errors" do
239
+ assert_that{
240
+ subject.new(exception: exception)
241
+ }.raises(ArgumentError)
242
+ end
243
+ end
244
+
89
245
  class FakeRecord
246
+ def self.i18n_scope
247
+ "fake_record"
248
+ end
249
+
90
250
  def errors
91
251
  Errors.new
92
252
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MuchRails::ServiceValidationErrors
4
+ class UnitTests < Assert::Context
5
+ desc "MuchRails::ServiceValidationErrors"
6
+ subject{ unit_class }
7
+
8
+ let(:unit_class){ MuchRails::ServiceValidationErrors }
9
+ end
10
+
11
+ class InitTests < UnitTests
12
+ desc "when init"
13
+ subject{ unit_class.new }
14
+
15
+ should have_readers :hash
16
+ should have_imeths :add, :exception_classes, :result_for
17
+ end
18
+
19
+ class InitAddTests < InitTests
20
+ desc "#add"
21
+
22
+ let(:exception_class){ StandardError }
23
+ let(:block){ proc{ MuchResult.failure } }
24
+
25
+ let(:invalid_exception_class) do
26
+ [
27
+ Class.new,
28
+ Factory.string,
29
+ nil,
30
+ ].sample
31
+ end
32
+
33
+ should "add an exception class and block" do
34
+ subject.add(exception_class, &block)
35
+ assert_that(subject.hash[exception_class]).is(block)
36
+ end
37
+
38
+ should "raise an error when it's not passed an Exception" do
39
+ assert_that{
40
+ subject.add(invalid_exception_class, &block)
41
+ }.raises(ArgumentError)
42
+ end
43
+ end
44
+
45
+ class InitExceptionClassesTests < InitTests
46
+ desc "#exception_classes"
47
+
48
+ setup do
49
+ exception_classes.each do |exception_class|
50
+ subject.add(exception_class, &block)
51
+ end
52
+ end
53
+
54
+ let(:exception_classes) do
55
+ [
56
+ StandardError,
57
+ ArgumentError,
58
+ RuntimeError,
59
+ ]
60
+ end
61
+ let(:block){ proc{ MuchResult.failure } }
62
+
63
+ should "return all the added exception classes" do
64
+ assert_that(subject.exception_classes).equals(exception_classes)
65
+ end
66
+ end
67
+
68
+ class InitResultForTests < InitTests
69
+ desc "#result_for"
70
+
71
+ setup do
72
+ subject.add(exception_class, &block)
73
+ end
74
+
75
+ let(:exception){ exception_class.new(Factory.string) }
76
+ let(:exception_class){ StandardError }
77
+ let(:block) do
78
+ proc{ MuchResult.failure(error_message: failure_result_error_message) }
79
+ end
80
+ let(:failure_result_error_message){ Factory.string }
81
+
82
+ let(:inherited_exception){ RuntimeError.new(Factory.string) }
83
+
84
+ let(:invalid_exception){ Exception.new(Factory.string) }
85
+
86
+ should "return the result of calling the added block "\
87
+ "for the exception class" do
88
+ result = subject.result_for(exception)
89
+ assert_that(result.failure?).is_true
90
+ assert_that(result.error_message).equals(failure_result_error_message)
91
+ end
92
+
93
+ should "return the result of calling the added block "\
94
+ "for an exception class ancestor" do
95
+ result = subject.result_for(exception)
96
+ assert_that(result.failure?).is_true
97
+ assert_that(result.error_message).equals(failure_result_error_message)
98
+ end
99
+
100
+ should "raise an error if a block hasn't been added "\
101
+ "for the exception class" do
102
+ assert_that{
103
+ subject.result_for(invalid_exception)
104
+ }.raises(ArgumentError)
105
+ end
106
+ end
107
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: much-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kelly Redding
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-01-15 00:00:00.000000000 Z
12
+ date: 2021-01-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: much-style-guide
@@ -291,6 +291,7 @@ files:
291
291
  - lib/much-rails/save_action.rb
292
292
  - lib/much-rails/save_service.rb
293
293
  - lib/much-rails/service.rb
294
+ - lib/much-rails/service_validation_errors.rb
294
295
  - lib/much-rails/time.rb
295
296
  - lib/much-rails/version.rb
296
297
  - lib/much-rails/view_models.rb
@@ -343,6 +344,7 @@ files:
343
344
  - test/unit/save_action_tests.rb
344
345
  - test/unit/save_service_tests.rb
345
346
  - test/unit/service_tests.rb
347
+ - test/unit/service_validation_errors_tests.rb
346
348
  - test/unit/time_tests.rb
347
349
  - test/unit/view_models/breadcrumb_tests.rb
348
350
  - test/unit/wrap_and_call_method_tests.rb
@@ -367,7 +369,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
367
369
  - !ruby/object:Gem::Version
368
370
  version: '0'
369
371
  requirements: []
370
- rubygems_version: 3.2.4
372
+ rubyforge_project:
373
+ rubygems_version: 2.7.6.2
371
374
  signing_key:
372
375
  specification_version: 4
373
376
  summary: Rails utilities.
@@ -416,6 +419,7 @@ test_files:
416
419
  - test/unit/save_action_tests.rb
417
420
  - test/unit/save_service_tests.rb
418
421
  - test/unit/service_tests.rb
422
+ - test/unit/service_validation_errors_tests.rb
419
423
  - test/unit/time_tests.rb
420
424
  - test/unit/view_models/breadcrumb_tests.rb
421
425
  - test/unit/wrap_and_call_method_tests.rb