iknow_view_models 3.6.1 → 3.6.4
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/.circleci/config.yml +10 -10
- data/Gemfile +1 -0
- data/gemfiles/rails_5_2.gemfile +1 -0
- data/gemfiles/rails_6_0.gemfile +1 -0
- data/gemfiles/rails_6_1.gemfile +1 -0
- data/gemfiles/rails_7_0.gemfile +1 -0
- data/iknow_view_models.gemspec +1 -0
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model/access_control/composed.rb +66 -19
- data/lib/view_model/access_control/tree.rb +4 -6
- data/lib/view_model/access_control.rb +8 -0
- data/lib/view_model/migration/no_path_error.rb +1 -1
- data/lib/view_model.rb +1 -0
- data/test/unit/view_model/access_control_test.rb +68 -0
- data/test/unit/view_model/active_record/cache_test.rb +3 -8
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31ecbd493e6716db0442e309c1566f38c13f676ef72a55ea9708f1537875331f
|
4
|
+
data.tar.gz: b03d5b1b1f0d3ba458f11f5575f379969eea72089f781ef65462ab47e181e158
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06e8384611d8fe7e93ac6065c11aca651f15b717be02a0818106198b769fe6ea59c843a83fd7cd457089a44a98cda499e7f5a2ae905d6c4b46c04e18c68cd4af
|
7
|
+
data.tar.gz: 3c20e972b8a5011aeae40494bf8d271ec3ef5092bbccb7495ea435a7e99dbf45b89f632e868c56bdf681722251f4432e6bd426dba2bfd9d2ef93cce7fc71c066
|
data/.circleci/config.yml
CHANGED
@@ -8,7 +8,7 @@ executors:
|
|
8
8
|
default: "2.7"
|
9
9
|
pg-version:
|
10
10
|
type: string
|
11
|
-
default: "
|
11
|
+
default: "12.10"
|
12
12
|
gemfile:
|
13
13
|
type: string
|
14
14
|
default: "Gemfile"
|
@@ -16,14 +16,14 @@ executors:
|
|
16
16
|
PGHOST: 127.0.0.1
|
17
17
|
PGUSER: eikaiwa
|
18
18
|
docker:
|
19
|
-
- image:
|
19
|
+
- image: cimg/ruby:<< parameters.ruby-version >>
|
20
20
|
environment:
|
21
21
|
BUNDLE_JOBS: 3
|
22
22
|
BUNDLE_RETRY: 3
|
23
23
|
BUNDLE_PATH: vendor/bundle
|
24
24
|
RAILS_ENV: test
|
25
25
|
BUNDLE_GEMFILE: << parameters.gemfile >>
|
26
|
-
- image:
|
26
|
+
- image: cimg/postgres:<< parameters.pg-version >>
|
27
27
|
environment:
|
28
28
|
POSTGRES_USER: eikaiwa
|
29
29
|
POSTGRES_DB: iknow_view_models
|
@@ -104,27 +104,27 @@ workflows:
|
|
104
104
|
- test:
|
105
105
|
name: 'ruby 2.7 rails 5.2 pg 12'
|
106
106
|
ruby-version: "2.7"
|
107
|
-
pg-version: "12"
|
107
|
+
pg-version: "12.10"
|
108
108
|
gemfile: gemfiles/rails_5_2.gemfile
|
109
109
|
- test:
|
110
110
|
name: 'ruby 2.7 rails 6.0 pg 12'
|
111
111
|
ruby-version: "2.7"
|
112
|
-
pg-version: "12"
|
112
|
+
pg-version: "12.10"
|
113
113
|
gemfile: gemfiles/rails_6_0.gemfile
|
114
114
|
- test:
|
115
115
|
name: 'ruby 2.7 rails 6.1 pg 12'
|
116
116
|
ruby-version: "2.7"
|
117
|
-
pg-version: "12"
|
117
|
+
pg-version: "12.10"
|
118
118
|
gemfile: gemfiles/rails_6_1.gemfile
|
119
119
|
- test:
|
120
120
|
name: 'ruby 3.0 rails 6.1 pg 12'
|
121
121
|
ruby-version: "3.0"
|
122
|
-
pg-version: "12"
|
122
|
+
pg-version: "12.10"
|
123
123
|
gemfile: gemfiles/rails_6_1.gemfile
|
124
124
|
- test:
|
125
|
-
name: 'ruby 3.
|
126
|
-
ruby-version: "3.
|
127
|
-
pg-version: "
|
125
|
+
name: 'ruby 3.1 rails 7.0 pg 13'
|
126
|
+
ruby-version: "3.1"
|
127
|
+
pg-version: "13.6"
|
128
128
|
gemfile: gemfiles/rails_7_0.gemfile
|
129
129
|
- publish:
|
130
130
|
filters:
|
data/Gemfile
CHANGED
data/gemfiles/rails_5_2.gemfile
CHANGED
data/gemfiles/rails_6_0.gemfile
CHANGED
data/gemfiles/rails_6_1.gemfile
CHANGED
data/gemfiles/rails_7_0.gemfile
CHANGED
data/iknow_view_models.gemspec
CHANGED
@@ -39,15 +39,31 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
|
|
39
39
|
case
|
40
40
|
when new_allow
|
41
41
|
nil
|
42
|
-
when self.allow_error
|
43
|
-
|
42
|
+
when self.allow_error.nil?
|
43
|
+
other.allow_error
|
44
|
+
when other.allow_error.nil?
|
45
|
+
self.allow_error
|
46
|
+
# Mergeable (standard) errors should be merged if possible; if not
|
47
|
+
# possible, we should take the first non-standard error.
|
48
|
+
when mergeable_error?(self.allow_error)
|
49
|
+
if mergeable_error?(other.allow_error)
|
50
|
+
self.allow_error.merge(other.allow_error)
|
51
|
+
else
|
52
|
+
other.allow_error
|
53
|
+
end
|
44
54
|
else
|
45
|
-
self.allow_error
|
55
|
+
self.allow_error
|
46
56
|
end
|
47
57
|
|
48
58
|
ComposedResult.new(new_allow, other.veto, new_allow_error, other.veto_error)
|
49
59
|
end
|
50
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def mergeable_error?(err)
|
65
|
+
err.is_a?(NoRequiredConditionsError)
|
66
|
+
end
|
51
67
|
end
|
52
68
|
|
53
69
|
PermissionsCheck = Struct.new(:location, :reason, :error_type, :checker) do
|
@@ -159,11 +175,10 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
|
|
159
175
|
end
|
160
176
|
|
161
177
|
def inspect
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
s
|
178
|
+
checks = inspect_checks
|
179
|
+
checks << "includes checkers: #{@included_checkers.inspect}" if @included_checkers.present?
|
180
|
+
|
181
|
+
super + '(' + checks.join(', ') + ')'
|
167
182
|
end
|
168
183
|
|
169
184
|
def inspect_checks
|
@@ -196,22 +211,54 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
|
|
196
211
|
protected
|
197
212
|
|
198
213
|
def check_delegates(env, ifs, unlesses)
|
199
|
-
|
214
|
+
veto, veto_error = detect_veto(env, unlesses)
|
215
|
+
allow, allow_error = detect_allow(env, ifs)
|
216
|
+
|
217
|
+
ComposedResult.new(allow, veto, allow_error, veto_error)
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def detect_veto(env, checkers)
|
223
|
+
checkers.each do |checker|
|
224
|
+
result = checker.check(env)
|
225
|
+
next unless result
|
200
226
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
227
|
+
error =
|
228
|
+
if result.is_a?(StandardError)
|
229
|
+
result
|
230
|
+
else
|
231
|
+
checker.error_type.new('Action not permitted because: ' +
|
232
|
+
checker.reason,
|
233
|
+
env.view.blame_reference)
|
234
|
+
end
|
235
|
+
|
236
|
+
# short-circuit exit with failure
|
237
|
+
return true, error
|
206
238
|
end
|
207
239
|
|
208
|
-
|
240
|
+
return false, nil
|
241
|
+
end
|
242
|
+
|
243
|
+
def detect_allow(env, checkers)
|
244
|
+
error = nil
|
245
|
+
|
246
|
+
checkers.each do |checker|
|
247
|
+
result = checker.check(env)
|
248
|
+
next unless result
|
209
249
|
|
210
|
-
|
211
|
-
|
212
|
-
|
250
|
+
if result.is_a?(StandardError)
|
251
|
+
error ||= result
|
252
|
+
else
|
253
|
+
# short-circuit exit with success
|
254
|
+
return true, nil
|
255
|
+
end
|
213
256
|
end
|
214
257
|
|
215
|
-
|
258
|
+
error ||= NoRequiredConditionsError.new(
|
259
|
+
env.view.blame_reference,
|
260
|
+
checkers.map(&:name))
|
261
|
+
|
262
|
+
return false, error
|
216
263
|
end
|
217
264
|
end
|
@@ -192,8 +192,6 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
192
192
|
def inspect_checks
|
193
193
|
checks = super
|
194
194
|
if root?
|
195
|
-
checks << 'no root checks'
|
196
|
-
else
|
197
195
|
checks << "root_children_visible_if: #{root_children_visible_ifs.map(&:reason)}" if root_children_visible_ifs.present?
|
198
196
|
checks << "root_children_visible_unless: #{root_children_visible_unlesses.map(&:reason)}" if root_children_visible_unlesses.present?
|
199
197
|
checks << "root_children_editable_if: #{root_children_editable_ifs.map(&:reason)}" if root_children_editable_ifs.present?
|
@@ -252,16 +250,16 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
252
250
|
|
253
251
|
def save_root_visibility!(traversal_env)
|
254
252
|
result = check_delegates(traversal_env,
|
255
|
-
self.class.each_check(:root_children_visible_ifs, ->(a) { a
|
256
|
-
self.class.each_check(:root_children_visible_unlesses, ->(a) { a
|
253
|
+
self.class.each_check(:root_children_visible_ifs, ->(a) { a < Node }),
|
254
|
+
self.class.each_check(:root_children_visible_unlesses, ->(a) { a < Node }))
|
257
255
|
|
258
256
|
store_descendent_visibility(traversal_env.view, result)
|
259
257
|
end
|
260
258
|
|
261
259
|
def save_root_editability!(traversal_env)
|
262
260
|
result = check_delegates(traversal_env,
|
263
|
-
self.class.each_check(:root_children_editable_ifs, ->(a) { a
|
264
|
-
self.class.each_check(:root_children_editable_unlesses, ->(a) { a
|
261
|
+
self.class.each_check(:root_children_editable_ifs, ->(a) { a < Node }),
|
262
|
+
self.class.each_check(:root_children_editable_unlesses, ->(a) { a < Node }))
|
265
263
|
|
266
264
|
store_descendent_editability(traversal_env.view, result)
|
267
265
|
end
|
@@ -149,6 +149,14 @@ class ViewModel::AccessControl
|
|
149
149
|
def raise_if_error!(result)
|
150
150
|
raise (result.error || yield) unless result.permit?
|
151
151
|
end
|
152
|
+
|
153
|
+
# Called from composed access controls via the `env`, this is used to make the
|
154
|
+
# if/unless DSL more readable when returning a custom failure error.
|
155
|
+
def failure(err)
|
156
|
+
raise ArgumentError.new("Unexpected failure type: #{err}") unless err.is_a?(StandardError)
|
157
|
+
|
158
|
+
err
|
159
|
+
end
|
152
160
|
end
|
153
161
|
|
154
162
|
require 'view_model/access_control/open'
|
@@ -14,7 +14,7 @@ class ViewModel::Migration::NoPathError < ViewModel::AbstractError
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def detail
|
17
|
-
"No migration path for #{vm_name}
|
17
|
+
"No migration path for #{vm_name} between client version #{from} and server version #{to}"
|
18
18
|
end
|
19
19
|
|
20
20
|
def meta
|
data/lib/view_model.rb
CHANGED
@@ -156,6 +156,54 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
156
156
|
assert_equal(2, ex.reasons.count)
|
157
157
|
end
|
158
158
|
|
159
|
+
def test_veto_ordering
|
160
|
+
TestAccessControl.visible_if!('always') { true }
|
161
|
+
|
162
|
+
TestAccessControl.visible_unless!('car starts with i') do
|
163
|
+
view.car =~ /^i/
|
164
|
+
end
|
165
|
+
|
166
|
+
TestAccessControl.visible_unless!('car ends with e') do
|
167
|
+
view.car =~ /e$/
|
168
|
+
end
|
169
|
+
|
170
|
+
assert_serializes(ListView, List.create!(car: 'ok'))
|
171
|
+
refute_serializes(ListView, List.create!(car: 'invisible'), /not permitted.*car starts with i/)
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_custom_error_if
|
175
|
+
TestAccessControl.visible_if!('car is visible1') do
|
176
|
+
view.car == 'visible1' ||
|
177
|
+
# In principle a failure() may return any error, but by returning an
|
178
|
+
# AccessControlError we make it possible to test with refute_serializes
|
179
|
+
failure(ViewModel::AccessControlError.new('Custom Error Message', view.blame_reference))
|
180
|
+
end
|
181
|
+
|
182
|
+
TestAccessControl.visible_if!('car is visible2') do
|
183
|
+
view.car == 'visible2' ||
|
184
|
+
# Only the first failure() recorded by a failed if check will be
|
185
|
+
# raised as the error.
|
186
|
+
failure(ViewModel::AccessControlError.new('Should not be seen', view.blame_reference))
|
187
|
+
end
|
188
|
+
|
189
|
+
assert_serializes(ListView, List.create!(car: 'visible1'))
|
190
|
+
assert_serializes(ListView, List.create!(car: 'visible2'))
|
191
|
+
refute_serializes(ListView, List.create!(car: 'bad'), /Custom Error Message/)
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_custom_error_unless
|
195
|
+
TestAccessControl.visible_if!('always') { true }
|
196
|
+
|
197
|
+
TestAccessControl.visible_unless!('car is invisible') do
|
198
|
+
if view.car == 'invisible'
|
199
|
+
failure(ViewModel::AccessControlError.new('Custom Error Message', view.blame_reference))
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
assert_serializes(ListView, List.create!(car: 'ok'))
|
204
|
+
refute_serializes(ListView, List.create!(car: 'invisible'), /Custom Error Message/)
|
205
|
+
end
|
206
|
+
|
159
207
|
def test_inheritance
|
160
208
|
child_access_control = Class.new(ViewModel::AccessControl::Composed)
|
161
209
|
child_access_control.include_from(TestAccessControl)
|
@@ -271,6 +319,26 @@ class ViewModel::AccessControlTest < ActiveSupport::TestCase
|
|
271
319
|
assert_serializes(Tree1View, make_tree('rule:visible_children', 'visible child', 'rule:visible_children', 'visible child'))
|
272
320
|
end
|
273
321
|
|
322
|
+
def test_root_inheritance
|
323
|
+
parent_access_control = Class.new(ViewModel::AccessControl::Tree)
|
324
|
+
parent_access_control.view 'Tree1' do
|
325
|
+
visible_if!('true') { true }
|
326
|
+
|
327
|
+
root_children_visible_if!('root children visible') do
|
328
|
+
view.val == 'rule:visible_children'
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
TestAccessControl.include_from(parent_access_control)
|
333
|
+
|
334
|
+
refute_serializes(Tree1View, make_tree('arbitrary parent', 'invisible child'))
|
335
|
+
assert_serializes(Tree1View, make_tree('rule:visible_children', 'visible child'))
|
336
|
+
|
337
|
+
# nested root
|
338
|
+
refute_serializes(Tree1View, make_tree('rule:visible_children', 'visible child', 'arbitrary parent', 'invisible child'))
|
339
|
+
assert_serializes(Tree1View, make_tree('rule:visible_children', 'visible child', 'rule:visible_children', 'visible child'))
|
340
|
+
end
|
341
|
+
|
274
342
|
def test_visibility_veto_from_root
|
275
343
|
TestAccessControl.view 'Tree1' do
|
276
344
|
root_children_visible_unless!('root children invisible') do
|
@@ -15,16 +15,11 @@ require 'view_model/active_record'
|
|
15
15
|
|
16
16
|
DUMMY_RAILS_CACHE = ActiveSupport::Cache::MemoryStore.new
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
18
|
+
IknowCache.configure! do
|
19
|
+
logger ::ActiveRecord::Base.logger
|
20
|
+
cache DUMMY_RAILS_CACHE
|
22
21
|
end
|
23
22
|
|
24
|
-
# Ensure we have a dummy Rails, and then prepend our dummy cache
|
25
|
-
module Rails; end
|
26
|
-
Rails.singleton_class.prepend(RailsDummyCache)
|
27
|
-
|
28
23
|
class ViewModel::ActiveRecord
|
29
24
|
class CacheTest < ActiveSupport::TestCase
|
30
25
|
using ViewModel::Utils::Collections
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iknow_view_models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.6.
|
4
|
+
version: 3.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iKnow Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: actionpack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: activerecord
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -495,7 +509,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
495
509
|
- !ruby/object:Gem::Version
|
496
510
|
version: '0'
|
497
511
|
requirements: []
|
498
|
-
rubygems_version: 3.
|
512
|
+
rubygems_version: 3.3.11
|
499
513
|
signing_key:
|
500
514
|
specification_version: 4
|
501
515
|
summary: ViewModels provide a means of encapsulating a collection of related data
|