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 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,