solder 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: caab8b73cfc913261226022ae6e2e0d67fd6643164fa5d2b9058d7a8eb298cb1
4
- data.tar.gz: a03a6323c422e4ee59cc89ccbc3b6431b3b82f755378ca7057d5868fde4e181a
3
+ metadata.gz: 9d86c83657628b64222a9656e535f19ca6d66a4e675c3e80e7b86d4c2a0f3835
4
+ data.tar.gz: 42f8a93bca5208c7c0bab9e1d5cf764f8dfa70564dd2c43952d580df8fee8f77
5
5
  SHA512:
6
- metadata.gz: 1dc4b63179b081c60def903eae5ee336209a8c90eebe8136c43f2f5f3dd1f932a9b1b2745c672cf3c80f421243433f1228f6db36acc29bd838f0fba11b63c62e
7
- data.tar.gz: 2d02e8e58009e38c21568c5d8aaa3dc97bfa6f42682865b4d2c13dba549dc76c4913670460dcf4a4ea0ac1c1cedb14138baf97133fcc13d349405a229671f5ee
6
+ metadata.gz: cf18a4e8990db861c48cb81585afff0e6d14d3baa98fe2769947c383af10f9aeefe5c73d0787dbf27f994920eef3aeac4910a07bf7386813bc3dde0ae1a857d9
7
+ data.tar.gz: 0b08006eda6254c442df937076b51980a01b47becff672f3bca401ba73d73af641f6e1d684a325cff4396c3c58c068bbbeb491e5b109efa5335866ab83040423
data/README.md CHANGED
@@ -1,12 +1,30 @@
1
1
  # 🧑‍🏭 Solder
2
- Short description and motivation.
2
+ Persist and restore ephemeral attributes of HTML elements using the Rails cache store and StimulusJS
3
3
 
4
- ## Usage
5
- How to use my plugin.
4
+ In short, this plugin will persist UI state changes a user makes on a per-element basis.
5
+
6
+ This is useful for
7
+ - storing the open/closed state of a sidebar, accordion, etc.
8
+ - storing the state of tree views (e.g. storefront categories),
9
+ - custom dashboard layouts
10
+ - etc.
11
+
12
+ ![A Cyborg Hand Soldering, Steampunk Style](https://user-images.githubusercontent.com/4352208/208506264-db5abac6-7d33-4504-9c0d-2d5f8c26994b.png)
13
+
14
+ _Image: [openjourney](https://replicate.com/prompthero/openjourney), prompt: mdjrny-v4 style cyborg soldering a piece of code onto a web application user interface, 8k, steampunk_
6
15
 
7
- ### Persistence
16
+ ## Rationale
8
17
 
9
- Uses the active Rails cache store, possibly more adapters to come
18
+ Modern server-side rendering techniques like Turbo Frames/Streams, StimulusReflex and others require to persist state on the server to facilitate rerendering without UI discrepancies.
19
+
20
+ Typically, you have a few options to achieve this:
21
+ - ActiveRecord,
22
+ - the `session`,
23
+ - [Kredis](https://github.com/rails/kredis)
24
+ are the most frequent ones. It requires you to invent keys to access the state of UI elements, e.g. `session[:collapsed_categories]` etc.
25
+ Experience shows that the management of those keys tends to increase complexity.
26
+
27
+ Hence, the part that this gem takes care of is the automatic generation and management of those keys.
10
28
 
11
29
  ## Installation
12
30
  Add this line to your application's Gemfile:
@@ -25,6 +43,25 @@ Or install it yourself as:
25
43
  $ gem install solder
26
44
  ```
27
45
 
46
+ ## Usage
47
+
48
+ In your view, use the `solder_onto` helper to create a unique key for the element whose attributes you want to track. For example, imagine we have an online store with multiple category-specific landing pages. There's a tree view on it for further filtering items:
49
+
50
+ ```erb
51
+ <%= solder_onto([current_user, @category] do %>
52
+ <details>
53
+ <summary>Outdoor Equipment</summary>
54
+
55
+ <details> ... </details>
56
+ </details>
57
+ <% end %>
58
+ ```
59
+
60
+ **IMPORTANT**:
61
+ 1. The helper only takes care of the attributes of the **first child** within it. This is because of how [`capture`](https://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-capture) works.
62
+ 2. Think of the first argument to `solder_onto` as something akin to a cache key. You want it to be unique with regard to (almost always) the logged in user, and any other record it is scoped to. In fact, it adheres to the same interface as [`cache`](https://api.rubyonrails.org/classes/ActionView/Helpers/CacheHelper.html#method-i-cache)
63
+
64
+
28
65
  ## Dependencies
29
66
 
30
67
  ### Ruby
@@ -34,7 +71,10 @@ $ gem install solder
34
71
 
35
72
  - Stimulus
36
73
  - @rails/request.js
37
- - Turbo
74
+
75
+ ## Persistence
76
+
77
+ Uses the active Rails cache store, possibly more adapters to come.
38
78
 
39
79
  ## Contributing
40
80
  Contribution directions go here.
data/Rakefile CHANGED
@@ -6,3 +6,13 @@ load "rails/tasks/engine.rake"
6
6
  load "rails/tasks/statistics.rake"
7
7
 
8
8
  require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/**/*_test.rb"
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -1,13 +1,15 @@
1
1
  module Solder
2
2
  class UiStateController < ApplicationController
3
3
  before_action :set_ui_state, only: :show
4
+ around_action Solder.config[:around_action]
4
5
 
5
6
  def show
6
7
  render json: @ui_state.to_json
7
8
  end
8
9
 
9
10
  def update
10
- Rails.cache.write "solder/#{params[:key]}", JSON.parse(params[:attributes])
11
+ Rails.cache.write "solder/#{ui_state_params[:key]}", parsed_attributes
12
+ records_to_touch.map(&:touch)
11
13
 
12
14
  head :ok
13
15
  end
@@ -19,7 +21,15 @@ module Solder
19
21
  end
20
22
 
21
23
  def set_ui_state
22
- @ui_state = Rails.cache.read "solder/#{params[:key]}"
24
+ @ui_state = Rails.cache.read "solder/#{ui_state_params[:key]}"
25
+ end
26
+
27
+ def records_to_touch
28
+ GlobalID::Locator.locate_many_signed parsed_attributes["data-solder-touch"]&.split(":") || []
29
+ end
30
+
31
+ def parsed_attributes
32
+ JSON.parse(ui_state_params[:attributes])
23
33
  end
24
34
  end
25
35
  end
@@ -1,5 +1,27 @@
1
1
  module Solder
2
2
  module ApplicationHelper
3
+ def solder_onto(name, touch: [], attribute_safelist: ["class"], &block)
4
+ soldered_html = capture(&block).to_s.strip
5
+ fragment = Nokogiri::HTML.fragment(soldered_html)
6
+
7
+ first_fragment_child = fragment.first_element_child
8
+
9
+ # rehydrate
10
+ ui_state = Rails.cache.read "solder/#{solder_key(name)}"
11
+
12
+ ui_state&.select { attribute_safelist.include?(_1) }&.each do |attribute_name, value|
13
+ first_fragment_child[attribute_name] = sanitize(value, tags: [])
14
+ end
15
+
16
+ # add stimulus controller and create unique key
17
+ first_fragment_child["data-controller"] = "#{first_fragment_child["data-controller"]} solder".strip
18
+ first_fragment_child["data-solder-key-value"] = solder_key(name)
19
+
20
+ first_fragment_child["data-solder-touch"] ||= Array(touch).map(&:to_sgid).map(&:to_s).join(":")
21
+
22
+ first_fragment_child.to_html.html_safe
23
+ end
24
+
3
25
  def solder_key(name)
4
26
  key = cache_fragment_name(name, skip_digest: true)
5
27
  .flatten
@@ -7,7 +29,7 @@ module Solder
7
29
  .map(&:cache_key)
8
30
  .join(":")
9
31
 
10
- key += ":#{caller(1..1).first}"
32
+ key += ":#{caller.find { _1 =~ /html/ }}"
11
33
 
12
34
  ActiveSupport::Digest.hexdigest(key)
13
35
  end
@@ -1,12 +1,35 @@
1
1
  module Solder
2
2
  module ApplicationHelper
3
+ def solder_onto(name, touch: [], attribute_safelist: ["class"], &block)
4
+ soldered_html = capture(&block).to_s.strip
5
+ fragment = Nokogiri::HTML.fragment(soldered_html)
6
+
7
+ first_fragment_child = fragment.first_element_child
8
+
9
+ # rehydrate
10
+ ui_state = Rails.cache.read "solder/#{solder_key(name)}"
11
+
12
+ ui_state&.select { attribute_safelist.include?(_1) }&.each do |attribute_name, value|
13
+ first_fragment_child[attribute_name] = sanitize value
14
+ end
15
+
16
+ # add stimulus controller and create unique key
17
+ first_fragment_child["data-controller"] = "#{first_fragment_child["data-controller"]} solder".strip
18
+ first_fragment_child["data-solder-key-value"] = solder_key(name)
19
+
20
+ first_fragment_child["data-solder-touch"] ||= Array(touch).map(&:to_sgid).map(&:to_s).join(":")
21
+
22
+ first_fragment_child.to_html.html_safe
23
+ end
24
+
3
25
  def solder_key(name)
4
26
  key = cache_fragment_name(name, skip_digest: true)
5
27
  .flatten
6
28
  .compact
7
29
  .map(&:cache_key)
8
30
  .join(":")
9
- key += ":#{caller(1..1).first}"
31
+
32
+ key += ":#{caller.find { _1 =~ /html/ }}"
10
33
 
11
34
  ActiveSupport::Digest.hexdigest(key)
12
35
  end
data/lib/solder/engine.rb CHANGED
@@ -1,7 +1,14 @@
1
1
  module Solder
2
+ def self.config
3
+ Rails.application.config.solder
4
+ end
5
+
2
6
  class Engine < ::Rails::Engine
3
7
  isolate_namespace Solder
4
8
 
9
+ config.solder = ActiveSupport::OrderedOptions.new
10
+ config.solder[:around_action] = ->(_controller, action) { action.call }
11
+
5
12
  initializer "solder.check_caching" do |app|
6
13
  unless app.config.action_controller.perform_caching && app.config.cache_store != :null_store
7
14
  puts <<~WARN
@@ -15,8 +22,11 @@ module Solder
15
22
  end
16
23
  end
17
24
 
18
- config.after_initialize do
19
- ::ApplicationHelper.include helpers
25
+ initializer "solder.helpers" do
26
+ ActiveSupport.on_load(:action_controller_base) do
27
+ include Solder::ApplicationHelper
28
+ helper Solder::Engine.helpers
29
+ end
20
30
  end
21
31
  end
22
32
  end
@@ -1,16 +1,29 @@
1
1
  module Solder
2
+ def self.config
3
+ Rails.application.config.solder
4
+ end
5
+
2
6
  class Engine < ::Rails::Engine
3
7
  isolate_namespace Solder
4
8
 
5
- initializer "solder.check_caching" do
6
- unless Rails.application.config.action_controller.perform_caching && Rails.application.config.cache_store != :null_store
9
+ config.solder = ActiveSupport::OrderedOptions.new
10
+ config.solder[:around_action] = ->(_controller, action) { action.call }
11
+
12
+ initializer "solder.check_caching" do |app|
13
+ unless app.config.action_controller.perform_caching && app.config.cache_store != :null_store
7
14
  puts <<~WARN
8
- Solder uses the Rails cache store to provide UI state persistence. Therefore, please make sure caching is enabled in your environment.Engine
15
+ 🧑‍🏭 Solder uses the Rails cache store to provide UI state persistence. Therefore, please make sure caching is enabled in your environment.
9
16
 
10
17
  To enable caching in development, run:
11
18
  rails dev:cache
12
19
  WARN
20
+
21
+ exit false
13
22
  end
14
23
  end
24
+
25
+ config.after_initialize do
26
+ ::ApplicationHelper.include helpers
27
+ end
15
28
  end
16
29
  end
@@ -1,3 +1,3 @@
1
1
  module Solder
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,3 @@
1
+ module Solder
2
+ VERSION = "0.1.0"
3
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solder
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
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-12 00:00:00.000000000 Z
11
+ date: 2023-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rails
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +53,19 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '1.1'
41
55
  - !ruby/object:Gem::Dependency
42
- name: turbo-rails
56
+ name: mocha
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '1.1'
48
- type: :runtime
61
+ version: '0'
62
+ type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '1.1'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: solargraph-rails
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +134,7 @@ files:
120
134
  - lib/solder/engine.rb
121
135
  - lib/solder/engine.rb~
122
136
  - lib/solder/version.rb
137
+ - lib/solder/version.rb~
123
138
  - lib/tasks/solder_tasks.rake
124
139
  homepage: https://github.com/julianrubisch/solder
125
140
  licenses: []