solder 0.2.0 → 0.3.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: 9d86c83657628b64222a9656e535f19ca6d66a4e675c3e80e7b86d4c2a0f3835
4
- data.tar.gz: 42f8a93bca5208c7c0bab9e1d5cf764f8dfa70564dd2c43952d580df8fee8f77
3
+ metadata.gz: 00c285939bae079a1349a896571e8b315752b813715081d4d29d043a25d0c27a
4
+ data.tar.gz: '092845396d182c4082c4915a12fd9a9087666557c95cca1cae4a6be1e9078420'
5
5
  SHA512:
6
- metadata.gz: cf18a4e8990db861c48cb81585afff0e6d14d3baa98fe2769947c383af10f9aeefe5c73d0787dbf27f994920eef3aeac4910a07bf7386813bc3dde0ae1a857d9
7
- data.tar.gz: 0b08006eda6254c442df937076b51980a01b47becff672f3bca401ba73d73af641f6e1d684a325cff4396c3c58c068bbbeb491e5b109efa5335866ab83040423
6
+ metadata.gz: adb71a8e89b6c6730250dd0ba23b5ad0390822ba6d35afd130fd655a37ab6cecbcd45f1c443ece71d8e199a8fce984d6070d8dd0dcab4e6444041f0089e402bf
7
+ data.tar.gz: ea5f579f47725c9d5f148ca0f1e9dca34e2f98a81b6575595442266b36c3e264577ef96fcff94487dbc3fef0390d7c1c891c8f00b9196023ff796d02cde216dc
@@ -1,5 +1,7 @@
1
1
  module Solder
2
2
  class UiStateController < ApplicationController
3
+ include ActionView::Helpers::SanitizeHelper
4
+
3
5
  before_action :set_ui_state, only: :show
4
6
  around_action Solder.config[:around_action]
5
7
 
@@ -29,7 +31,7 @@ module Solder
29
31
  end
30
32
 
31
33
  def parsed_attributes
32
- JSON.parse(ui_state_params[:attributes])
34
+ JSON.parse(ui_state_params[:attributes]).deep_transform_values { sanitize(_1) }
33
35
  end
34
36
  end
35
37
  end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies the Solder configuration template to config/initializers
3
+
4
+ Example:
5
+ bin/rails generate solder:initializer
6
+
7
+ This will create:
8
+ config/initializers/solder.rb
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate initializer Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class Solder::InitializerGenerator < Rails::Generators::Base
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ def copy_controller_template
5
+ template "solder.rb", Rails.root.join("config/initializers/solder.rb")
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Solder::InitializerGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("templates", __dir__)
3
+ end
@@ -0,0 +1,10 @@
1
+ Solder.configure do |config|
2
+ # Specify a global around action for Solder's UIStateController
3
+ # This is useful for any assumptions your app makes, e.g. instance
4
+ # variables being present, headers being checked, or multitenancy
5
+ # (see example below)
6
+ #
7
+ # config[:around_action] = ->(_controller, action) do
8
+ # ActsAsTenant.without_tenant { action.call }
9
+ # end
10
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies the solder stimulus controller to app/javascript/controllers
3
+
4
+ Example:
5
+ bin/rails generate solder:stimulus
6
+
7
+ This will create:
8
+ app/javascript/controllers/solder_controller.js
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate stimulus Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class Solder::StimulusGenerator < Rails::Generators::Base
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ def copy_controller_template
5
+ template "solder_controller.js.tt", Rails.root.join("app/javascript/controllers/solder_controller.js")
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Solder::StimulusGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("templates", __dir__)
3
+ end
@@ -0,0 +1,41 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { get, patch } from '@rails/request.js'
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ key: String
7
+ }
8
+
9
+ async connect () {
10
+ this.mutationObserver = new MutationObserver(this.mutateState.bind(this))
11
+
12
+ this.mutationObserver.observe(this.element, { attributes: true })
13
+ }
14
+
15
+ disconnect () {
16
+ this.mutationObserver?.disconnect()
17
+ }
18
+
19
+ mutateState (mutationList, observer) {
20
+ mutationList
21
+ .filter(mutation => mutation.attributeName !== 'data-solder-touch')
22
+ .forEach(async mutation => {
23
+ const body = new FormData()
24
+
25
+ const attributes = {}
26
+ this.element
27
+ .getAttributeNames()
28
+ .filter(name => name !== 'data-controller')
29
+ .map(name => {
30
+ attributes[name] = this.element.getAttribute(name)
31
+ })
32
+
33
+ body.append('key', this.keyValue)
34
+ body.append('attributes', JSON.stringify(attributes))
35
+
36
+ await patch('/solder/ui_state/update', {
37
+ body
38
+ })
39
+ })
40
+ }
41
+ }
@@ -0,0 +1,36 @@
1
+ # install @rails/request.js
2
+ if Rails.root.join("config/importmap.rb").exist?
3
+ say "Pin @rails/request.js"
4
+ append_to_file "config/importmap.rb", %(pin "@rails/request.js", preload: true\n)
5
+ else
6
+ say "Install @rails/request.js"
7
+ run "yarn add @rails/request.js"
8
+ end
9
+
10
+ gemfile = Rails.root.join("Gemfile").read
11
+
12
+ # install stimulus-rails, if not already present
13
+ if !gemfile.include? "stimulus-rails"
14
+ gem "stimulus-rails"
15
+ rails_command "stimulus:install"
16
+ say "✅ stimulus-rails has been installed"
17
+ else
18
+ say "⏩ stimulus-rails is already installed. Skipping."
19
+ end
20
+
21
+ # copy stimulus controller template
22
+ generate "solder:stimulus"
23
+
24
+ # turn on development caching
25
+ if Rails.root.join("tmp", "caching-dev.txt").exist?
26
+ say "⏩ Already caching in development. Skipping."
27
+ else
28
+ system "rails dev:cache"
29
+ say "✅ Enabled caching in development"
30
+ end
31
+
32
+ # mount engine
33
+ route 'mount Solder::Engine, at: "/solder"'
34
+
35
+ # copy initializer template
36
+ generate "solder:initializer" if yes?("Do you want to install the solder initializer template?")
@@ -0,0 +1,17 @@
1
+ if Rails.root.join("config/importmap.rb").exist?
2
+ say "Pin @rails/request.js"
3
+ append_to_file "config/importmap.rb", %(pin "@rails/request.js", preload: true\n)
4
+ else
5
+ say "Install @rails/request.js"
6
+ run "yarn add @rails/request.js"
7
+ end
8
+
9
+ # if stimulus isn't present
10
+ # bundle add stimulus-rails
11
+ # bin/rails stimulus:install
12
+ gemfile = Rails.root.join("Gemfile").read
13
+ binding.irb
14
+
15
+ say "Installing Stimulus"
16
+ gem "stimulus-rails"
17
+ rails_command "stimulus:install"
@@ -0,0 +1,41 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { get, patch } from '@rails/request.js'
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ key: String
7
+ }
8
+
9
+ async connect () {
10
+ this.mutationObserver = new MutationObserver(this.mutateState.bind(this))
11
+
12
+ this.mutationObserver.observe(this.element, { attributes: true })
13
+ }
14
+
15
+ disconnect () {
16
+ this.mutationObserver?.disconnect()
17
+ }
18
+
19
+ mutateState (mutationList, observer) {
20
+ mutationList
21
+ .filter(mutation => mutation.attributeName !== 'data-solder-touch')
22
+ .forEach(async mutation => {
23
+ const body = new FormData()
24
+
25
+ const attributes = {}
26
+ this.element
27
+ .getAttributeNames()
28
+ .filter(name => name !== 'data-controller')
29
+ .map(name => {
30
+ attributes[name] = this.element.getAttribute(name)
31
+ })
32
+
33
+ body.append('key', this.keyValue)
34
+ body.append('attributes', JSON.stringify(attributes))
35
+
36
+ await patch('/solder/ui_state/update', {
37
+ body
38
+ })
39
+ })
40
+ }
41
+ }
data/lib/solder/engine.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  module Solder
2
- def self.config
3
- Rails.application.config.solder
2
+ class << self
3
+ def config
4
+ Rails.application.config.solder
5
+ end
6
+
7
+ def configure
8
+ yield config
9
+ end
4
10
  end
5
11
 
6
12
  class Engine < ::Rails::Engine
@@ -10,6 +16,9 @@ module Solder
10
16
  config.solder[:around_action] = ->(_controller, action) { action.call }
11
17
 
12
18
  initializer "solder.check_caching" do |app|
19
+ next if called_by_installer?
20
+ next if called_by_generator?
21
+
13
22
  unless app.config.action_controller.perform_caching && app.config.cache_store != :null_store
14
23
  puts <<~WARN
15
24
  🧑‍🏭 Solder uses the Rails cache store to provide UI state persistence. Therefore, please make sure caching is enabled in your environment.
@@ -28,5 +37,17 @@ module Solder
28
37
  helper Solder::Engine.helpers
29
38
  end
30
39
  end
40
+
41
+ private
42
+
43
+ def called_by_installer?
44
+ Rake.application.top_level_tasks.include? "app:template"
45
+ rescue
46
+ false
47
+ end
48
+
49
+ def called_by_generator?
50
+ ARGV.any? { _1.include? "solder:" }
51
+ end
31
52
  end
32
53
  end
@@ -22,8 +22,11 @@ module Solder
22
22
  end
23
23
  end
24
24
 
25
- config.after_initialize do
26
- ::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
27
30
  end
28
31
  end
29
32
  end
@@ -1,3 +1,3 @@
1
1
  module Solder
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Solder
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,4 +1,6 @@
1
- # desc "Explaining what the task does"
2
- # task :solder do
3
- # # Task goes here
4
- # end
1
+ namespace :solder do
2
+ desc "Install Solder into the app"
3
+ task :install do
4
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install.rb", __dir__)}"
5
+ end
6
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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: 2023-01-16 00:00:00.000000000 Z
11
+ date: 2023-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 7.0.4
41
- - !ruby/object:Gem::Dependency
42
- name: stimulus-rails
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '1.1'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '1.1'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: mocha
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -121,15 +107,26 @@ files:
121
107
  - app/assets/stylesheets/solder/application.css
122
108
  - app/controllers/solder/application_controller.rb
123
109
  - app/controllers/solder/ui_state_controller.rb
124
- - app/controllers/solder/ui_state_controller.rb~
125
110
  - app/helpers/solder/application_helper.rb
126
- - app/helpers/solder/application_helper.rb~
127
111
  - app/jobs/solder/application_job.rb
128
112
  - app/mailers/solder/application_mailer.rb
129
113
  - app/models/solder/application_record.rb
130
114
  - app/views/layouts/solder/application.html.erb
131
115
  - config/routes.rb
132
- - config/routes.rb~
116
+ - lib/generators/solder/initializer/USAGE
117
+ - lib/generators/solder/initializer/USAGE~
118
+ - lib/generators/solder/initializer/initializer_generator.rb
119
+ - lib/generators/solder/initializer/initializer_generator.rb~
120
+ - lib/generators/solder/initializer/templates/solder.rb
121
+ - lib/generators/solder/initializer/templates/solder.rb~
122
+ - lib/generators/solder/stimulus/USAGE
123
+ - lib/generators/solder/stimulus/USAGE~
124
+ - lib/generators/solder/stimulus/stimulus_generator.rb
125
+ - lib/generators/solder/stimulus/stimulus_generator.rb~
126
+ - lib/generators/solder/stimulus/templates/solder_controller.js.tt
127
+ - lib/install/install.rb
128
+ - lib/install/install.rb~
129
+ - lib/install/templates/solder_controller.js.tt
133
130
  - lib/solder.rb
134
131
  - lib/solder/engine.rb
135
132
  - lib/solder/engine.rb~
@@ -156,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
153
  - !ruby/object:Gem::Version
157
154
  version: '0'
158
155
  requirements: []
159
- rubygems_version: 3.3.26
156
+ rubygems_version: 3.3.22
160
157
  signing_key:
161
158
  specification_version: 4
162
159
  summary: Simplistic UI State Management for Rails Apps using Hotwire and Caching
@@ -1,25 +0,0 @@
1
- module Solder
2
- class UiStateController < ApplicationController
3
- before_action :set_ui_state
4
-
5
- def show
6
- render json: @ui_state.value
7
- end
8
-
9
- def update
10
- @ui_state.value = JSON.parse(params[:attributes])
11
-
12
- head :ok
13
- end
14
-
15
- private
16
-
17
- def ui_state_params
18
- params.permit(:attributes, :key)
19
- end
20
-
21
- def set_ui_state
22
- @ui_state = Kredis.json params[:key]
23
- end
24
- end
25
- end
@@ -1,37 +0,0 @@
1
- module Solder
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
-
25
- def solder_key(name)
26
- key = cache_fragment_name(name, skip_digest: true)
27
- .flatten
28
- .compact
29
- .map(&:cache_key)
30
- .join(":")
31
-
32
- key += ":#{caller.find { _1 =~ /html/ }}"
33
-
34
- ActiveSupport::Digest.hexdigest(key)
35
- end
36
- end
37
- end
data/config/routes.rb~ DELETED
@@ -1,4 +0,0 @@
1
- Solder::Engine.routes.draw do
2
- get 'ui_state/update'
3
- get 'ui_state/show'
4
- end