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.
- 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))
|