iknow_view_models 3.6.1 → 3.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f05051b924e9602a09da08b3aec5e5c4de224753d70d29523d66eb34db6947f
4
- data.tar.gz: aac9f7453c54bb216d3cf59b438070c2857f186138c8ff871abaf026a94e4ab4
3
+ metadata.gz: 267212f1572c3f81b65462be6c30e02a12a6562a29d01bd5e0c342a12396acbc
4
+ data.tar.gz: e8c4addd984351d6df889ba7d75abbe8d5bde7793a48791e24a1b42e7a3fdf42
5
5
  SHA512:
6
- metadata.gz: 3edbbb12ef310f58fea255589d2e2112cc62760518af31628c1121b0ea1db9634cb5ace9657087bceecd7f1bd38188fe9bec8b7e03c88613b773fafd2c6b7b6a
7
- data.tar.gz: b063d4884cf2846bef6baca22bbc7f8533ddb2c876bf5ffa5e456a5834d7729881b0d08aab69ff524b5cf0fd451599346cce022502334f2bba26673b753a8068
6
+ metadata.gz: 6039dcc05bed20bce4681c322c3509be3f2a276c0a755fb3e27240eec74fd9eddef68f3c272df33554407853065a98e0f4d03a068c8c0e068cfc02be6f73ab67
7
+ data.tar.gz: 95fb89d93724d5028905612d846534916c4bdacd1bb88009ae434090b183e17d7311bb2709bfa81b08ff3dff45940ced0ce080ff861529c59cbb96671a661b64
data/Gemfile CHANGED
@@ -13,3 +13,4 @@ gem 'minitest-ci'
13
13
  # Override gemspec for development version preferences
14
14
  gem 'activerecord', '~> 7.0.0'
15
15
  gem 'activesupport', '~> 7.0.0'
16
+ gem 'actionpack', '~> 7.0.0'
@@ -5,5 +5,6 @@ source 'https://rubygems.org'
5
5
  gem 'minitest-ci'
6
6
  gem 'activerecord', '~> 5.2.0'
7
7
  gem 'activesupport', '~> 5.2.0'
8
+ gem 'actionpack', '~> 5.2.0'
8
9
 
9
10
  gemspec path: '../'
@@ -5,5 +5,6 @@ source 'https://rubygems.org'
5
5
  gem 'minitest-ci'
6
6
  gem 'activerecord', '~> 6.0.0'
7
7
  gem 'activesupport', '~> 6.0.0'
8
+ gem 'actionpack', '~> 6.0.0'
8
9
 
9
10
  gemspec path: '../'
@@ -5,5 +5,6 @@ source 'https://rubygems.org'
5
5
  gem 'minitest-ci'
6
6
  gem 'activerecord', '~> 6.1.0'
7
7
  gem 'activesupport', '~> 6.1.0'
8
+ gem 'actionpack', '~> 6.1.0'
8
9
 
9
10
  gemspec path: '../'
@@ -5,5 +5,6 @@ source "https://rubygems.org"
5
5
  gem 'minitest-ci'
6
6
  gem "activerecord", "~> 7.0.0"
7
7
  gem "activesupport", "~> 7.0.0"
8
+ gem "actionpack", "~> 7.0.0"
8
9
 
9
10
  gemspec path: '../'
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.required_ruby_version = '>= 2.7'
23
23
 
24
+ spec.add_dependency 'actionpack', '>= 5.0'
24
25
  spec.add_dependency 'activerecord', '>= 5.0'
25
26
  spec.add_dependency 'activesupport', '>= 5.0'
26
27
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.6.1'
4
+ VERSION = '3.6.2'
5
5
  end
@@ -39,7 +39,7 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
39
39
  case
40
40
  when new_allow
41
41
  nil
42
- when self.allow_error && other.allow_error
42
+ when mergeable_error?(self.allow_error) && mergeable_error?(other.allow_error)
43
43
  self.allow_error.merge(other.allow_error)
44
44
  else
45
45
  self.allow_error || other.allow_error
@@ -48,6 +48,12 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
48
48
  ComposedResult.new(new_allow, other.veto, new_allow_error, other.veto_error)
49
49
  end
50
50
  end
51
+
52
+ private
53
+
54
+ def mergeable_error?(err)
55
+ err&.is_a?(NoRequiredConditionsError)
56
+ end
51
57
  end
52
58
 
53
59
  PermissionsCheck = Struct.new(:location, :reason, :error_type, :checker) do
@@ -196,22 +202,54 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl
196
202
  protected
197
203
 
198
204
  def check_delegates(env, ifs, unlesses)
199
- vetoed_checker = unlesses.detect { |checker| checker.check(env) }
205
+ veto, veto_error = detect_veto(env, unlesses)
206
+ allow, allow_error = detect_allow(env, ifs)
207
+
208
+ ComposedResult.new(allow, veto, allow_error, veto_error)
209
+ end
210
+
211
+ private
212
+
213
+ def detect_veto(env, checkers)
214
+ checkers.each do |checker|
215
+ result = checker.check(env)
216
+ next unless result
200
217
 
201
- veto = vetoed_checker.present?
202
- if veto
203
- veto_error = vetoed_checker.error_type.new('Action not permitted because: ' +
204
- vetoed_checker.reason,
205
- env.view.blame_reference)
218
+ error =
219
+ if result.is_a?(StandardError)
220
+ result
221
+ else
222
+ checker.error_type.new('Action not permitted because: ' +
223
+ checker.reason,
224
+ env.view.blame_reference)
225
+ end
226
+
227
+ # short-circuit exit with failure
228
+ return true, error
206
229
  end
207
230
 
208
- allow = ifs.any? { |checker| checker.check(env) }
231
+ return false, nil
232
+ end
233
+
234
+ def detect_allow(env, checkers)
235
+ error = nil
236
+
237
+ checkers.each do |checker|
238
+ result = checker.check(env)
239
+ next unless result
209
240
 
210
- unless allow
211
- allow_error = NoRequiredConditionsError.new(env.view.blame_reference,
212
- ifs.map(&:name))
241
+ if result.is_a?(StandardError)
242
+ error ||= result
243
+ else
244
+ # short-circuit exit with success
245
+ return true, nil
246
+ end
213
247
  end
214
248
 
215
- ComposedResult.new(allow, veto, allow_error, veto_error)
249
+ error ||= NoRequiredConditionsError.new(
250
+ env.view.blame_reference,
251
+ checkers.map(&:name))
252
+
253
+ return false, error
216
254
  end
217
255
  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'
@@ -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
- module RailsDummyCache
19
- def cache
20
- DUMMY_RAILS_CACHE
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.1
4
+ version: 3.6.2
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-01-12 00:00:00.000000000 Z
11
+ date: 2022-02-16 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