iknow_view_models 3.6.0 → 3.6.3
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 +12 -7
- data/Appraisals +5 -0
- data/Gemfile +3 -2
- 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 +10 -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 +62 -14
- data/lib/view_model/access_control.rb +8 -0
- data/lib/view_model/active_record/update_context.rb +2 -2
- data/lib/view_model/migration/no_path_error.rb +1 -1
- data/lib/view_model/test_helpers/arvm_builder.rb +4 -1
- data/lib/view_model.rb +1 -0
- data/nix/dependencies.nix +1 -1
- data/test/helpers/controller_test_helpers.rb +5 -1
- data/test/unit/view_model/access_control_test.rb +48 -0
- data/test/unit/view_model/active_record/cache_test.rb +3 -8
- data/test/unit/view_model/record_test.rb +5 -1
- metadata +18 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f71d69093b0d35214edcfe799aa3fc4493e0640a349e93c828e0158260d4b6c
|
|
4
|
+
data.tar.gz: 2c2368cd3b0bb27f37ef8e609eae6a396d4de12c4555b90c153386e8bd2a9998
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c27c10d2940db8b9ed7f0ec512873053f4a2d94283d11004d68b4b70153f696fe3fd45aa103f7ee02ed75fbf3c5d0d8ac899b9df769196308cf67c32dbb3af35
|
|
7
|
+
data.tar.gz: 882931e849905b9caaf8af154725339b7cda761b1f3ad2deb4d93c4f489cb11c8699143df1e907e51d572ec0a93c07d2f960c86f3e6c35b844d2f86c2839eb66
|
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,23 +104,28 @@ 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
|
+
- test:
|
|
125
|
+
name: 'ruby 3.1 rails 7.0 pg 13'
|
|
126
|
+
ruby-version: "3.1"
|
|
127
|
+
pg-version: "13.6"
|
|
128
|
+
gemfile: gemfiles/rails_7_0.gemfile
|
|
124
129
|
- publish:
|
|
125
130
|
filters:
|
|
126
131
|
branches:
|
data/Appraisals
CHANGED
data/Gemfile
CHANGED
|
@@ -11,5 +11,6 @@ gem 'rubocop-iknow'
|
|
|
11
11
|
gem 'minitest-ci'
|
|
12
12
|
|
|
13
13
|
# Override gemspec for development version preferences
|
|
14
|
-
gem 'activerecord', '~>
|
|
15
|
-
gem 'activesupport', '~>
|
|
14
|
+
gem 'activerecord', '~> 7.0.0'
|
|
15
|
+
gem 'activesupport', '~> 7.0.0'
|
|
16
|
+
gem 'actionpack', '~> 7.0.0'
|
data/gemfiles/rails_5_2.gemfile
CHANGED
data/gemfiles/rails_6_0.gemfile
CHANGED
data/gemfiles/rails_6_1.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
|
|
@@ -196,22 +212,54 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
|
|
|
196
212
|
protected
|
|
197
213
|
|
|
198
214
|
def check_delegates(env, ifs, unlesses)
|
|
199
|
-
|
|
215
|
+
veto, veto_error = detect_veto(env, unlesses)
|
|
216
|
+
allow, allow_error = detect_allow(env, ifs)
|
|
217
|
+
|
|
218
|
+
ComposedResult.new(allow, veto, allow_error, veto_error)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
def detect_veto(env, checkers)
|
|
224
|
+
checkers.each do |checker|
|
|
225
|
+
result = checker.check(env)
|
|
226
|
+
next unless result
|
|
200
227
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
228
|
+
error =
|
|
229
|
+
if result.is_a?(StandardError)
|
|
230
|
+
result
|
|
231
|
+
else
|
|
232
|
+
checker.error_type.new('Action not permitted because: ' +
|
|
233
|
+
checker.reason,
|
|
234
|
+
env.view.blame_reference)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# short-circuit exit with failure
|
|
238
|
+
return true, error
|
|
206
239
|
end
|
|
207
240
|
|
|
208
|
-
|
|
241
|
+
return false, nil
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def detect_allow(env, checkers)
|
|
245
|
+
error = nil
|
|
246
|
+
|
|
247
|
+
checkers.each do |checker|
|
|
248
|
+
result = checker.check(env)
|
|
249
|
+
next unless result
|
|
209
250
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
251
|
+
if result.is_a?(StandardError)
|
|
252
|
+
error ||= result
|
|
253
|
+
else
|
|
254
|
+
# short-circuit exit with success
|
|
255
|
+
return true, nil
|
|
256
|
+
end
|
|
213
257
|
end
|
|
214
258
|
|
|
215
|
-
|
|
259
|
+
error ||= NoRequiredConditionsError.new(
|
|
260
|
+
env.view.blame_reference,
|
|
261
|
+
checkers.map(&:name))
|
|
262
|
+
|
|
263
|
+
return false, error
|
|
216
264
|
end
|
|
217
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,9 +14,9 @@ class ViewModel::ActiveRecord
|
|
|
14
14
|
def release!
|
|
15
15
|
model = viewmodel.model
|
|
16
16
|
case association_data.direct_reflection.options[:dependent]
|
|
17
|
-
when :delete
|
|
17
|
+
when :delete, :delete_all
|
|
18
18
|
model.delete
|
|
19
|
-
when :destroy
|
|
19
|
+
when :destroy, :destroy_async
|
|
20
20
|
model.destroy
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -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
|
|
@@ -65,8 +65,11 @@ class ViewModel::TestHelpers::ARVMBuilder
|
|
|
65
65
|
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{name.underscore.pluralize} CASCADE")
|
|
66
66
|
namespace.send(:remove_const, name)
|
|
67
67
|
namespace.send(:remove_const, viewmodel_name) if viewmodel
|
|
68
|
+
|
|
68
69
|
# prevent cached old class from being used to resolve associations
|
|
69
|
-
ActiveSupport::
|
|
70
|
+
if ActiveSupport::VERSION::MAJOR < 7
|
|
71
|
+
ActiveSupport::Dependencies::Reference.clear!
|
|
72
|
+
end
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
private
|
data/lib/view_model.rb
CHANGED
data/nix/dependencies.nix
CHANGED
|
@@ -323,7 +323,11 @@ module ControllerTestControllers
|
|
|
323
323
|
CONTROLLER_NAMES.each do |name|
|
|
324
324
|
Object.send(:remove_const, name)
|
|
325
325
|
end
|
|
326
|
-
|
|
326
|
+
|
|
327
|
+
if ActiveSupport::VERSION::MAJOR < 7
|
|
328
|
+
ActiveSupport::Dependencies::Reference.clear!
|
|
329
|
+
end
|
|
330
|
+
|
|
327
331
|
super
|
|
328
332
|
end
|
|
329
333
|
end
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -472,7 +472,11 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
|
472
472
|
def teardown
|
|
473
473
|
Object.send(:remove_const, :Nested)
|
|
474
474
|
Object.send(:remove_const, :NestedView)
|
|
475
|
-
|
|
475
|
+
|
|
476
|
+
if ActiveSupport::VERSION::MAJOR < 7
|
|
477
|
+
ActiveSupport::Dependencies::Reference.clear!
|
|
478
|
+
end
|
|
479
|
+
|
|
476
480
|
super
|
|
477
481
|
end
|
|
478
482
|
|
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.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- iKnow Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-05-23 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
|
|
@@ -380,6 +394,7 @@ files:
|
|
|
380
394
|
- gemfiles/rails_5_2.gemfile
|
|
381
395
|
- gemfiles/rails_6_0.gemfile
|
|
382
396
|
- gemfiles/rails_6_1.gemfile
|
|
397
|
+
- gemfiles/rails_7_0.gemfile
|
|
383
398
|
- iknow_view_models.gemspec
|
|
384
399
|
- lib/iknow_view_models.rb
|
|
385
400
|
- lib/iknow_view_models/railtie.rb
|
|
@@ -494,7 +509,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
494
509
|
- !ruby/object:Gem::Version
|
|
495
510
|
version: '0'
|
|
496
511
|
requirements: []
|
|
497
|
-
rubygems_version: 3.
|
|
512
|
+
rubygems_version: 3.3.11
|
|
498
513
|
signing_key:
|
|
499
514
|
specification_version: 4
|
|
500
515
|
summary: ViewModels provide a means of encapsulating a collection of related data
|