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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 573266179096880a34febada583570484e420b91a242920198cfa119192c3fbc
4
- data.tar.gz: c024eb9398a4b0d49103d1806379d0f20312f7c6965e9fe2751b09bb720f2fc3
3
+ metadata.gz: 0f71d69093b0d35214edcfe799aa3fc4493e0640a349e93c828e0158260d4b6c
4
+ data.tar.gz: 2c2368cd3b0bb27f37ef8e609eae6a396d4de12c4555b90c153386e8bd2a9998
5
5
  SHA512:
6
- metadata.gz: 353f42c901c4d52cba6bbe459cc6266c2e59f3a56ca432bac616cd4534c95116d0a0e27b8329799717259640e098c704c4f6b63c83713cd42cb3096c6ca6c5b3
7
- data.tar.gz: 4e74aa987e3ff26dd9b14bb3fbebc291ce86087dca8f8bcc93ef9b320d7d7ed22f150ce4f3153e27c51f6a2f8d6561fe53cdcb7ac45734caf156f50b10a543d1
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"
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: circleci/ruby:<< parameters.ruby-version >>
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: circleci/postgres:<< parameters.pg-version >>-alpine
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
@@ -12,3 +12,8 @@ appraise 'rails-6-1' do
12
12
  gem 'activerecord', '~> 6.1.0'
13
13
  gem 'activesupport', '~> 6.1.0'
14
14
  end
15
+
16
+ appraise 'rails-7-0' do
17
+ gem 'activerecord', '~> 7.0.0'
18
+ gem 'activesupport', '~> 7.0.0'
19
+ end
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', '~> 6.0.0'
15
- gem 'activesupport', '~> 6.0.0'
14
+ gem 'activerecord', '~> 7.0.0'
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: '../'
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'minitest-ci'
6
+ gem "activerecord", "~> 7.0.0"
7
+ gem "activesupport", "~> 7.0.0"
8
+ gem "actionpack", "~> 7.0.0"
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.0'
4
+ VERSION = '3.6.3'
5
5
  end
@@ -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 && other.allow_error
43
- self.allow_error.merge(other.allow_error)
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 || other.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
- vetoed_checker = unlesses.detect { |checker| checker.check(env) }
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
- 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)
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
- allow = ifs.any? { |checker| checker.check(env) }
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
- unless allow
211
- allow_error = NoRequiredConditionsError.new(env.view.blame_reference,
212
- ifs.map(&:name))
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
- ComposedResult.new(allow, veto, allow_error, veto_error)
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} from #{from} to #{to}"
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::Dependencies::Reference.clear!
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
@@ -3,6 +3,7 @@
3
3
  # A ViewModel encapsulates a particular aggregation of data calculated via the
4
4
  # underlying models and provides a means of serializing it into views.
5
5
  require 'jbuilder'
6
+ require 'base64'
6
7
  require 'deep_preloader'
7
8
 
8
9
  class ViewModel
data/nix/dependencies.nix CHANGED
@@ -1,5 +1,5 @@
1
1
  {pkgs}:
2
2
  {
3
- ruby = pkgs.ruby_2_7;
3
+ ruby = pkgs.ruby_3_0;
4
4
  postgresql = pkgs.postgresql_12;
5
5
  }
@@ -323,7 +323,11 @@ module ControllerTestControllers
323
323
  CONTROLLER_NAMES.each do |name|
324
324
  Object.send(:remove_const, name)
325
325
  end
326
- ActiveSupport::Dependencies::Reference.clear!
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
- 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
@@ -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
- ActiveSupport::Dependencies::Reference.clear!
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.0
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: 2021-12-17 00:00:00.000000000 Z
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.1.6
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