iknow_view_models 3.2.13 → 3.2.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3991314cdb010347b417869ff3e2e22b0e7adb4ab590e25739cb6b12bf49a219
4
- data.tar.gz: 0cd5994d07bdd9b9d311732be56b01a51d7b4f1eb71f7fa1d0217bf71911b97f
3
+ metadata.gz: ae5588513419124ea7eac511b571af52ea42e23d9bf87c01cf59a60af50e8055
4
+ data.tar.gz: c7de424a0a465c65fa4ba6823f7aa3743c69c0d41a8efa9da9c67efb030931d8
5
5
  SHA512:
6
- metadata.gz: a5cd70dd6f79db33a97dbdfb05eefec460c7e332784839ad461cf5a359307ce8abedb39c046d42d04a3b7355280d1ad54948cb6cc61fdc35effa0f684ea4c821
7
- data.tar.gz: 6a673b6387892588434f981b57f8131e9c7a97991cc16d3901e72f043b9429efd0fef7a8e1f44cb0ea1ee364256d63d48f198a10e270b487d3da61311780eec7
6
+ metadata.gz: dbcde44426aa3f49d963f4f6588ea49f2fce10ae616e8dd5d78af30bac20a09381fe2e369829b92888c23078136f12a2f6a4b2fd49be17e4920927ed918dc04d
7
+ data.tar.gz: da13c596cb4ec09f17d74c79d9c2c17b8e3b9b2405856b49355f8d02be015d8cb430984fc2adf1c6f3fa668332152b8d440e5972204b66694d0a181cc1213f52
data/.circleci/config.yml CHANGED
@@ -111,6 +111,11 @@ workflows:
111
111
  ruby-version: "2.7"
112
112
  pg-version: "12"
113
113
  gemfile: gemfiles/rails_6_0.gemfile
114
+ - test:
115
+ name: 'ruby 2.7 rails 6.1 pg 12'
116
+ ruby-version: "2.7"
117
+ pg-version: "12"
118
+ gemfile: gemfiles/rails_6_1.gemfile
114
119
  - publish:
115
120
  filters:
116
121
  branches:
data/Appraisals CHANGED
@@ -7,3 +7,8 @@ appraise 'rails-6-0' do
7
7
  gem 'activerecord', '~> 6.0.0'
8
8
  gem 'activesupport', '~> 6.0.0'
9
9
  end
10
+
11
+ appraise 'rails-6-1' do
12
+ gem 'activerecord', '~> 6.1.0'
13
+ gem 'activesupport', '~> 6.1.0'
14
+ end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # IknowViewModels
2
2
 
3
- [![Build Status](https://travis-ci.org/iknow/iknow_view_models.svg?branch=master)](https://travis-ci.org/iknow/iknow_view_models)
3
+ [![Build Status](https://circleci.com/gh/iknow/iknow_view_models.svg?style=svg)](https://circleci.com/gh/iknow/iknow_view_models/)
4
4
 
5
5
  ViewModels provide a means of encapsulating a collection of related data and specifying its JSON serialization.
6
6
 
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'minitest-ci'
6
+ gem 'activerecord', '~> 6.1.0'
7
+ gem 'activesupport', '~> 6.1.0'
8
+
9
+ gemspec path: '../'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.2.13'
4
+ VERSION = '3.2.14'
5
5
  end
data/lib/view_model.rb CHANGED
@@ -173,6 +173,17 @@ class ViewModel
173
173
  Jbuilder.new { |json| serialize(viewmodel, json, serialize_context: serialize_context) }.attributes!
174
174
  end
175
175
 
176
+ def serialize_from_cache(views, migration_versions: {}, locked: false, serialize_context:)
177
+ plural = views.is_a?(Array)
178
+ views = Array.wrap(views)
179
+
180
+ json_views, json_refs = ViewModel::ActiveRecord::Cache.render_viewmodels_from_cache(
181
+ views, locked: locked, migration_versions: migration_versions, serialize_context: serialize_context)
182
+
183
+ json_views = json_views.first unless plural
184
+ return json_views, json_refs
185
+ end
186
+
176
187
  def encode_json(value)
177
188
  # Jbuilder#encode no longer uses MultiJson, but instead calls `.to_json`. In
178
189
  # the context of ActiveSupport, we don't want this, because AS replaces the
@@ -11,6 +11,30 @@ class ViewModel::ActiveRecord::Cache
11
11
 
12
12
  attr_reader :viewmodel_class
13
13
 
14
+ class << self
15
+ def render_viewmodels_from_cache(viewmodels, migration_versions: {}, locked: false, serialize_context: nil)
16
+ if viewmodels.empty?
17
+ return [], {}
18
+ end
19
+
20
+ ids = viewmodels.map(&:id)
21
+ # ideally the roots wouldn't have to all be the same type
22
+ viewmodel_class = viewmodels.first.class
23
+ serialize_context ||= viewmodel_class.new_serialize_context
24
+
25
+ render_from_cache(viewmodel_class, ids,
26
+ initial_viewmodels: viewmodels,
27
+ migration_versions: migration_versions,
28
+ locked: locked,
29
+ serialize_context: serialize_context)
30
+ end
31
+
32
+ def render_from_cache(viewmodel_class, ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: viewmodel_class.new_serialize_context)
33
+ worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context)
34
+ worker.render_from_cache(viewmodel_class, ids, initial_viewmodels: initial_viewmodels, locked: locked)
35
+ end
36
+ end
37
+
14
38
  # If cache_group: is specified, it must be a group of a single key: `:id`
15
39
  def initialize(viewmodel_class, cache_group: nil)
16
40
  @viewmodel_class = viewmodel_class
@@ -35,85 +59,98 @@ class ViewModel::ActiveRecord::Cache
35
59
  @cache_group.invalidate_cache_group
36
60
  end
37
61
 
62
+ # @deprecated Replaced by class methods
38
63
  def fetch_by_viewmodel(viewmodels, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
39
64
  ids = viewmodels.map(&:id)
40
65
  fetch(ids, initial_viewmodels: viewmodels, migration_versions: migration_versions, locked: locked, serialize_context: serialize_context)
41
66
  end
42
67
 
68
+ # @deprecated Replaced by class methods
43
69
  def fetch(ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
44
- data_serializations = Array.new(ids.size)
45
- worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context)
46
-
47
- # If initial root viewmodels were provided, visit them to ensure that they
48
- # are visible. Other than this, no traversal callbacks are performed, as a
49
- # view may be resolved from the cache without ever loading its viewmodel.
50
- # Note that if unlocked, these views will be reloaded as part of obtaining a
51
- # share lock. If the visibility of this viewmodel can change due to edits,
52
- # it is necessary to obtain a lock before calling `fetch`.
53
- initial_viewmodels&.each do |v|
54
- serialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeVisit, v)
55
- serialize_context.run_callback(ViewModel::Callbacks::Hook::AfterVisit, v)
56
- end
70
+ self.class.render_from_cache(@viewmodel_class, ids,
71
+ initial_viewmodels: initial_viewmodels, locked: locked,
72
+ migration_versions: migration_versions, serialize_context: serialize_context)
73
+ end
57
74
 
58
- # Collect input array positions for each id, allowing duplicates
59
- positions = ids.each_with_index.with_object({}) do |(id, i), h|
60
- (h[id] ||= []) << i
61
- end
75
+ class CacheWorker
76
+ SENTINEL = Object.new
77
+ WorklistEntry = Struct.new(:ref_name, :viewmodel)
62
78
 
63
- # Fetch duplicates only once
64
- ids = positions.keys
79
+ attr_reader :migration_versions, :serialize_context, :resolved_references
65
80
 
66
- # Load existing serializations from the cache
67
- cached_serializations = worker.load_from_cache(self, ids)
68
- cached_serializations.each do |id, data|
69
- positions[id].each do |idx|
70
- data_serializations[idx] = data
71
- end
81
+ def initialize(migration_versions:, serialize_context:)
82
+ @worklist = {} # Hash[type_name, Hash[id, WorklistEntry]]
83
+ @resolved_references = {} # Hash[refname, json]
84
+ @migration_versions = migration_versions
85
+ @migrated_cache_versions = {}
86
+ @serialize_context = serialize_context
72
87
  end
73
88
 
74
- # Resolve and serialize missing views
75
- missing_ids = ids.to_set.subtract(cached_serializations.keys)
89
+ def render_from_cache(viewmodel_class, ids, initial_viewmodels: nil, locked: false)
90
+ viewmodel_class.transaction do
91
+ root_serializations = Array.new(ids.size)
76
92
 
77
- # If initial viewmodels have been locked, we can serialize them for cache
78
- # misses.
79
- available_viewmodels =
80
- if locked
81
- initial_viewmodels&.each_with_object({}) do |vm, h|
82
- h[vm.id] = vm if missing_ids.include?(vm.id)
93
+ # Collect input array positions for each id, allowing duplicates
94
+ positions = ids.each_with_index.with_object({}) do |(id, i), h|
95
+ (h[id] ||= []) << i
83
96
  end
84
- end
85
97
 
86
- @viewmodel_class.transaction do
87
- # Load remaining views and serialize
88
- viewmodels = worker.find_and_preload_viewmodels(@viewmodel_class, missing_ids.to_a,
89
- available_viewmodels: available_viewmodels)
98
+ # If duplicates are specified, fetch each only once
99
+ ids = positions.keys
100
+
101
+ ids_to_render = ids.to_set
102
+
103
+ if viewmodel_class < CacheableView
104
+ # Load existing serializations from the cache
105
+ cached_serializations = load_from_cache(viewmodel_class.viewmodel_cache, ids)
106
+ cached_serializations.each do |id, data|
107
+ positions[id].each do |idx|
108
+ root_serializations[idx] = data
109
+ end
110
+ end
90
111
 
91
- loaded_serializations = worker.serialize_and_cache(viewmodels)
92
- loaded_serializations.each do |id, data|
93
- positions[id].each do |idx|
94
- data_serializations[idx] = data
112
+ ids_to_render.subtract(cached_serializations.keys)
113
+
114
+ # If initial root viewmodels were provided, call hooks on any
115
+ # viewmodels which were rendered from the cache to ensure that the
116
+ # root is visible (in isolation). Other than this, no traversal
117
+ # callbacks are performed for cache-rendered views. This particularly
118
+ # requires care for references: if a visible view may refer to
119
+ # non-visible cacheable views, those referenced views will not be
120
+ # access control checked.
121
+ initial_viewmodels&.each do |v|
122
+ next unless cached_serializations.has_key?(v.id)
123
+ serialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeVisit, v)
124
+ serialize_context.run_callback(ViewModel::Callbacks::Hook::AfterVisit, v)
125
+ end
95
126
  end
96
- end
97
127
 
98
- # Resolve references
99
- worker.resolve_references!
128
+ # Render remaining views. If initial viewmodels have been locked, we may
129
+ # use them to serialize from, otherwise we must reload with share lock
130
+ # in find_and_preload.
131
+ available_viewmodels =
132
+ if locked
133
+ initial_viewmodels&.each_with_object({}) do |vm, h|
134
+ h[vm.id] = vm if ids_to_render.include?(vm.id)
135
+ end
136
+ end
100
137
 
101
- return data_serializations, worker.resolved_references
102
- end
103
- end
138
+ viewmodels = find_and_preload_viewmodels(viewmodel_class, ids_to_render.to_a,
139
+ available_viewmodels: available_viewmodels)
104
140
 
105
- class CacheWorker
106
- SENTINEL = Object.new
107
- WorklistEntry = Struct.new(:ref_name, :viewmodel)
141
+ loaded_serializations = serialize_and_cache(viewmodels)
108
142
 
109
- attr_reader :migration_versions, :serialize_context, :resolved_references
143
+ loaded_serializations.each do |id, data|
144
+ positions[id].each do |idx|
145
+ root_serializations[idx] = data
146
+ end
147
+ end
110
148
 
111
- def initialize(migration_versions:, serialize_context:)
112
- @worklist = {}
113
- @resolved_references = {}
114
- @migration_versions = migration_versions
115
- @migrated_cache_versions = {}
116
- @serialize_context = serialize_context
149
+ # recursively resolve referenced views
150
+ self.resolve_references!
151
+
152
+ [root_serializations, self.resolved_references]
153
+ end
117
154
  end
118
155
 
119
156
  def resolve_references!
@@ -39,14 +39,6 @@ module ViewModel::ActiveRecord::Cache::CacheableView
39
39
  def viewmodel_cache
40
40
  @viewmodel_cache
41
41
  end
42
-
43
- def serialize_from_cache(views, migration_versions: {}, locked: false, serialize_context:)
44
- plural = views.is_a?(Array)
45
- views = Array.wrap(views)
46
- json_views, json_refs = viewmodel_cache.fetch_by_viewmodel(views, locked: locked, migration_versions: migration_versions, serialize_context: serialize_context)
47
- json_views = json_views.first unless plural
48
- return json_views, json_refs
49
- end
50
42
  end
51
43
 
52
44
  # Clear the cache if the view or its nested children were changed during
@@ -135,8 +135,8 @@ class ViewModel::ActiveRecord
135
135
  [data, refs]
136
136
  end
137
137
 
138
- def fetch_with_cache
139
- viewmodel_class.viewmodel_cache.fetch([root.id], migration_versions: migration_versions)
138
+ def fetch_with_cache(**rest)
139
+ ViewModel::ActiveRecord::Cache.render_from_cache(viewmodel_class, [root.id], migration_versions: migration_versions, **rest)
140
140
  end
141
141
 
142
142
  def serialize_with_cache
@@ -206,6 +206,19 @@ class ViewModel::ActiveRecord
206
206
 
207
207
  describe 'without migrations' do
208
208
  include BehavesLikeACache
209
+
210
+ it 'returns the right serialization with provided initial viewmodel' do
211
+ fetched_result = parse_result(fetch_with_cache(initial_viewmodels: [root_view]))
212
+
213
+ value(fetched_result).must_equal(serialize_from_database)
214
+ end
215
+
216
+ it 'returns the right serialization with provided locked initial viewmodel' do
217
+ locked_root_view = viewmodel_class.new(model_class.lock("FOR SHARE").find(root.id))
218
+ fetched_result = parse_result(fetch_with_cache(initial_viewmodels: [locked_root_view], locked: true))
219
+
220
+ value(fetched_result).must_equal(serialize_from_database)
221
+ end
209
222
  end
210
223
 
211
224
  describe 'with migrations' do
@@ -337,8 +350,8 @@ class ViewModel::ActiveRecord
337
350
  [data, refs]
338
351
  end
339
352
 
340
- def fetch_with_cache
341
- viewmodel_class.viewmodel_cache.fetch([root.id, root2.id])
353
+ def fetch_with_cache(**rest)
354
+ ViewModel::ActiveRecord::Cache.render_from_cache(viewmodel_class, [root.id, root2.id], **rest)
342
355
  end
343
356
 
344
357
  it 'merges matching shared references between cache hits and misses' do
@@ -376,8 +389,8 @@ class ViewModel::ActiveRecord
376
389
  end
377
390
 
378
391
  describe 'when fetched by viewmodel' do
379
- def fetch_with_cache
380
- viewmodel_class.viewmodel_cache.fetch_by_viewmodel([root_view])
392
+ def fetch_with_cache(**rest)
393
+ ViewModel::ActiveRecord::Cache.render_viewmodels_from_cache([root_view], **rest)
381
394
  end
382
395
 
383
396
  include CacheableParentAndChildren
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iknow_view_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.13
4
+ version: 3.2.14
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-02-19 00:00:00.000000000 Z
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -379,6 +379,7 @@ files:
379
379
  - Rakefile
380
380
  - gemfiles/rails_5_2.gemfile
381
381
  - gemfiles/rails_6_0.gemfile
382
+ - gemfiles/rails_6_1.gemfile
382
383
  - iknow_view_models.gemspec
383
384
  - lib/iknow_view_models.rb
384
385
  - lib/iknow_view_models/railtie.rb