much-rails 0.1.3 → 0.2.0

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