vident 0.1.0 → 0.2.1

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: b58f418c48b9c37897a26d83c5b500bacd54c8f37cdb95bf549e5b283c00a36a
4
+ data.tar.gz: f2f76b2518a26933cac78eb7c7eb3ab6c0c1091a0e0bca020c8672ed7b947594
5
5
  SHA512:
6
- metadata.gz: d9eea045322f4bad4bcbc03bf4985163674385c08db20aa5aec398edabf56c428aeac532bacdbfd32f3188d3e5a7cc5f6b3bea4c9ba1c51209befd32c5810128
7
- data.tar.gz: 06b566851995e5b4873dcca265345900b89cf516a13ae50fe4a6d08c848764e8fd6f7fd0c520d71985347a29befbcae8bd514639c525541bded0617f4188899a
6
+ metadata.gz: ab7baf21555430c664923fdfb1bbfc75a9eb8e56fef39839f1597951c48cf433c89d669898f429dcf87c724ccd5d016d89d06b82193db74dd57f7c3762a93392
7
+ data.tar.gz: a93dd215124bd3b6799e8b1519db632ae6521ce091ae9515b6bf943a3fc5987e5cbc301ecf751f778a891ef0ef6c21161103b07978580c4ca2c6430b5d789d48
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,158 @@
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
+ 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 respond_to?(:slots?) && slots?
24
+ # Add view file to cache key
25
+ attrs << :component_modified_time
26
+ named_cache_key_includes(name, *attrs)
27
+ end
28
+
29
+ def enable_cache_key_benchmarking
30
+ @enable_cache_key_benchmarking = true
31
+ end
32
+
33
+ attr_reader :named_cache_key_attributes
34
+
35
+ # TypedComponents can be used with fragment caching, but you need to be careful! Read on...
36
+ #
37
+ # <% cache component do %>
38
+ # <%= render component %>
39
+ # <% end %>
40
+ #
41
+ # The most important point is that Rails cannot track dependencies on the component itself, so you need to
42
+ # be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
43
+ # component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
44
+ # depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
45
+ # will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
46
+ # then no need to worry about changing the cache key on component changes, only on attribute/data changes.
47
+ #
48
+ # A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
49
+ # as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
50
+ def depends_on(*klasses)
51
+ @component_dependencies ||= []
52
+ @component_dependencies += klasses
53
+ end
54
+
55
+ attr_reader :component_dependencies
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
+ private
87
+
88
+ def named_cache_key_includes(name, *attrs)
89
+ define_cache_key_method unless @named_cache_key_attributes
90
+ @named_cache_key_attributes ||= {}
91
+ @named_cache_key_attributes[name] = attrs
92
+ end
93
+
94
+ def define_cache_key_method
95
+ # If the presenter defines cache key setup then define the method. Otherwise Rails assumes this
96
+ # will return a valid key if the class will respond to this
97
+ define_method :cache_key do |n = :_collection|
98
+ if defined?(@cache_key)
99
+ return @cache_key[n] if @cache_key.key?(n)
100
+ else
101
+ @cache_key ||= {}
102
+ end
103
+ if @enable_cache_key_benchmarking
104
+ time = ::Benchmark.measure { generate_cache_key(n) }
105
+ ::Logging::Log.debug "Cache key #{self.class.name}: #{time.real}"
106
+ ::Thread.current[:total_key_generation_time] ||= 0
107
+ ::Thread.current[:total_key_generation_time] += time.real
108
+ else
109
+ generate_cache_key(n)
110
+ end
111
+ @cache_key[n]
112
+ end
113
+ end
114
+ end
115
+
116
+ # Component modified time which is combined with other cache key attributes to generate cache key for an instance
117
+ def component_modified_time
118
+ self.class.component_modified_time
119
+ end
120
+
121
+ def cacheable?
122
+ respond_to? :cache_key
123
+ end
124
+
125
+ def cache_key_modifier
126
+ ENV["RAILS_CACHE_ID"]
127
+ end
128
+
129
+ def cache_keys_for_sources(key_attributes)
130
+ sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
131
+ sources.compact.map do |item|
132
+ next if item == self
133
+ generate_item_cache_key_from(item)
134
+ end
135
+ end
136
+
137
+ def generate_item_cache_key_from(item)
138
+ if item.respond_to? :cache_key_with_version
139
+ item.cache_key_with_version
140
+ elsif item.respond_to? :cache_key
141
+ item.cache_key
142
+ elsif item.is_a?(String)
143
+ Digest::SHA1.hexdigest(item)
144
+ else
145
+ Digest::SHA1.hexdigest(Marshal.dump(item))
146
+ end
147
+ end
148
+
149
+ def generate_cache_key(index)
150
+ key_attributes = self.class.named_cache_key_attributes[index]
151
+ return nil unless key_attributes
152
+ key = "#{self.class.name}/#{cache_keys_for_sources(key_attributes).join("/")}"
153
+ raise StandardError, "Cache key for key #{key} is blank!" if key.blank?
154
+ @cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
155
+ end
156
+ end
157
+ end
158
+ 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.1"
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.1
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,