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 +4 -4
- data/README.md +46 -6
- data/Rakefile +10 -0
- data/app/controllers/solder/ui_state_controller.rb +12 -2
- data/app/helpers/solder/application_helper.rb +23 -1
- data/app/helpers/solder/application_helper.rb~ +24 -1
- data/lib/solder/engine.rb +12 -2
- data/lib/solder/engine.rb~ +16 -3
- data/lib/solder/version.rb +1 -1
- data/lib/solder/version.rb~ +3 -0
- metadata +21 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d86c83657628b64222a9656e535f19ca6d66a4e675c3e80e7b86d4c2a0f3835
|
4
|
+
data.tar.gz: 42f8a93bca5208c7c0bab9e1d5cf764f8dfa70564dd2c43952d580df8fee8f77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf18a4e8990db861c48cb81585afff0e6d14d3baa98fe2769947c383af10f9aeefe5c73d0787dbf27f994920eef3aeac4910a07bf7386813bc3dde0ae1a857d9
|
7
|
+
data.tar.gz: 0b08006eda6254c442df937076b51980a01b47becff672f3bca401ba73d73af641f6e1d684a325cff4396c3c58c068bbbeb491e5b109efa5335866ab83040423
|
data/README.md
CHANGED
@@ -1,12 +1,30 @@
|
|
1
1
|
# 🧑🏭 Solder
|
2
|
-
|
2
|
+
Persist and restore ephemeral attributes of HTML elements using the Rails cache store and StimulusJS
|
3
3
|
|
4
|
-
|
5
|
-
|
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
|
-
|
16
|
+
## Rationale
|
8
17
|
|
9
|
-
|
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
|
-
|
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/#{
|
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/#{
|
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
|
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
|
-
|
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
|
-
|
19
|
-
|
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
|
data/lib/solder/engine.rb~
CHANGED
@@ -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
|
-
|
6
|
-
|
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
|
-
|
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
|
data/lib/solder/version.rb
CHANGED
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.
|
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:
|
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:
|
56
|
+
name: mocha
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
type: :
|
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: '
|
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: []
|