iknow_view_models 3.2.0 → 3.2.1
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 +4 -4
- data/.rubocop.yml +13 -0
- data/Appraisals +6 -6
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +5 -5
- data/iknow_view_models.gemspec +40 -39
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +17 -14
- data/lib/view_model/access_control.rb +5 -2
- data/lib/view_model/access_control/composed.rb +10 -9
- data/lib/view_model/access_control/open.rb +2 -0
- data/lib/view_model/access_control/read_only.rb +2 -0
- data/lib/view_model/access_control/tree.rb +11 -6
- data/lib/view_model/access_control_error.rb +4 -1
- data/lib/view_model/active_record.rb +12 -11
- data/lib/view_model/active_record/association_data.rb +2 -1
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +4 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- data/lib/view_model/active_record/controller_base.rb +4 -1
- data/lib/view_model/active_record/nested_controller_base.rb +1 -0
- data/lib/view_model/active_record/update_context.rb +8 -6
- data/lib/view_model/active_record/update_data.rb +32 -30
- data/lib/view_model/active_record/update_operation.rb +17 -13
- data/lib/view_model/active_record/visitor.rb +0 -1
- data/lib/view_model/after_transaction_runner.rb +0 -1
- data/lib/view_model/callbacks.rb +3 -1
- data/lib/view_model/controller.rb +13 -3
- data/lib/view_model/deserialization_error.rb +15 -12
- data/lib/view_model/error.rb +12 -10
- data/lib/view_model/error_view.rb +3 -1
- data/lib/view_model/migration/no_path_error.rb +1 -0
- data/lib/view_model/migration/one_way_error.rb +1 -0
- data/lib/view_model/migration/unspecified_version_error.rb +1 -0
- data/lib/view_model/record.rb +11 -13
- data/lib/view_model/reference.rb +3 -1
- data/lib/view_model/references.rb +8 -5
- data/lib/view_model/registry.rb +1 -1
- data/lib/view_model/schemas.rb +9 -4
- data/lib/view_model/serialization_error.rb +4 -1
- data/lib/view_model/serialize_context.rb +4 -4
- data/lib/view_model/test_helpers.rb +8 -3
- data/lib/view_model/test_helpers/arvm_builder.rb +19 -14
- data/lib/view_model/traversal_context.rb +2 -1
- data/test/.rubocop.yml +14 -0
- data/test/helpers/arvm_test_models.rb +12 -9
- data/test/helpers/arvm_test_utilities.rb +5 -3
- data/test/helpers/controller_test_helpers.rb +31 -29
- data/test/helpers/match_enumerator.rb +1 -0
- data/test/helpers/query_logging.rb +2 -1
- data/test/helpers/test_access_control.rb +5 -3
- data/test/helpers/viewmodel_spec_helpers.rb +21 -20
- data/test/unit/view_model/access_control_test.rb +144 -144
- data/test/unit/view_model/active_record/alias_test.rb +15 -13
- data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
- data/test/unit/view_model/active_record/cache_test.rb +27 -26
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +37 -38
- data/test/unit/view_model/active_record/counter_test.rb +10 -9
- data/test/unit/view_model/active_record/customization_test.rb +59 -58
- data/test/unit/view_model/active_record/has_many_test.rb +112 -111
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
- data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
- data/test/unit/view_model/active_record/has_one_test.rb +37 -36
- data/test/unit/view_model/active_record/migration_test.rb +13 -13
- data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
- data/test/unit/view_model/active_record/poly_test.rb +44 -45
- data/test/unit/view_model/active_record/shared_test.rb +30 -28
- data/test/unit/view_model/active_record/version_test.rb +9 -7
- data/test/unit/view_model/active_record_test.rb +72 -72
- data/test/unit/view_model/callbacks_test.rb +19 -15
- data/test/unit/view_model/controller_test.rb +4 -2
- data/test/unit/view_model/record_test.rb +92 -97
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +7 -5
@@ -14,6 +14,7 @@ module MiniTest::Assertions
|
|
14
14
|
|
15
15
|
def result
|
16
16
|
return false unless @actual.respond_to? :to_a
|
17
|
+
|
17
18
|
@extra_items = difference_between_enumerators(@actual, @expected)
|
18
19
|
@missing_items = difference_between_enumerators(@expected, @actual)
|
19
20
|
@extra_items.empty? & @missing_items.empty?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Test mixin that allows queries executed in a block to be introspected.
|
2
4
|
#
|
3
5
|
# Code run within a `log_queries` block will collect data. Collected data is
|
@@ -9,7 +11,6 @@
|
|
9
11
|
require 'active_support/subscriber'
|
10
12
|
|
11
13
|
module QueryLogging
|
12
|
-
|
13
14
|
# ActiveRecord integration
|
14
15
|
class QueryLogger < ActiveSupport::Subscriber
|
15
16
|
@log = false
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'iknow_view_models'
|
3
4
|
|
4
5
|
class TestAccessControl < ViewModel::AccessControl
|
5
6
|
attr_accessor :editable_checks, :visible_checks
|
@@ -41,13 +42,14 @@ class TestAccessControl < ViewModel::AccessControl
|
|
41
42
|
def valid_edit_changes(ref)
|
42
43
|
all = all_valid_edit_changes(ref)
|
43
44
|
raise "Expected single change for ref '#{ref}'; found #{all}" unless all.size == 1
|
45
|
+
|
44
46
|
all.first
|
45
47
|
end
|
46
48
|
|
47
49
|
def all_valid_edit_changes(ref)
|
48
50
|
@valid_edit_checks
|
49
|
-
.select { |
|
50
|
-
.map { |_cref,
|
51
|
+
.select { |cref, _changes| cref == ref }
|
52
|
+
.map { |_cref, changes| changes }
|
51
53
|
end
|
52
54
|
|
53
55
|
def was_edited?(ref)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'view_model'
|
2
4
|
require 'view_model/test_helpers'
|
3
5
|
|
@@ -77,7 +79,7 @@ module ViewModelSpecHelpers
|
|
77
79
|
ViewModel::TestHelpers::ARVMBuilder::Spec.new(
|
78
80
|
schema: ->(t) { t.string :name },
|
79
81
|
model: ->(m) {},
|
80
|
-
viewmodel: ->(
|
82
|
+
viewmodel: ->(_v) { root!; attribute :name },
|
81
83
|
)
|
82
84
|
end
|
83
85
|
|
@@ -85,7 +87,7 @@ module ViewModelSpecHelpers
|
|
85
87
|
ViewModel::TestHelpers::ARVMBuilder::Spec.new(
|
86
88
|
schema: ->(t) { t.string :name },
|
87
89
|
model: ->(m) {},
|
88
|
-
viewmodel: ->(
|
90
|
+
viewmodel: ->(_v) { attribute :name },
|
89
91
|
)
|
90
92
|
end
|
91
93
|
|
@@ -125,7 +127,7 @@ module ViewModelSpecHelpers
|
|
125
127
|
end
|
126
128
|
|
127
129
|
def child_attributes
|
128
|
-
super.merge(model: ->(
|
130
|
+
super.merge(model: ->(_m) { has_one :model, inverse_of: :child })
|
129
131
|
end
|
130
132
|
|
131
133
|
# parent depends on child, ensure it's touched first
|
@@ -208,7 +210,7 @@ module ViewModelSpecHelpers
|
|
208
210
|
extend ActiveSupport::Concern
|
209
211
|
include ViewModelSpecHelpers::ParentAndBelongsToChild
|
210
212
|
def child_attributes
|
211
|
-
super.merge(viewmodel: ->(
|
213
|
+
super.merge(viewmodel: ->(_v) { root! })
|
212
214
|
end
|
213
215
|
end
|
214
216
|
|
@@ -222,11 +224,11 @@ module ViewModelSpecHelpers
|
|
222
224
|
t.string :name
|
223
225
|
t.integer :next_id
|
224
226
|
},
|
225
|
-
model: ->(
|
227
|
+
model: ->(_m) {
|
226
228
|
belongs_to :next, class_name: self.name, inverse_of: :previous, dependent: :destroy
|
227
229
|
has_one :previous, class_name: self.name, foreign_key: :next_id, inverse_of: :next
|
228
230
|
},
|
229
|
-
viewmodel: ->(
|
231
|
+
viewmodel: ->(_v) {
|
230
232
|
# Not a root
|
231
233
|
association :next
|
232
234
|
attribute :name
|
@@ -245,15 +247,15 @@ module ViewModelSpecHelpers
|
|
245
247
|
def model_attributes
|
246
248
|
f = subject_association_features
|
247
249
|
super.merge(
|
248
|
-
model: ->(
|
249
|
-
viewmodel: ->(
|
250
|
+
model: ->(_m) { has_one :child, inverse_of: :model, dependent: :destroy },
|
251
|
+
viewmodel: ->(_v) { association :child, **f },
|
250
252
|
)
|
251
253
|
end
|
252
254
|
|
253
255
|
def child_attributes
|
254
256
|
super.merge(
|
255
257
|
schema: ->(t) { t.references :model, foreign_key: true },
|
256
|
-
model: ->(
|
258
|
+
model: ->(_m) { belongs_to :model, inverse_of: :child },
|
257
259
|
)
|
258
260
|
end
|
259
261
|
|
@@ -272,7 +274,7 @@ module ViewModelSpecHelpers
|
|
272
274
|
extend ActiveSupport::Concern
|
273
275
|
include ViewModelSpecHelpers::ParentAndHasOneChild
|
274
276
|
def child_attributes
|
275
|
-
super.merge(viewmodel: ->(
|
277
|
+
super.merge(viewmodel: ->(_v) { root! })
|
276
278
|
end
|
277
279
|
end
|
278
280
|
|
@@ -283,15 +285,15 @@ module ViewModelSpecHelpers
|
|
283
285
|
def model_attributes
|
284
286
|
f = subject_association_features
|
285
287
|
super.merge(
|
286
|
-
model: ->(
|
287
|
-
viewmodel: ->(
|
288
|
+
model: ->(_m) { has_many :children, inverse_of: :model, dependent: :destroy },
|
289
|
+
viewmodel: ->(_v) { association :children, **f },
|
288
290
|
)
|
289
291
|
end
|
290
292
|
|
291
293
|
def child_attributes
|
292
294
|
super.merge(
|
293
295
|
schema: ->(t) { t.references :model, foreign_key: true },
|
294
|
-
model: ->(
|
296
|
+
model: ->(_m) { belongs_to :model, inverse_of: :children },
|
295
297
|
)
|
296
298
|
end
|
297
299
|
|
@@ -310,7 +312,7 @@ module ViewModelSpecHelpers
|
|
310
312
|
extend ActiveSupport::Concern
|
311
313
|
include ViewModelSpecHelpers::ParentAndHasManyChildren
|
312
314
|
def child_attributes
|
313
|
-
super.merge(viewmodel: ->(
|
315
|
+
super.merge(viewmodel: ->(_v) { root! })
|
314
316
|
end
|
315
317
|
end
|
316
318
|
|
@@ -336,12 +338,11 @@ module ViewModelSpecHelpers
|
|
336
338
|
table = model.table_name
|
337
339
|
model.connection.execute <<-SQL
|
338
340
|
ALTER TABLE #{table} ADD CONSTRAINT #{table}_unique_on_model_and_position UNIQUE(model_id, position) DEFERRABLE INITIALLY DEFERRED
|
339
|
-
|
341
|
+
SQL
|
340
342
|
end
|
341
343
|
end
|
342
344
|
end
|
343
345
|
|
344
|
-
|
345
346
|
module ParentAndExternalSharedChild
|
346
347
|
extend ActiveSupport::Concern
|
347
348
|
include ViewModelSpecHelpers::ParentAndSharedBelongsToChild
|
@@ -358,15 +359,15 @@ module ViewModelSpecHelpers
|
|
358
359
|
def model_attributes
|
359
360
|
f = subject_association_features
|
360
361
|
super.merge(
|
361
|
-
model: ->(
|
362
|
-
viewmodel: ->(
|
362
|
+
model: ->(_m) { has_many :model_children, inverse_of: :model, dependent: :destroy },
|
363
|
+
viewmodel: ->(_v) { association :children, through: :model_children, through_order_attr: :position, **f },
|
363
364
|
)
|
364
365
|
end
|
365
366
|
|
366
367
|
def child_attributes
|
367
368
|
super.merge(
|
368
|
-
model: ->(
|
369
|
-
viewmodel: ->(
|
369
|
+
model: ->(_m) { has_many :model_children, inverse_of: :child, dependent: :destroy },
|
370
|
+
viewmodel: ->(_v) { root! },
|
370
371
|
)
|
371
372
|
end
|
372
373
|
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative '../../helpers/viewmodel_spec_helpers
|
3
|
+
require_relative '../../helpers/arvm_test_utilities'
|
4
|
+
require_relative '../../helpers/arvm_test_models'
|
5
|
+
require_relative '../../helpers/viewmodel_spec_helpers'
|
6
6
|
|
7
|
-
require
|
7
|
+
require 'minitest/autorun'
|
8
8
|
require 'minitest/unit'
|
9
9
|
|
10
10
|
require 'rspec/expectations/minitest_integration'
|
11
11
|
|
12
|
-
require
|
12
|
+
require 'view_model/active_record'
|
13
13
|
|
14
14
|
class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
15
15
|
include ARVMTestUtilities
|
@@ -55,103 +55,103 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def test_visible_if
|
58
|
-
TestAccessControl.visible_if!(
|
59
|
-
view.car ==
|
58
|
+
TestAccessControl.visible_if!('car is visible1') do
|
59
|
+
view.car == 'visible1'
|
60
60
|
end
|
61
61
|
|
62
|
-
TestAccessControl.visible_if!(
|
63
|
-
view.car ==
|
62
|
+
TestAccessControl.visible_if!('car is visible2') do
|
63
|
+
view.car == 'visible2'
|
64
64
|
end
|
65
65
|
|
66
|
-
assert_serializes(ListView, List.create!(car:
|
67
|
-
assert_serializes(ListView, List.create!(car:
|
68
|
-
ex = refute_serializes(ListView, List.create!(car:
|
66
|
+
assert_serializes(ListView, List.create!(car: 'visible1'))
|
67
|
+
assert_serializes(ListView, List.create!(car: 'visible2'))
|
68
|
+
ex = refute_serializes(ListView, List.create!(car: 'bad'), /none of the possible/)
|
69
69
|
assert_equal(2, ex.reasons.count)
|
70
70
|
end
|
71
71
|
|
72
72
|
def test_visible_unless
|
73
|
-
TestAccessControl.visible_if!(
|
73
|
+
TestAccessControl.visible_if!('always') { true }
|
74
74
|
|
75
|
-
TestAccessControl.visible_unless!(
|
76
|
-
view.car ==
|
75
|
+
TestAccessControl.visible_unless!('car is invisible') do
|
76
|
+
view.car == 'invisible'
|
77
77
|
end
|
78
78
|
|
79
|
-
assert_serializes(ListView, List.create!(car:
|
80
|
-
refute_serializes(ListView, List.create!(car:
|
79
|
+
assert_serializes(ListView, List.create!(car: 'ok'))
|
80
|
+
refute_serializes(ListView, List.create!(car: 'invisible'), /not permitted.*car is invisible/)
|
81
81
|
end
|
82
82
|
|
83
83
|
def test_editable_if
|
84
|
-
TestAccessControl.visible_if!(
|
84
|
+
TestAccessControl.visible_if!('always') { true }
|
85
85
|
|
86
|
-
TestAccessControl.editable_if!(
|
87
|
-
view.car ==
|
86
|
+
TestAccessControl.editable_if!('car is editable1') do
|
87
|
+
view.car == 'editable1'
|
88
88
|
end
|
89
89
|
|
90
|
-
TestAccessControl.editable_if!(
|
91
|
-
view.car ==
|
90
|
+
TestAccessControl.editable_if!('car is editable2') do
|
91
|
+
view.car == 'editable2'
|
92
92
|
end
|
93
93
|
|
94
|
-
assert_deserializes(ListView, List.create!(car:
|
95
|
-
assert_deserializes(ListView, List.create!(car:
|
96
|
-
assert_deserializes(ListView, List.create!(car:
|
97
|
-
refute_deserializes(ListView, List.create!(car:
|
94
|
+
assert_deserializes(ListView, List.create!(car: 'editable1')) { |v, _| v['car'] = 'unchecked' }
|
95
|
+
assert_deserializes(ListView, List.create!(car: 'editable2')) { |v, _| v['car'] = 'unchecked' }
|
96
|
+
assert_deserializes(ListView, List.create!(car: 'forbidden')) { |v, _| v['car'] = 'forbidden' } # no change so permitted
|
97
|
+
refute_deserializes(ListView, List.create!(car: 'forbidden'), /none of the possible/) { |v, _| v['car'] = 'unchecked' }
|
98
98
|
end
|
99
99
|
|
100
100
|
def test_editable_unless
|
101
|
-
TestAccessControl.visible_if!(
|
102
|
-
TestAccessControl.editable_if!(
|
101
|
+
TestAccessControl.visible_if!('always') { true }
|
102
|
+
TestAccessControl.editable_if!('always') { true }
|
103
103
|
|
104
|
-
TestAccessControl.editable_unless!(
|
105
|
-
view.car ==
|
104
|
+
TestAccessControl.editable_unless!('car is uneditable') do
|
105
|
+
view.car == 'uneditable'
|
106
106
|
end
|
107
107
|
|
108
|
-
assert_deserializes(ListView, List.create!(car:
|
109
|
-
assert_deserializes(ListView, List.create!(car:
|
110
|
-
refute_deserializes(ListView, List.create!(car:
|
108
|
+
assert_deserializes(ListView, List.create!(car: 'ok')) { |v, _| v['car'] = 'unchecked' }
|
109
|
+
assert_deserializes(ListView, List.create!(car: 'uneditable')) { |v, _| v['car'] = 'uneditable' } # no change so permitted
|
110
|
+
refute_deserializes(ListView, List.create!(car: 'uneditable'), /car is uneditable/) { |v, _| v['car'] = 'unchecked' }
|
111
111
|
end
|
112
112
|
|
113
113
|
def test_edit_valid_if
|
114
|
-
TestAccessControl.visible_if!(
|
114
|
+
TestAccessControl.visible_if!('always') { true }
|
115
115
|
|
116
|
-
TestAccessControl.edit_valid_if!(
|
117
|
-
view.car ==
|
116
|
+
TestAccessControl.edit_valid_if!('car is validedit') do
|
117
|
+
view.car == 'validedit'
|
118
118
|
end
|
119
119
|
|
120
|
-
assert_deserializes(ListView, List.create!(car:
|
121
|
-
assert_deserializes(ListView, List.create!(car:
|
122
|
-
refute_deserializes(ListView, List.create!(car:
|
120
|
+
assert_deserializes(ListView, List.create!(car: 'unchecked')) { |v, _| v['car'] = 'validedit' }
|
121
|
+
assert_deserializes(ListView, List.create!(car: 'unmodified')) { |v, _| v['car'] = 'unmodified' } # no change so permitted
|
122
|
+
refute_deserializes(ListView, List.create!(car: 'unchecked'), /none of the possible/) { |v, _| v['car'] = 'bad' }
|
123
123
|
end
|
124
124
|
|
125
125
|
def test_edit_valid_unless
|
126
|
-
TestAccessControl.visible_if!(
|
127
|
-
TestAccessControl.edit_valid_if!(
|
128
|
-
TestAccessControl.edit_valid_unless!(
|
129
|
-
view.car ==
|
126
|
+
TestAccessControl.visible_if!('always') { true }
|
127
|
+
TestAccessControl.edit_valid_if!('always') { true }
|
128
|
+
TestAccessControl.edit_valid_unless!('car is invalidedit') do
|
129
|
+
view.car == 'invalidedit'
|
130
130
|
end
|
131
131
|
|
132
|
-
assert_deserializes(ListView, List.create!(car:
|
133
|
-
assert_deserializes(ListView, List.create!(car:
|
134
|
-
refute_deserializes(ListView, List.create!(car:
|
132
|
+
assert_deserializes(ListView, List.create!(car: 'unchecked')) { |v, _| v['car'] = 'ok' }
|
133
|
+
assert_deserializes(ListView, List.create!(car: 'invalidedit')) { |v, _| v['car'] = 'invalidedit' }
|
134
|
+
refute_deserializes(ListView, List.create!(car: 'unchecked'), /car is invalidedit/) { |v, _| v['car'] = 'invalidedit' }
|
135
135
|
end
|
136
136
|
|
137
137
|
def test_editable_and_edit_valid
|
138
|
-
TestAccessControl.visible_if!(
|
138
|
+
TestAccessControl.visible_if!('always') { true }
|
139
139
|
|
140
|
-
TestAccessControl.editable_if!(
|
141
|
-
view.car ==
|
140
|
+
TestAccessControl.editable_if!('original car permits') do
|
141
|
+
view.car == 'permitoriginal'
|
142
142
|
end
|
143
143
|
|
144
|
-
TestAccessControl.edit_valid_if!(
|
145
|
-
view.car ==
|
144
|
+
TestAccessControl.edit_valid_if!('resulting car permits') do
|
145
|
+
view.car == 'permitresult'
|
146
146
|
end
|
147
147
|
|
148
148
|
# at least one valid
|
149
|
-
assert_deserializes(ListView, List.create!(car:
|
150
|
-
assert_deserializes(ListView, List.create!(car:
|
151
|
-
assert_deserializes(ListView, List.create!(car:
|
149
|
+
assert_deserializes(ListView, List.create!(car: 'permitoriginal')) { |v, _| v['car'] = 'permitresult' }
|
150
|
+
assert_deserializes(ListView, List.create!(car: 'badoriginal')) { |v, _| v['car'] = 'permitresult' }
|
151
|
+
assert_deserializes(ListView, List.create!(car: 'permitoriginal')) { |v, _| v['car'] = 'badresult' }
|
152
152
|
|
153
153
|
# no valid
|
154
|
-
ex = refute_deserializes(ListView, List.create!(car:
|
154
|
+
ex = refute_deserializes(ListView, List.create!(car: 'badoriginal'), /none of the possible/) { |v, _| v['car'] = 'badresult' }
|
155
155
|
|
156
156
|
assert_equal(2, ex.reasons.count)
|
157
157
|
end
|
@@ -160,14 +160,14 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
160
160
|
child_access_control = Class.new(ViewModel::AccessControl::Composed)
|
161
161
|
child_access_control.include_from(TestAccessControl)
|
162
162
|
|
163
|
-
TestAccessControl.visible_if!(
|
164
|
-
child_access_control.visible_if!(
|
163
|
+
TestAccessControl.visible_if!('car is ancestor') { view.car == 'ancestor' }
|
164
|
+
child_access_control.visible_if!('car is descendent') { view.car == 'descendent' }
|
165
165
|
|
166
166
|
s_ctx = ListView.new_serialize_context(access_control: child_access_control.new)
|
167
167
|
|
168
|
-
assert_serializes(ListView, List.create!(car:
|
169
|
-
assert_serializes(ListView, List.create!(car:
|
170
|
-
ex = refute_serializes(ListView, List.create!(car:
|
168
|
+
assert_serializes(ListView, List.create!(car: 'ancestor'), serialize_context: s_ctx)
|
169
|
+
assert_serializes(ListView, List.create!(car: 'descendent'), serialize_context: s_ctx)
|
170
|
+
ex = refute_serializes(ListView, List.create!(car: 'foreigner'), serialize_context: s_ctx)
|
171
171
|
assert_equal(2, ex.reasons.count)
|
172
172
|
end
|
173
173
|
end
|
@@ -243,7 +243,7 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
243
243
|
|
244
244
|
child = root[attr]
|
245
245
|
|
246
|
-
if (child_ref = child[
|
246
|
+
if (child_ref = child['_ref'])
|
247
247
|
child = refs[child_ref]
|
248
248
|
end
|
249
249
|
|
@@ -255,189 +255,189 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
255
255
|
end
|
256
256
|
|
257
257
|
def test_visibility_from_root
|
258
|
-
TestAccessControl.view
|
259
|
-
visible_if!(
|
258
|
+
TestAccessControl.view 'Tree1' do
|
259
|
+
visible_if!('true') { true }
|
260
260
|
|
261
|
-
root_children_visible_if!(
|
262
|
-
view.val ==
|
261
|
+
root_children_visible_if!('root children visible') do
|
262
|
+
view.val == 'rule:visible_children'
|
263
263
|
end
|
264
264
|
end
|
265
265
|
|
266
|
-
refute_serializes(Tree1View, make_tree(
|
267
|
-
assert_serializes(Tree1View, make_tree(
|
266
|
+
refute_serializes(Tree1View, make_tree('arbitrary parent', 'invisible child'))
|
267
|
+
assert_serializes(Tree1View, make_tree('rule:visible_children', 'visible child'))
|
268
268
|
|
269
269
|
# nested root
|
270
|
-
refute_serializes(Tree1View, make_tree(
|
271
|
-
assert_serializes(Tree1View, make_tree(
|
270
|
+
refute_serializes(Tree1View, make_tree('rule:visible_children', 'visible child', 'arbitrary parent', 'invisible child'))
|
271
|
+
assert_serializes(Tree1View, make_tree('rule:visible_children', 'visible child', 'rule:visible_children', 'visible child'))
|
272
272
|
end
|
273
273
|
|
274
274
|
def test_visibility_veto_from_root
|
275
|
-
TestAccessControl.view
|
276
|
-
root_children_visible_unless!(
|
277
|
-
view.val ==
|
275
|
+
TestAccessControl.view 'Tree1' do
|
276
|
+
root_children_visible_unless!('root children invisible') do
|
277
|
+
view.val == 'rule:invisible_children'
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
281
281
|
TestAccessControl.always do
|
282
|
-
visible_if!(
|
282
|
+
visible_if!('true') { true }
|
283
283
|
end
|
284
284
|
|
285
|
-
assert_serializes(Tree1View, make_tree(
|
286
|
-
refute_serializes(Tree1View, make_tree(
|
285
|
+
assert_serializes(Tree1View, make_tree('arbitrary parent', 'visible child'))
|
286
|
+
refute_serializes(Tree1View, make_tree('rule:invisible_children', 'invisible child'))
|
287
287
|
|
288
288
|
# nested root
|
289
|
-
assert_serializes(Tree1View, make_tree(
|
290
|
-
refute_serializes(Tree1View, make_tree(
|
289
|
+
assert_serializes(Tree1View, make_tree('arbitrary parent', 'visible child', 'arbitrary nested parent', 'visible child'))
|
290
|
+
refute_serializes(Tree1View, make_tree('arbitrary parent', 'visible child', 'rule:invisible_children', 'invisible child'))
|
291
291
|
end
|
292
292
|
|
293
293
|
def test_editability_from_root
|
294
294
|
TestAccessControl.always do
|
295
|
-
visible_if!(
|
295
|
+
visible_if!('always') { true }
|
296
296
|
end
|
297
297
|
|
298
|
-
TestAccessControl.view
|
299
|
-
editable_if!(
|
298
|
+
TestAccessControl.view 'Tree1' do
|
299
|
+
editable_if!('true') { true }
|
300
300
|
|
301
|
-
root_children_editable_if!(
|
302
|
-
view.val ==
|
301
|
+
root_children_editable_if!('root children editable') do
|
302
|
+
view.val == 'rule:editable_children'
|
303
303
|
end
|
304
304
|
end
|
305
305
|
|
306
|
-
refute_deserializes(Tree1View, make_tree(
|
307
|
-
dig_tree(v, r,
|
306
|
+
refute_deserializes(Tree1View, make_tree('arbitrary parent', 'uneditable child')) { |v, r|
|
307
|
+
dig_tree(v, r, 'tree2')['val'] = 'change'
|
308
308
|
}
|
309
309
|
|
310
|
-
assert_deserializes(Tree1View, make_tree(
|
311
|
-
dig_tree(v, r,
|
310
|
+
assert_deserializes(Tree1View, make_tree('rule:editable_children', 'editable child')) { |v, r|
|
311
|
+
dig_tree(v, r, 'tree2')['val'] = 'change'
|
312
312
|
}
|
313
313
|
|
314
314
|
# nested root
|
315
|
-
refute_deserializes(Tree1View, make_tree(
|
316
|
-
dig_tree(v, r,
|
315
|
+
refute_deserializes(Tree1View, make_tree('rule:editable_children', 'editable child', 'arbitrary parent', 'uneditable child')) { |v, r|
|
316
|
+
dig_tree(v, r, 'tree2', 'tree1', 'tree2')['val'] = 'change'
|
317
317
|
}
|
318
318
|
|
319
|
-
assert_deserializes(Tree1View, make_tree(
|
320
|
-
dig_tree(v, r,
|
319
|
+
assert_deserializes(Tree1View, make_tree('arbitrary parent', 'uneditable child', 'rule:editable_children', 'editable child')) { |v, r|
|
320
|
+
dig_tree(v, r, 'tree2', 'tree1', 'tree2')['val'] = 'change'
|
321
321
|
}
|
322
322
|
end
|
323
323
|
|
324
324
|
def test_editability_veto_from_root
|
325
325
|
TestAccessControl.always do
|
326
|
-
visible_if!(
|
327
|
-
editable_if!(
|
326
|
+
visible_if!('always') { true }
|
327
|
+
editable_if!('always') { true }
|
328
328
|
end
|
329
329
|
|
330
|
-
TestAccessControl.view
|
331
|
-
root_children_editable_unless!(
|
332
|
-
view.val ==
|
330
|
+
TestAccessControl.view 'Tree1' do
|
331
|
+
root_children_editable_unless!('root children uneditable') do
|
332
|
+
view.val == 'rule:uneditable_children'
|
333
333
|
end
|
334
334
|
end
|
335
335
|
|
336
|
-
refute_deserializes(Tree1View, make_tree(
|
337
|
-
dig_tree(v, r,
|
336
|
+
refute_deserializes(Tree1View, make_tree('rule:uneditable_children', 'uneditable child')) { |v, r|
|
337
|
+
dig_tree(v, r, 'tree2')['val'] = 'change'
|
338
338
|
}
|
339
339
|
|
340
|
-
assert_deserializes(Tree1View, make_tree(
|
341
|
-
dig_tree(v, r,
|
340
|
+
assert_deserializes(Tree1View, make_tree('arbitrary parent', 'editable child')) { |v, r|
|
341
|
+
dig_tree(v, r, 'tree2')['val'] = 'change'
|
342
342
|
}
|
343
343
|
|
344
344
|
# nested root
|
345
|
-
refute_deserializes(Tree1View, make_tree(
|
346
|
-
dig_tree(v, r,
|
345
|
+
refute_deserializes(Tree1View, make_tree('arbitrary parent', 'editable child', 'rule:uneditable_children', 'uneditable child')) { |v, r|
|
346
|
+
dig_tree(v, r, 'tree2', 'tree1', 'tree2')['val'] = 'change'
|
347
347
|
}
|
348
348
|
|
349
|
-
assert_deserializes(Tree1View, make_tree(
|
350
|
-
dig_tree(v, r,
|
349
|
+
assert_deserializes(Tree1View, make_tree('rule:uneditable_children', 'uneditable child', 'arbitrary parent', 'editable child')) { |v, r|
|
350
|
+
dig_tree(v, r, 'tree2', 'tree1', 'tree2')['val'] = 'change'
|
351
351
|
}
|
352
352
|
end
|
353
353
|
|
354
354
|
def test_type_independence
|
355
|
-
TestAccessControl.view
|
356
|
-
visible_if!(
|
357
|
-
view.val ==
|
355
|
+
TestAccessControl.view 'Tree1' do
|
356
|
+
visible_if!('tree1 visible') do
|
357
|
+
view.val == 'tree1visible'
|
358
358
|
end
|
359
359
|
end
|
360
360
|
|
361
|
-
TestAccessControl.view
|
362
|
-
visible_if!(
|
363
|
-
view.val ==
|
361
|
+
TestAccessControl.view 'Tree2' do
|
362
|
+
visible_if!('tree2 visible') do
|
363
|
+
view.val == 'tree2visible'
|
364
364
|
end
|
365
365
|
end
|
366
366
|
|
367
|
-
refute_serializes(Tree1View, make_tree(
|
368
|
-
assert_serializes(Tree1View, make_tree(
|
369
|
-
refute_serializes(Tree1View, make_tree(
|
367
|
+
refute_serializes(Tree1View, make_tree('tree1invisible', 'tree2visible'))
|
368
|
+
assert_serializes(Tree1View, make_tree('tree1visible', 'tree2visible'))
|
369
|
+
refute_serializes(Tree1View, make_tree('tree1visible', 'tree2invisible'))
|
370
370
|
end
|
371
371
|
|
372
372
|
def test_visibility_always_composition
|
373
|
-
TestAccessControl.view
|
374
|
-
visible_if!(
|
375
|
-
view.val ==
|
373
|
+
TestAccessControl.view 'Tree1' do
|
374
|
+
visible_if!('tree1 visible') do
|
375
|
+
view.val == 'tree1visible'
|
376
376
|
end
|
377
377
|
end
|
378
378
|
|
379
379
|
TestAccessControl.always do
|
380
|
-
visible_if!(
|
381
|
-
view.val ==
|
380
|
+
visible_if!('tree2 visible') do
|
381
|
+
view.val == 'alwaysvisible'
|
382
382
|
end
|
383
383
|
end
|
384
384
|
|
385
|
-
refute_serializes(Tree1View, Tree1.create(val:
|
386
|
-
assert_serializes(Tree1View, Tree1.create(val:
|
387
|
-
assert_serializes(Tree1View, Tree1.create(val:
|
385
|
+
refute_serializes(Tree1View, Tree1.create(val: 'bad'))
|
386
|
+
assert_serializes(Tree1View, Tree1.create(val: 'tree1visible'))
|
387
|
+
assert_serializes(Tree1View, Tree1.create(val: 'alwaysvisible'))
|
388
388
|
end
|
389
389
|
|
390
390
|
def test_editability_always_composition
|
391
|
-
TestAccessControl.view
|
392
|
-
editable_if!(
|
393
|
-
edit_valid_if!(
|
391
|
+
TestAccessControl.view 'Tree1' do
|
392
|
+
editable_if!('editable1') { view.val == 'editable1' }
|
393
|
+
edit_valid_if!('editvalid1') { view.val == 'editvalid1' }
|
394
394
|
end
|
395
395
|
|
396
396
|
TestAccessControl.always do
|
397
|
-
editable_if!(
|
398
|
-
edit_valid_if!(
|
397
|
+
editable_if!('editable2') { view.val == 'editable2' }
|
398
|
+
edit_valid_if!('editvalid2') { view.val == 'editvalid2' }
|
399
399
|
|
400
|
-
visible_if!(
|
400
|
+
visible_if!('always') { true }
|
401
401
|
end
|
402
402
|
|
403
|
-
refute_deserializes(Tree1View, Tree1.create!(val:
|
403
|
+
refute_deserializes(Tree1View, Tree1.create!(val: 'bad')) { |v, _| v['val'] = 'alsobad' }
|
404
404
|
|
405
|
-
assert_deserializes(Tree1View, Tree1.create!(val:
|
406
|
-
assert_deserializes(Tree1View, Tree1.create!(val:
|
405
|
+
assert_deserializes(Tree1View, Tree1.create!(val: 'editable1')) { |v, _| v['val'] = 'unchecked' }
|
406
|
+
assert_deserializes(Tree1View, Tree1.create!(val: 'editable2')) { |v, _| v['val'] = 'unchecked' }
|
407
407
|
|
408
|
-
assert_deserializes(Tree1View, Tree1.create!(val:
|
409
|
-
assert_deserializes(Tree1View, Tree1.create!(val:
|
408
|
+
assert_deserializes(Tree1View, Tree1.create!(val: 'unchecked')) { |v, _| v['val'] = 'editvalid1' }
|
409
|
+
assert_deserializes(Tree1View, Tree1.create!(val: 'unchecked')) { |v, _| v['val'] = 'editvalid2' }
|
410
410
|
end
|
411
411
|
|
412
412
|
def test_ancestry
|
413
|
-
TestAccessControl.view
|
414
|
-
visible_if!(
|
413
|
+
TestAccessControl.view 'Tree1' do
|
414
|
+
visible_if!('parent tree1') { view.val == 'parenttree1' }
|
415
415
|
end
|
416
416
|
|
417
417
|
TestAccessControl.always do
|
418
|
-
visible_if!(
|
418
|
+
visible_if!('parent always') { view.val == 'parentalways' }
|
419
419
|
end
|
420
420
|
|
421
421
|
# Child must be set up after parent is fully defined
|
422
422
|
child_access_control = Class.new(ViewModel::AccessControl::Tree)
|
423
423
|
child_access_control.include_from(TestAccessControl)
|
424
424
|
|
425
|
-
child_access_control.view
|
426
|
-
visible_if!(
|
425
|
+
child_access_control.view 'Tree1' do
|
426
|
+
visible_if!('child tree1') { view.val == 'childtree1' }
|
427
427
|
end
|
428
428
|
|
429
429
|
child_access_control.always do
|
430
|
-
visible_if!(
|
430
|
+
visible_if!('child always') { view.val == 'childalways' }
|
431
431
|
end
|
432
432
|
|
433
433
|
s_ctx = Tree1View.new_serialize_context(access_control: child_access_control.new)
|
434
434
|
|
435
|
-
refute_serializes(Tree1View, Tree1.create!(val:
|
435
|
+
refute_serializes(Tree1View, Tree1.create!(val: 'bad'), serialize_context: s_ctx)
|
436
436
|
|
437
|
-
assert_serializes(Tree1View, Tree1.create!(val:
|
438
|
-
assert_serializes(Tree1View, Tree1.create!(val:
|
439
|
-
assert_serializes(Tree1View, Tree1.create!(val:
|
440
|
-
assert_serializes(Tree1View, Tree1.create!(val:
|
437
|
+
assert_serializes(Tree1View, Tree1.create!(val: 'parenttree1'), serialize_context: s_ctx)
|
438
|
+
assert_serializes(Tree1View, Tree1.create!(val: 'parentalways'), serialize_context: s_ctx)
|
439
|
+
assert_serializes(Tree1View, Tree1.create!(val: 'childtree1'), serialize_context: s_ctx)
|
440
|
+
assert_serializes(Tree1View, Tree1.create!(val: 'childalways'), serialize_context: s_ctx)
|
441
441
|
end
|
442
442
|
end
|
443
443
|
|
@@ -574,7 +574,7 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
574
574
|
model_class.new(
|
575
575
|
name: 'a',
|
576
576
|
children: [child_model_class.new(name: 'x', position: 1),
|
577
|
-
child_model_class.new(name: 'y', position: 2)])
|
577
|
+
child_model_class.new(name: 'y', position: 2),])
|
578
578
|
end
|
579
579
|
|
580
580
|
it 'records new children' do
|
@@ -619,7 +619,7 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
619
619
|
|
620
620
|
new_child = vm.children.detect { |c| c.name == 'b' }
|
621
621
|
c_changes = ctx.valid_edit_changes(new_child.to_reference)
|
622
|
-
assert_changes_match(c_changes, n:true, att: ['name'])
|
622
|
+
assert_changes_match(c_changes, n: true, att: ['name'])
|
623
623
|
|
624
624
|
oc_changes = ctx.valid_edit_changes(
|
625
625
|
ViewModel::Reference.new(child_viewmodel_class, replaced_child.id))
|