vident 0.1.0 → 0.2.0
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/Gemfile +3 -1
- data/README.md +20 -8
- data/lib/vident/base.rb +0 -66
- data/lib/vident/caching/cache_key.rb +162 -0
- data/lib/vident/version.rb +1 -1
- data/lib/vident.rb +1 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43ac831925166a175434c569389eb4c2a035fd07fbe1166da8deeef8c96b9b6b
|
4
|
+
data.tar.gz: ba8ac8e5c95cbecbf8952fd2ac3dcf476a777f52f420d3c126abce7c63368c27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b9f4399542e09fbe1bf5b9de41b7b0b7069c2e27a7b479ee65c6be09c54df1f78309c0ad1e2ed378299617e5b73fb99dc6e3dc5d9af89e8e4dbecf82c868fca
|
7
|
+
data.tar.gz: 4d39061d988c2b2b25c3a11c098d4e3328ab1e9d094ef09312278aa290574c227bd6bea9ba9086697cb213960fce5b87ac7abd0acb815816bf28035d10ac64a0
|
data/Gemfile
CHANGED
@@ -17,7 +17,9 @@ gem "stimulus-rails"
|
|
17
17
|
|
18
18
|
gem "dry-struct"
|
19
19
|
|
20
|
-
gem "phlex
|
20
|
+
gem "phlex-rails"
|
21
|
+
|
22
|
+
# FIXME: versions greater than 2.74 cause issues: https://github.com/ViewComponent/view_component/pull/1571
|
21
23
|
gem "view_component", "2.74.1"
|
22
24
|
|
23
25
|
gem "sqlite3"
|
data/README.md
CHANGED
@@ -39,7 +39,26 @@ This gem is a work in progress and I would love to get your feedback and contrib
|
|
39
39
|
exposes a simple API for configuring and adding Stimulus controllers, targets and actions. Normally you create these
|
40
40
|
using the `root` helper method on `Vident::Component`/`Vident::TypedComponent`.
|
41
41
|
|
42
|
-
|
42
|
+
|
43
|
+
# Examples
|
44
|
+
|
45
|
+
Before we dive into a specific example note that there are some components implemented with
|
46
|
+
both ViewComponent and Phlex (with and without Vident) in the `test/dummy`.
|
47
|
+
- https://github.com/stevegeek/vident/tree/main/test/dummy/app/components
|
48
|
+
- https://github.com/stevegeek/vident/tree/main/test/dummy/app/views
|
49
|
+
|
50
|
+
Start Rails:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
cd test/dummy
|
54
|
+
bundle install
|
55
|
+
rails assets:precompile
|
56
|
+
rails s
|
57
|
+
```
|
58
|
+
|
59
|
+
and visit http://localhost:3000
|
60
|
+
|
61
|
+
## ViewComponent + Vident example
|
43
62
|
|
44
63
|
Consider the following ERB that might be part of an application's views. The app uses `ViewComponent`, `Stimulus` and `Vident`.
|
45
64
|
|
@@ -327,14 +346,7 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
327
346
|
|
328
347
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
329
348
|
|
330
|
-
## Running the Examples in `test/dummy`
|
331
349
|
|
332
|
-
```bash
|
333
|
-
cd test/dummy
|
334
|
-
bundle install
|
335
|
-
rails assets:precompile
|
336
|
-
rails s
|
337
|
-
```
|
338
350
|
|
339
351
|
## Contributing
|
340
352
|
|
data/lib/vident/base.rb
CHANGED
@@ -23,66 +23,6 @@ module Vident
|
|
23
23
|
registered_slots.present?
|
24
24
|
end
|
25
25
|
|
26
|
-
# TODO: move stuff related to cache key to a module
|
27
|
-
|
28
|
-
# TypedComponents can be used with fragment caching, but you need to be careful! Read on...
|
29
|
-
#
|
30
|
-
# <% cache component do %>
|
31
|
-
# <%= render component %>
|
32
|
-
# <% end %>
|
33
|
-
#
|
34
|
-
# The most important point is that Rails cannot track dependencies on the component itself, so you need to
|
35
|
-
# be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
|
36
|
-
# component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
|
37
|
-
# depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
|
38
|
-
# will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
|
39
|
-
# then no need to worry about changing the cache key on component changes, only on attribute/data changes.
|
40
|
-
#
|
41
|
-
# A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
|
42
|
-
# as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
|
43
|
-
def depends_on(*klasses)
|
44
|
-
@component_dependencies ||= []
|
45
|
-
@component_dependencies += klasses
|
46
|
-
end
|
47
|
-
|
48
|
-
attr_reader :component_dependencies
|
49
|
-
|
50
|
-
def with_cache_key(*attrs, name: :_collection)
|
51
|
-
raise StandardError, "with_cache_key can only be used on components *without* slots as there is no eary way to track their content changes so too risky" if slots?
|
52
|
-
# Add view file to cache key
|
53
|
-
attrs << :component_modified_time
|
54
|
-
super
|
55
|
-
end
|
56
|
-
|
57
|
-
def component_modified_time
|
58
|
-
return @component_modified_time if Rails.env.production? && @component_modified_time
|
59
|
-
# FIXME: This could stack overflow if there are circular dependencies
|
60
|
-
deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
|
61
|
-
@component_modified_time = deps + sidecar_view_modified_time + rb_component_modified_time
|
62
|
-
end
|
63
|
-
|
64
|
-
def sidecar_view_modified_time
|
65
|
-
return @sidecar_view_modified_time if Rails.env.production? && defined?(@sidecar_view_modified_time)
|
66
|
-
@sidecar_view_modified_time = ::File.exist?(template_path) ? ::File.mtime(template_path).to_i.to_s : ""
|
67
|
-
end
|
68
|
-
|
69
|
-
def rb_component_modified_time
|
70
|
-
return @rb_component_modified_time if Rails.env.production? && defined?(@rb_component_modified_time)
|
71
|
-
@rb_component_modified_time = ::File.exist?(component_path) ? ::File.mtime(component_path).to_i.to_s : ""
|
72
|
-
end
|
73
|
-
|
74
|
-
def template_path
|
75
|
-
File.join components_base_path, "#{virtual_path}.html.erb"
|
76
|
-
end
|
77
|
-
|
78
|
-
def component_path
|
79
|
-
File.join components_base_path, "#{virtual_path}.rb"
|
80
|
-
end
|
81
|
-
|
82
|
-
def components_base_path
|
83
|
-
::Rails.configuration.view_component.view_component_path || "app/components"
|
84
|
-
end
|
85
|
-
|
86
26
|
# Dont check collection params, we use kwargs
|
87
27
|
def validate_collection_parameter!(validate_default: false)
|
88
28
|
end
|
@@ -197,12 +137,6 @@ module Vident
|
|
197
137
|
self.class.stimulus_identifier
|
198
138
|
end
|
199
139
|
|
200
|
-
# TODO: Move to caching module
|
201
|
-
# Component modified time which is combined with other cache key attributes to generate cache key for an instance
|
202
|
-
def component_modified_time
|
203
|
-
self.class.component_modified_time
|
204
|
-
end
|
205
|
-
|
206
140
|
# The `component` class name is used to create the controller name.
|
207
141
|
# The path of the Stimulus controller when none is explicitly set
|
208
142
|
def default_controller_path
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
|
5
|
+
# Rails fragment caching works by either expecting the cached key object to respond to `cache_key` or for that object
|
6
|
+
# to be an array or hash. In our case the object maybe an instance of Core::Presenter so here we add a default
|
7
|
+
# `cache_key` implementation.
|
8
|
+
module Vident
|
9
|
+
module Caching
|
10
|
+
module CacheKey
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def inherited(subclass)
|
15
|
+
subclass.instance_variable_set(
|
16
|
+
:@named_cache_key_attributes,
|
17
|
+
@named_cache_key_attributes.clone
|
18
|
+
)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_cache_key(*attrs, name: :_collection)
|
23
|
+
named_cache_key_includes(name, *attrs)
|
24
|
+
end
|
25
|
+
|
26
|
+
def enable_cache_key_benchmarking
|
27
|
+
@enable_cache_key_benchmarking = true
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :named_cache_key_attributes
|
31
|
+
|
32
|
+
# TypedComponents can be used with fragment caching, but you need to be careful! Read on...
|
33
|
+
#
|
34
|
+
# <% cache component do %>
|
35
|
+
# <%= render component %>
|
36
|
+
# <% end %>
|
37
|
+
#
|
38
|
+
# The most important point is that Rails cannot track dependencies on the component itself, so you need to
|
39
|
+
# be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
|
40
|
+
# component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
|
41
|
+
# depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
|
42
|
+
# will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
|
43
|
+
# then no need to worry about changing the cache key on component changes, only on attribute/data changes.
|
44
|
+
#
|
45
|
+
# A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
|
46
|
+
# as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
|
47
|
+
def depends_on(*klasses)
|
48
|
+
@component_dependencies ||= []
|
49
|
+
@component_dependencies += klasses
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :component_dependencies
|
53
|
+
|
54
|
+
def with_cache_key(*attrs, name: :_collection)
|
55
|
+
raise StandardError, "with_cache_key can only be used on components *without* slots as there is no eary way to track their content changes so too risky" if slots?
|
56
|
+
# Add view file to cache key
|
57
|
+
attrs << :component_modified_time
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def component_modified_time
|
62
|
+
return @component_modified_time if Rails.env.production? && @component_modified_time
|
63
|
+
# FIXME: This could stack overflow if there are circular dependencies
|
64
|
+
deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
|
65
|
+
@component_modified_time = deps + sidecar_view_modified_time + rb_component_modified_time
|
66
|
+
end
|
67
|
+
|
68
|
+
def sidecar_view_modified_time
|
69
|
+
return @sidecar_view_modified_time if Rails.env.production? && defined?(@sidecar_view_modified_time)
|
70
|
+
@sidecar_view_modified_time = ::File.exist?(template_path) ? ::File.mtime(template_path).to_i.to_s : ""
|
71
|
+
end
|
72
|
+
|
73
|
+
def rb_component_modified_time
|
74
|
+
return @rb_component_modified_time if Rails.env.production? && defined?(@rb_component_modified_time)
|
75
|
+
@rb_component_modified_time = ::File.exist?(component_path) ? ::File.mtime(component_path).to_i.to_s : ""
|
76
|
+
end
|
77
|
+
|
78
|
+
def template_path
|
79
|
+
File.join components_base_path, "#{virtual_path}.html.erb"
|
80
|
+
end
|
81
|
+
|
82
|
+
def component_path
|
83
|
+
File.join components_base_path, "#{virtual_path}.rb"
|
84
|
+
end
|
85
|
+
|
86
|
+
def components_base_path
|
87
|
+
::Rails.configuration.view_component.view_component_path || "app/components"
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def named_cache_key_includes(name, *attrs)
|
93
|
+
define_cache_key_method unless @named_cache_key_attributes
|
94
|
+
@named_cache_key_attributes ||= {}
|
95
|
+
@named_cache_key_attributes[name] = attrs
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_cache_key_method
|
99
|
+
# If the presenter defines cache key setup then define the method. Otherwise Rails assumes this
|
100
|
+
# will return a valid key if the class will respond to this
|
101
|
+
define_method :cache_key do |n = :_collection|
|
102
|
+
if defined?(@cache_key)
|
103
|
+
return @cache_key[n] if @cache_key.key?(n)
|
104
|
+
else
|
105
|
+
@cache_key ||= {}
|
106
|
+
end
|
107
|
+
if @enable_cache_key_benchmarking
|
108
|
+
time = ::Benchmark.measure { generate_cache_key(n) }
|
109
|
+
::Logging::Log.debug "Cache key #{self.class.name}: #{time.real}"
|
110
|
+
::Thread.current[:total_key_generation_time] ||= 0
|
111
|
+
::Thread.current[:total_key_generation_time] += time.real
|
112
|
+
else
|
113
|
+
generate_cache_key(n)
|
114
|
+
end
|
115
|
+
@cache_key[n]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Component modified time which is combined with other cache key attributes to generate cache key for an instance
|
121
|
+
def component_modified_time
|
122
|
+
self.class.component_modified_time
|
123
|
+
end
|
124
|
+
|
125
|
+
def cacheable?
|
126
|
+
respond_to? :cache_key
|
127
|
+
end
|
128
|
+
|
129
|
+
def cache_key_modifier
|
130
|
+
ENV["RAILS_CACHE_ID"]
|
131
|
+
end
|
132
|
+
|
133
|
+
def cache_keys_for_sources(key_attributes)
|
134
|
+
sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
|
135
|
+
sources.compact.map do |item|
|
136
|
+
next if item == self
|
137
|
+
generate_item_cache_key_from(item)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate_item_cache_key_from(item)
|
142
|
+
if item.respond_to? :cache_key_with_version
|
143
|
+
item.cache_key_with_version
|
144
|
+
elsif item.respond_to? :cache_key
|
145
|
+
item.cache_key
|
146
|
+
elsif item.is_a?(String)
|
147
|
+
Digest::SHA1.hexdigest(item)
|
148
|
+
else
|
149
|
+
Digest::SHA1.hexdigest(Marshal.dump(item))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def generate_cache_key(index)
|
154
|
+
key_attributes = self.class.named_cache_key_attributes[index]
|
155
|
+
return nil unless key_attributes
|
156
|
+
key = "#{self.class.name}/#{cache_keys_for_sources(key_attributes).join("/")}"
|
157
|
+
raise StandardError, "Cache key for key #{key} is blank!" if key.blank?
|
158
|
+
@cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/vident/version.rb
CHANGED
data/lib/vident.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vident
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Ierodiaconou
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '6.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '6.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
27
33
|
description: Vident makes using Stimulus with your `ViewComponent` or `Phlex` view
|
28
34
|
components as easy as writing Ruby. It also provides a base class for your components
|
29
35
|
to inherit from.
|
@@ -47,6 +53,7 @@ files:
|
|
47
53
|
- lib/vident/attributes/typed_niling_struct.rb
|
48
54
|
- lib/vident/attributes/types.rb
|
49
55
|
- lib/vident/base.rb
|
56
|
+
- lib/vident/caching/cache_key.rb
|
50
57
|
- lib/vident/component.rb
|
51
58
|
- lib/vident/railtie.rb
|
52
59
|
- lib/vident/root_component/base.rb
|
@@ -77,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
84
|
- !ruby/object:Gem::Version
|
78
85
|
version: '0'
|
79
86
|
requirements: []
|
80
|
-
rubygems_version: 3.3.
|
87
|
+
rubygems_version: 3.3.26
|
81
88
|
signing_key:
|
82
89
|
specification_version: 4
|
83
90
|
summary: Vident is a view component base class for your design system implementation,
|