iknow_view_models 3.2.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -0
  3. data/Appraisals +6 -6
  4. data/Rakefile +5 -5
  5. data/gemfiles/rails_5_2.gemfile +5 -5
  6. data/gemfiles/rails_6_0.gemfile +5 -5
  7. data/iknow_view_models.gemspec +40 -39
  8. data/lib/iknow_view_models.rb +9 -7
  9. data/lib/iknow_view_models/version.rb +1 -1
  10. data/lib/view_model.rb +17 -14
  11. data/lib/view_model/access_control.rb +5 -2
  12. data/lib/view_model/access_control/composed.rb +10 -9
  13. data/lib/view_model/access_control/open.rb +2 -0
  14. data/lib/view_model/access_control/read_only.rb +2 -0
  15. data/lib/view_model/access_control/tree.rb +11 -6
  16. data/lib/view_model/access_control_error.rb +4 -1
  17. data/lib/view_model/active_record.rb +12 -11
  18. data/lib/view_model/active_record/association_data.rb +2 -1
  19. data/lib/view_model/active_record/association_manipulation.rb +6 -4
  20. data/lib/view_model/active_record/cache.rb +4 -2
  21. data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
  22. data/lib/view_model/active_record/controller_base.rb +4 -1
  23. data/lib/view_model/active_record/nested_controller_base.rb +1 -0
  24. data/lib/view_model/active_record/update_context.rb +8 -6
  25. data/lib/view_model/active_record/update_data.rb +32 -30
  26. data/lib/view_model/active_record/update_operation.rb +17 -13
  27. data/lib/view_model/active_record/visitor.rb +0 -1
  28. data/lib/view_model/after_transaction_runner.rb +0 -1
  29. data/lib/view_model/callbacks.rb +3 -1
  30. data/lib/view_model/controller.rb +13 -3
  31. data/lib/view_model/deserialization_error.rb +15 -12
  32. data/lib/view_model/error.rb +12 -10
  33. data/lib/view_model/error_view.rb +3 -1
  34. data/lib/view_model/migration/no_path_error.rb +1 -0
  35. data/lib/view_model/migration/one_way_error.rb +1 -0
  36. data/lib/view_model/migration/unspecified_version_error.rb +1 -0
  37. data/lib/view_model/record.rb +11 -13
  38. data/lib/view_model/reference.rb +3 -1
  39. data/lib/view_model/references.rb +8 -5
  40. data/lib/view_model/registry.rb +1 -1
  41. data/lib/view_model/schemas.rb +9 -4
  42. data/lib/view_model/serialization_error.rb +4 -1
  43. data/lib/view_model/serialize_context.rb +4 -4
  44. data/lib/view_model/test_helpers.rb +8 -3
  45. data/lib/view_model/test_helpers/arvm_builder.rb +19 -14
  46. data/lib/view_model/traversal_context.rb +2 -1
  47. data/test/.rubocop.yml +14 -0
  48. data/test/helpers/arvm_test_models.rb +12 -9
  49. data/test/helpers/arvm_test_utilities.rb +5 -3
  50. data/test/helpers/controller_test_helpers.rb +31 -29
  51. data/test/helpers/match_enumerator.rb +1 -0
  52. data/test/helpers/query_logging.rb +2 -1
  53. data/test/helpers/test_access_control.rb +5 -3
  54. data/test/helpers/viewmodel_spec_helpers.rb +21 -20
  55. data/test/unit/view_model/access_control_test.rb +144 -144
  56. data/test/unit/view_model/active_record/alias_test.rb +15 -13
  57. data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
  58. data/test/unit/view_model/active_record/cache_test.rb +27 -26
  59. data/test/unit/view_model/active_record/cloner_test.rb +67 -63
  60. data/test/unit/view_model/active_record/controller_test.rb +37 -38
  61. data/test/unit/view_model/active_record/counter_test.rb +10 -9
  62. data/test/unit/view_model/active_record/customization_test.rb +59 -58
  63. data/test/unit/view_model/active_record/has_many_test.rb +112 -111
  64. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
  65. data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
  66. data/test/unit/view_model/active_record/has_one_test.rb +37 -36
  67. data/test/unit/view_model/active_record/migration_test.rb +13 -13
  68. data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
  69. data/test/unit/view_model/active_record/poly_test.rb +44 -45
  70. data/test/unit/view_model/active_record/shared_test.rb +30 -28
  71. data/test/unit/view_model/active_record/version_test.rb +9 -7
  72. data/test/unit/view_model/active_record_test.rb +72 -72
  73. data/test/unit/view_model/callbacks_test.rb +19 -15
  74. data/test/unit/view_model/controller_test.rb +4 -2
  75. data/test/unit/view_model/record_test.rb +92 -97
  76. data/test/unit/view_model/traversal_context_test.rb +4 -5
  77. data/test/unit/view_model_test.rb +18 -16
  78. metadata +7 -5
@@ -1,4 +1,6 @@
1
- require "renum"
1
+ # frozen_string_literal: true
2
+
3
+ require 'renum'
2
4
 
3
5
  # Partially parsed tree of user-specified update hashes, created during deserialization.
4
6
  class ViewModel::ActiveRecord
@@ -14,7 +16,7 @@ class ViewModel::ActiveRecord
14
16
  :update_data,
15
17
  :points_to, # AssociationData => UpdateOperation (returns single new viewmodel to update fkey)
16
18
  :pointed_to, # AssociationData => UpdateOperation(s) (returns viewmodel(s) with which to update assoc cache)
17
- :reparent_to, # If node needs to update its pointer to a new parent, ParentData for the parent
19
+ :reparent_to, # If node needs to update its pointer to a new parent, ParentData for the parent
18
20
  :reposition_to, # if this node participates in a list under its parent, what should its position be?
19
21
  :released_children # Set of children that have been released
20
22
 
@@ -46,11 +48,11 @@ class ViewModel::ActiveRecord
46
48
 
47
49
  # Evaluate a built update tree, applying and saving changes to the models.
48
50
  def run!(deserialize_context:)
49
- raise ViewModel::DeserializationError::Internal.new("Internal error: UpdateOperation run before build") unless built?
51
+ raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation run before build') unless built?
50
52
 
51
53
  case @run_state
52
54
  when RunState::Running
53
- raise ViewModel::DeserializationError::Internal.new("Internal error: Cycle found in running UpdateOperation")
55
+ raise ViewModel::DeserializationError::Internal.new('Internal error: Cycle found in running UpdateOperation')
54
56
  when RunState::Run
55
57
  return viewmodel
56
58
  end
@@ -217,7 +219,7 @@ class ViewModel::ActiveRecord
217
219
 
218
220
  # Recursively builds UpdateOperations for the associations in our UpdateData
219
221
  def build!(update_context)
220
- raise ViewModel::DeserializationError::Internal.new("Internal error: UpdateOperation cannot build a deferred update") if viewmodel.nil?
222
+ raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation cannot build a deferred update') if viewmodel.nil?
221
223
  return self if built?
222
224
 
223
225
  update_data.associations.each do |association_name, association_update_data|
@@ -254,8 +256,8 @@ class ViewModel::ActiveRecord
254
256
  def add_update(association_data, update)
255
257
  target =
256
258
  case association_data.pointer_location
257
- when :remote; pointed_to
258
- when :local; points_to
259
+ when :remote then pointed_to
260
+ when :local then points_to
259
261
  end
260
262
 
261
263
  target[association_data] = update
@@ -658,7 +660,7 @@ class ViewModel::ActiveRecord
658
660
  other.indirect_viewmodel_reference == self.indirect_viewmodel_reference
659
661
  end
660
662
 
661
- alias :eql? :==
663
+ alias eql? ==
662
664
  end
663
665
 
664
666
  # Helper class to wrap the previous members of a referenced collection and
@@ -767,6 +769,7 @@ class ViewModel::ActiveRecord
767
769
  member.ref_string = ref_string if ref_string
768
770
  member
769
771
  end
772
+
770
773
  def remove_from_members(removed_members)
771
774
  s = removed_members.to_set
772
775
  members.reject! { |m| s.include?(m) }
@@ -900,11 +903,12 @@ class ViewModel::ActiveRecord
900
903
 
901
904
  def clear_association_cache(model, reflection)
902
905
  association = model.association(reflection.name)
903
- if reflection.collection?
904
- association.target = []
905
- else
906
- association.target = nil
907
- end
906
+ association.target =
907
+ if reflection.collection?
908
+ []
909
+ else
910
+ nil
911
+ end
908
912
  end
909
913
 
910
914
  def blame_reference
@@ -91,5 +91,4 @@ class ViewModel::ActiveRecord::Visitor
91
91
  def reset_state!
92
92
  @ignored_associations = Set.new
93
93
  end
94
-
95
94
  end
@@ -38,7 +38,6 @@ module ViewModel::AfterTransactionRunner
38
38
  end
39
39
  end
40
40
 
41
-
42
41
  # Override to tie to a specific connection.
43
42
  def connection
44
43
  ActiveRecord::Base.connection
@@ -79,7 +79,7 @@ module ViewModel::Callbacks
79
79
  end
80
80
  SRC
81
81
  else
82
- def self.create(callbacks, view, context)
82
+ def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition
83
83
  self.new(callbacks, view, context)
84
84
  end
85
85
  end
@@ -105,6 +105,7 @@ module ViewModel::Callbacks
105
105
  define_singleton_method(:class_callbacks) { base_callbacks }
106
106
  define_singleton_method(:all_callbacks) do |&block|
107
107
  return to_enum(__method__) unless block
108
+
108
109
  block.call(base_callbacks)
109
110
  end
110
111
  end
@@ -115,6 +116,7 @@ module ViewModel::Callbacks
115
116
  subclass.define_singleton_method(:class_callbacks) { subclass_callbacks }
116
117
  subclass.define_singleton_method(:all_callbacks) do |&block|
117
118
  return to_enum(__method__) unless block
119
+
118
120
  super(&block)
119
121
  block.call(subclass_callbacks)
120
122
  end
@@ -88,6 +88,7 @@ module ViewModel::Controller
88
88
  if data.blank?
89
89
  raise ViewModel::Error.new(status: 400, detail: "No data submitted: #{data.inspect}")
90
90
  end
91
+
91
92
  data.map { |el| _extract_param_hash(el) }
92
93
  else
93
94
  _extract_param_hash(data)
@@ -129,9 +130,18 @@ module ViewModel::Controller
129
130
  # untouched. Requires a MultiJson adapter other than ActiveSupport's
130
131
  # (modified) JsonGem.
131
132
  class CompiledJson
132
- def initialize(s); @s = s; end
133
- def to_json(*args); @s; end
134
- def to_s; @s; end
133
+ def initialize(s)
134
+ @s = s
135
+ end
136
+
137
+ def to_json(*_args)
138
+ @s
139
+ end
140
+
141
+ def to_s
142
+ @s
143
+ end
144
+
135
145
  undef_method :as_json
136
146
  end
137
147
 
@@ -15,13 +15,14 @@ class ViewModel
15
15
  unless nodes.all? { |n| n.viewmodel_class == first }
16
16
  raise ArgumentError.new("All nodes must be of the same type for #{self.class.name}")
17
17
  end
18
+
18
19
  first
19
20
  end
20
21
 
21
22
  # A collection of DeserializationErrors
22
23
  class Collection < ViewModel::AbstractErrorCollection
23
- title "Error(s) occurred during deserialization"
24
- code "DeserializationError.Collection"
24
+ title 'Error(s) occurred during deserialization'
25
+ code 'DeserializationError.Collection'
25
26
 
26
27
  def detail
27
28
  "Error(s) occurred during deserialization: #{cause_details}"
@@ -33,7 +34,7 @@ class ViewModel
33
34
  class InvalidRequest < DeserializationError
34
35
  # Abstract
35
36
  status 400
36
- title "Invalid request"
37
+ title 'Invalid request'
37
38
  end
38
39
 
39
40
  # There has been an unexpected internal failure of the ViewModel library.
@@ -145,6 +146,7 @@ class ViewModel
145
146
  # association.
146
147
  class InvalidAssociationType < InvalidRequest
147
148
  attr_reader :association, :target_type
149
+
148
150
  def initialize(association, target_type, node)
149
151
  @association = association
150
152
  @target_type = target_type
@@ -198,7 +200,7 @@ class ViewModel
198
200
  end
199
201
 
200
202
  def detail
201
- errors = missing_nodes.map(&:to_s).join(", ")
203
+ errors = missing_nodes.map(&:to_s).join(', ')
202
204
  "Couldn't find requested member node(s) in association '#{association}': "\
203
205
  "#{errors}"
204
206
  end
@@ -218,7 +220,7 @@ class ViewModel
218
220
  end
219
221
 
220
222
  def detail
221
- "Duplicate views for the same '#{type}' specified: "+ nodes.map(&:to_s).join(", ")
223
+ "Duplicate views for the same '#{type}' specified: " + nodes.map(&:to_s).join(', ')
222
224
  end
223
225
 
224
226
  def meta
@@ -235,14 +237,14 @@ class ViewModel
235
237
  end
236
238
 
237
239
  def detail
238
- "Multiple parents attempted to claim the same owned '#{association_name}' reference: " + nodes.map(&:to_s).join(", ")
240
+ "Multiple parents attempted to claim the same owned '#{association_name}' reference: " + nodes.map(&:to_s).join(', ')
239
241
  end
240
242
  end
241
243
 
242
244
  class ParentNotFound < NotFound
243
245
  def detail
244
- "Could not resolve release from previous parent for the following owned viewmodel(s): " +
245
- nodes.map(&:to_s).join(", ")
246
+ 'Could not resolve release from previous parent for the following owned viewmodel(s): ' +
247
+ nodes.map(&:to_s).join(', ')
246
248
  end
247
249
  end
248
250
 
@@ -284,7 +286,7 @@ class ViewModel
284
286
 
285
287
  class ReadOnlyType < DeserializationError
286
288
  status 400
287
- detail "Deserialization not defined for view type"
289
+ detail 'Deserialization not defined for view type'
288
290
  end
289
291
 
290
292
  class InvalidAttributeType < InvalidRequest
@@ -326,7 +328,7 @@ class ViewModel
326
328
  status 400
327
329
 
328
330
  def detail
329
- errors = nodes.map(&:to_s).join(", ")
331
+ errors = nodes.map(&:to_s).join(', ')
330
332
  "Optimistic lock failure updating nodes: #{errors}"
331
333
  end
332
334
  end
@@ -408,8 +410,9 @@ class ViewModel
408
410
 
409
411
  private
410
412
 
411
- QUOTED_IDENTIFIER = /\A"(?:[^"]|"")+"/
412
- UNQUOTED_IDENTIFIER = /\A(?:\p{Alpha}|_)(?:\p{Alnum}|_)*/
413
+ QUOTED_IDENTIFIER = /\A"(?:[^"]|"")+"/.freeze
414
+ UNQUOTED_IDENTIFIER = /\A(?:\p{Alpha}|_)(?:\p{Alnum}|_)*/.freeze
415
+
413
416
  def parse_identifier(stream)
414
417
  if (identifier = stream.slice!(UNQUOTED_IDENTIFIER))
415
418
  identifier
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abstract base for renderable errors in ViewModel-based APIs. Errors of this
2
4
  # type will be caught by ViewModel controllers and rendered in a standard format
3
5
  # by ViewModel::ErrorView, which loosely follows errors in JSON-API.
@@ -6,7 +8,7 @@ class ViewModel::AbstractError < StandardError
6
8
  # Brief DSL for quickly defining constant attribute values in subclasses
7
9
  [:detail, :status, :title, :code].each do |attribute|
8
10
  define_method(attribute) do |x|
9
- define_method(attribute){ x }
11
+ define_method(attribute) { x }
10
12
  end
11
13
  end
12
14
  end
@@ -25,7 +27,7 @@ class ViewModel::AbstractError < StandardError
25
27
 
26
28
  # Human-readable reason for use displaying this error.
27
29
  def detail
28
- "ViewModel::AbstractError"
30
+ 'ViewModel::AbstractError'
29
31
  end
30
32
 
31
33
  # HTTP status code most appropriate for this error
@@ -40,7 +42,7 @@ class ViewModel::AbstractError < StandardError
40
42
 
41
43
  # Unique symbol identifying this error type
42
44
  def code
43
- "ViewModel.AbstractError"
45
+ 'ViewModel.AbstractError'
44
46
  end
45
47
 
46
48
  # Additional information specific to this error type.
@@ -74,8 +76,6 @@ class ViewModel::AbstractError < StandardError
74
76
 
75
77
  protected
76
78
 
77
-
78
-
79
79
  def format_references(viewmodel_refs)
80
80
  viewmodel_refs.map do |viewmodel_ref|
81
81
  format_reference(viewmodel_ref)
@@ -85,7 +85,7 @@ class ViewModel::AbstractError < StandardError
85
85
  def format_reference(viewmodel_ref)
86
86
  {
87
87
  ViewModel::TYPE_ATTRIBUTE => viewmodel_ref.viewmodel_class.view_name,
88
- ViewModel::ID_ATTRIBUTE => viewmodel_ref.model_id
88
+ ViewModel::ID_ATTRIBUTE => viewmodel_ref.model_id,
89
89
  }
90
90
  end
91
91
  end
@@ -100,12 +100,13 @@ class ViewModel::AbstractErrorWithBlame < ViewModel::AbstractError
100
100
  unless @nodes.all? { |n| n.is_a?(ViewModel::Reference) }
101
101
  raise ArgumentError.new("#{self.class.name}: 'blame_nodes' must all be of type ViewModel::Reference")
102
102
  end
103
+
103
104
  super()
104
105
  end
105
106
 
106
107
  def meta
107
108
  {
108
- nodes: format_references(nodes)
109
+ nodes: format_references(nodes),
109
110
  }
110
111
  end
111
112
  end
@@ -117,8 +118,9 @@ class ViewModel::AbstractErrorCollection < ViewModel::AbstractError
117
118
  def initialize(causes)
118
119
  @causes = Array.wrap(causes)
119
120
  unless @causes.present?
120
- raise ArgumentError.new("A collection must have at least one cause")
121
+ raise ArgumentError.new('A collection must have at least one cause')
121
122
  end
123
+
122
124
  super()
123
125
  end
124
126
 
@@ -151,7 +153,7 @@ class ViewModel::AbstractErrorCollection < ViewModel::AbstractError
151
153
  protected
152
154
 
153
155
  def cause_details
154
- causes.map(&:detail).join("; ")
156
+ causes.map(&:detail).join('; ')
155
157
  end
156
158
  end
157
159
 
@@ -180,7 +182,7 @@ end
180
182
  class ViewModel::Error < ViewModel::AbstractError
181
183
  attr_reader :detail, :status, :title, :code, :meta
182
184
 
183
- def initialize(status: 400, detail: "ViewModel Error", title: nil, code: nil, meta: {})
185
+ def initialize(status: 400, detail: 'ViewModel Error', title: nil, code: nil, meta: {})
184
186
  @detail = detail
185
187
  @status = status
186
188
  @title = title
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'view_model/record'
2
4
 
3
5
  # ViewModel for rendering ViewModel::AbstractErrors
@@ -10,7 +12,7 @@ class ViewModel::ErrorView < ViewModel::Record
10
12
  def serialize_view(json, serialize_context: nil)
11
13
  json.set! :class, exception.class.name
12
14
  json.backtrace exception.backtrace
13
- if cause = exception.cause
15
+ if (cause = exception.cause)
14
16
  json.cause do
15
17
  json.set! :class, cause.class.name
16
18
  json.backtrace cause.backtrace
@@ -9,6 +9,7 @@ class ViewModel::Migration::NoPathError < ViewModel::AbstractError
9
9
  @vm_name = viewmodel.view_name
10
10
  @from = from
11
11
  @to = to
12
+ super()
12
13
  end
13
14
 
14
15
  def detail
@@ -8,6 +8,7 @@ class ViewModel::Migration::OneWayError < ViewModel::AbstractError
8
8
  def initialize(vm_name, direction)
9
9
  @vm_name = vm_name
10
10
  @direction = direction
11
+ super()
11
12
  end
12
13
 
13
14
  def detail
@@ -8,6 +8,7 @@ class ViewModel::Migration::UnspecifiedVersionError < ViewModel::AbstractError
8
8
  def initialize(vm_name, version)
9
9
  @vm_name = vm_name
10
10
  @version = version
11
+ super()
11
12
  end
12
13
 
13
14
  def detail
@@ -116,7 +116,7 @@ class ViewModel::Record < ViewModel
116
116
  end
117
117
  end
118
118
 
119
- def resolve_viewmodel(metadata, view_hash, deserialize_context:)
119
+ def resolve_viewmodel(_metadata, _view_hash, deserialize_context:)
120
120
  self.for_new_model
121
121
  end
122
122
 
@@ -181,6 +181,8 @@ class ViewModel::Record < ViewModel
181
181
  @changed_attributes = []
182
182
  @changed_nested_children = false
183
183
  @changed_referenced_children = false
184
+
185
+ super()
184
186
  end
185
187
 
186
188
  # VM::Record identity matches the identity of its model. If the model has a
@@ -309,12 +311,10 @@ class ViewModel::Record < ViewModel
309
311
  # viewmodel), it's only desired for converting the value to and from wire
310
312
  # format, so conversion is deferred to serialization time.
311
313
  value = attr_data.map_value(value) do |v|
312
- begin
313
- attr_data.attribute_serializer.dump(v, json: true)
314
- rescue IknowParams::Serializer::DumpError => ex
315
- raise ViewModel::SerializationError.new(
316
- "Could not serialize invalid value '#{vm_attr_name}': #{ex.message}")
317
- end
314
+ attr_data.attribute_serializer.dump(v, json: true)
315
+ rescue IknowParams::Serializer::DumpError => ex
316
+ raise ViewModel::SerializationError.new(
317
+ "Could not serialize invalid value '#{vm_attr_name}': #{ex.message}")
318
318
  end
319
319
  end
320
320
 
@@ -342,12 +342,10 @@ class ViewModel::Record < ViewModel
342
342
  end
343
343
  when attr_data.using_serializer?
344
344
  attr_data.map_value(serialized_value) do |sv|
345
- begin
346
- attr_data.attribute_serializer.load(sv)
347
- rescue IknowParams::Serializer::LoadError => ex
348
- reason = "could not be deserialized because #{ex.message}"
349
- raise ViewModel::DeserializationError::Validation.new(vm_attr_name, reason, {}, blame_reference)
350
- end
345
+ attr_data.attribute_serializer.load(sv)
346
+ rescue IknowParams::Serializer::LoadError => ex
347
+ reason = "could not be deserialized because #{ex.message}"
348
+ raise ViewModel::DeserializationError::Validation.new(vm_attr_name, reason, {}, blame_reference)
351
349
  end
352
350
  else
353
351
  serialized_value
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ViewModel
2
4
  # Key to identify a viewmodel with some kind of inherent ID (e.g. an ViewModel::ActiveRecord)
3
5
  class Reference
@@ -22,7 +24,7 @@ class ViewModel
22
24
  other.model_id == model_id
23
25
  end
24
26
 
25
- alias :eql? :==
27
+ alias eql? ==
26
28
 
27
29
  def hash
28
30
  [viewmodel_class, model_id].hash