vident 0.1.0 → 0.2.0

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: 481f74d4370e94387dfa81c6d6287e8a5a8dd022d6e353d656cfa3807f069b03
4
- data.tar.gz: b462c2871a4f48b2193ad91a630a773d37efb581d5efae91699523602d18b68f
3
+ metadata.gz: 43ac831925166a175434c569389eb4c2a035fd07fbe1166da8deeef8c96b9b6b
4
+ data.tar.gz: ba8ac8e5c95cbecbf8952fd2ac3dcf476a777f52f420d3c126abce7c63368c27
5
5
  SHA512:
6
- metadata.gz: d9eea045322f4bad4bcbc03bf4985163674385c08db20aa5aec398edabf56c428aeac532bacdbfd32f3188d3e5a7cc5f6b3bea4c9ba1c51209befd32c5810128
7
- data.tar.gz: 06b566851995e5b4873dcca265345900b89cf516a13ae50fe4a6d08c848764e8fd6f7fd0c520d71985347a29befbcae8bd514639c525541bded0617f4188899a
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", "~> 0.5.3", require: "phlex/rails"
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
- # Example
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vident
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/vident.rb CHANGED
@@ -31,3 +31,4 @@ require_relative "vident/root_component/using_view_component"
31
31
  require_relative "vident/base"
32
32
  require_relative "vident/component"
33
33
  require_relative "vident/typed_component"
34
+ require_relative "vident/caching/cache_key"
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.1.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-01 00:00:00.000000000 Z
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.7
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,