stimulus_reflex 3.4.0.pre8 → 3.5.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +639 -463
- data/CODE_OF_CONDUCT.md +6 -0
- data/Gemfile.lock +103 -100
- data/LATEST +1 -0
- data/README.md +14 -13
- data/app/channels/stimulus_reflex/channel.rb +62 -67
- data/lib/generators/USAGE +1 -1
- data/lib/generators/stimulus_reflex/{config_generator.rb → initializer_generator.rb} +3 -3
- data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +3 -2
- data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +1 -1
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +14 -0
- data/lib/stimulus_reflex.rb +11 -3
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +7 -4
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +12 -5
- data/lib/stimulus_reflex/cable_ready_channels.rb +30 -6
- data/lib/stimulus_reflex/callbacks.rb +98 -0
- data/lib/stimulus_reflex/concern_enhancer.rb +37 -0
- data/lib/stimulus_reflex/configuration.rb +3 -1
- data/lib/stimulus_reflex/element.rb +31 -7
- data/lib/stimulus_reflex/policies/reflex_invocation_policy.rb +28 -0
- data/lib/stimulus_reflex/reflex.rb +57 -57
- data/lib/stimulus_reflex/reflex_data.rb +79 -0
- data/lib/stimulus_reflex/reflex_factory.rb +31 -0
- data/lib/stimulus_reflex/request_parameters.rb +19 -0
- data/lib/stimulus_reflex/{logger.rb → utils/logger.rb} +6 -8
- data/lib/stimulus_reflex/{sanity_checker.rb → utils/sanity_checker.rb} +65 -10
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/tasks/stimulus_reflex/install.rake +14 -7
- data/test/broadcasters/broadcaster_test.rb +2 -0
- data/test/broadcasters/broadcaster_test_case.rb +3 -1
- data/test/broadcasters/nothing_broadcaster_test.rb +7 -3
- data/test/broadcasters/page_broadcaster_test.rb +10 -4
- data/test/broadcasters/selector_broadcaster_test.rb +173 -55
- data/test/callbacks_test.rb +652 -0
- data/test/concern_enhancer_test.rb +54 -0
- data/test/element_test.rb +181 -0
- data/test/reflex_test.rb +7 -1
- data/test/test_helper.rb +10 -0
- data/test/tmp/app/reflexes/application_reflex.rb +1 -1
- data/test/tmp/app/reflexes/demo_reflex.rb +3 -2
- metadata +45 -36
- data/package.json +0 -57
- data/stimulus_reflex.gemspec +0 -40
- data/tags +0 -139
- data/yarn.lock +0 -4711
data/lib/generators/USAGE
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
require "rails/generators"
|
4
4
|
|
5
5
|
module StimulusReflex
|
6
|
-
class
|
7
|
-
desc "Creates
|
6
|
+
class InitializerGenerator < Rails::Generators::Base
|
7
|
+
desc "Creates a StimulusReflex initializer in config/initializers"
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
9
9
|
|
10
|
-
def
|
10
|
+
def copy_initializer_file
|
11
11
|
copy_file "config/initializers/stimulus_reflex.rb"
|
12
12
|
end
|
13
13
|
end
|
@@ -17,12 +17,13 @@ class <%= class_name %>Reflex < ApplicationReflex
|
|
17
17
|
# - unsigned - use an unsigned Global ID to map dataset attribute to a model eg. element.unsigned[:foo]
|
18
18
|
# - cable_ready - a special cable_ready that can broadcast to the current visitor (no brackets needed)
|
19
19
|
# - reflex_id - a UUIDv4 that uniquely identies each Reflex
|
20
|
+
# - tab_id - a UUIDv4 that uniquely identifies the browser tab
|
20
21
|
#
|
21
22
|
# Example:
|
22
23
|
#
|
23
24
|
# before_reflex do
|
24
25
|
# # throw :abort # this will prevent the Reflex from continuing
|
25
|
-
# # learn more about callbacks at https://docs.stimulusreflex.com/lifecycle
|
26
|
+
# # learn more about callbacks at https://docs.stimulusreflex.com/rtfm/lifecycle
|
26
27
|
# end
|
27
28
|
#
|
28
29
|
# def example(argument=true)
|
@@ -30,7 +31,7 @@ class <%= class_name %>Reflex < ApplicationReflex
|
|
30
31
|
# # Any declared instance variables will be made available to the Rails controller and view.
|
31
32
|
# end
|
32
33
|
#
|
33
|
-
# Learn more at: https://docs.stimulusreflex.com/
|
34
|
+
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
|
34
35
|
|
35
36
|
<% actions.each do |action| -%>
|
36
37
|
def <%= action %>
|
@@ -8,5 +8,5 @@ class ApplicationReflex < StimulusReflex::Reflex
|
|
8
8
|
# # If your ActionCable connection is: `identified_by :current_user`
|
9
9
|
# delegate :current_user, to: :connection
|
10
10
|
#
|
11
|
-
# Learn more at: https://docs.stimulusreflex.com/
|
11
|
+
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
|
12
12
|
end
|
@@ -6,6 +6,11 @@ StimulusReflex.configure do |config|
|
|
6
6
|
|
7
7
|
# config.on_failed_sanity_checks = :exit
|
8
8
|
|
9
|
+
# Enable/disable exiting / warning when there's a new StimulusReflex release
|
10
|
+
# `:exit` or `:warn` or `:ignore`
|
11
|
+
|
12
|
+
# config.on_new_version_available = :ignore
|
13
|
+
|
9
14
|
# Override the parent class that the StimulusReflex ActionCable channel inherits from
|
10
15
|
|
11
16
|
# config.parent_channel = "ApplicationCable::Channel"
|
@@ -15,6 +20,15 @@ StimulusReflex.configure do |config|
|
|
15
20
|
# Available colors: red, green, yellow, blue, magenta, cyan, white
|
16
21
|
# You can also use attributes from your ActionCable Connection's identifiers that resolve to valid ActiveRecord models
|
17
22
|
# eg. if your connection is `identified_by :current_user` and your User model has an email attribute, you can access r.email (it will display `-` if the user isn't logged in)
|
23
|
+
# Learn more at: https://docs.stimulusreflex.com/appendices/troubleshooting#stimulusreflex-logging
|
18
24
|
|
19
25
|
# config.logging = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
26
|
+
|
27
|
+
# Optimized for speed, StimulusReflex doesn't enable Rack middleware by default.
|
28
|
+
# If you are using Page Morphs and your app uses Rack middleware to rewrite part of the request path, you must enable those middleware modules in StimulusReflex.
|
29
|
+
#
|
30
|
+
# Learn more about registering Rack middleware in Rails here: https://guides.rubyonrails.org/rails_on_rack.html#configuring-middleware-stack
|
31
|
+
|
32
|
+
# config.middleware.use FirstRackMiddleware
|
33
|
+
# config.middleware.use SecondRackMiddleware
|
20
34
|
end
|
data/lib/stimulus_reflex.rb
CHANGED
@@ -1,30 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "uri"
|
4
|
+
require "open-uri"
|
4
5
|
require "rack"
|
5
6
|
require "rails/engine"
|
6
7
|
require "active_support/all"
|
7
8
|
require "action_dispatch"
|
8
9
|
require "action_cable"
|
10
|
+
require "action_view"
|
9
11
|
require "nokogiri"
|
10
12
|
require "cable_ready"
|
11
13
|
require "stimulus_reflex/version"
|
12
14
|
require "stimulus_reflex/cable_ready_channels"
|
15
|
+
require "stimulus_reflex/concern_enhancer"
|
13
16
|
require "stimulus_reflex/configuration"
|
17
|
+
require "stimulus_reflex/callbacks"
|
18
|
+
require "stimulus_reflex/request_parameters"
|
14
19
|
require "stimulus_reflex/reflex"
|
20
|
+
require "stimulus_reflex/reflex_data"
|
21
|
+
require "stimulus_reflex/reflex_factory"
|
15
22
|
require "stimulus_reflex/element"
|
16
|
-
require "stimulus_reflex/sanity_checker"
|
17
23
|
require "stimulus_reflex/broadcasters/broadcaster"
|
18
24
|
require "stimulus_reflex/broadcasters/nothing_broadcaster"
|
19
25
|
require "stimulus_reflex/broadcasters/page_broadcaster"
|
20
26
|
require "stimulus_reflex/broadcasters/selector_broadcaster"
|
27
|
+
require "stimulus_reflex/policies/reflex_invocation_policy"
|
21
28
|
require "stimulus_reflex/utils/colorize"
|
22
|
-
require "stimulus_reflex/logger"
|
29
|
+
require "stimulus_reflex/utils/logger"
|
30
|
+
require "stimulus_reflex/utils/sanity_checker"
|
23
31
|
|
24
32
|
module StimulusReflex
|
25
33
|
class Engine < Rails::Engine
|
26
34
|
initializer "stimulus_reflex.sanity_check" do
|
27
|
-
SanityChecker.check!
|
35
|
+
SanityChecker.check! unless Rails.env.production?
|
28
36
|
end
|
29
37
|
end
|
30
38
|
end
|
@@ -3,7 +3,10 @@
|
|
3
3
|
module StimulusReflex
|
4
4
|
class Broadcaster
|
5
5
|
attr_reader :reflex, :logger, :operations
|
6
|
-
delegate :cable_ready, :permanent_attribute_name, to: :reflex
|
6
|
+
delegate :cable_ready, :permanent_attribute_name, :payload, to: :reflex
|
7
|
+
|
8
|
+
DEFAULT_HTML_WITHOUT_FORMAT = Nokogiri::XML::Node::SaveOptions::DEFAULT_HTML &
|
9
|
+
~Nokogiri::XML::Node::SaveOptions::FORMAT
|
7
10
|
|
8
11
|
def initialize(reflex)
|
9
12
|
@reflex = reflex
|
@@ -23,13 +26,13 @@ module StimulusReflex
|
|
23
26
|
false
|
24
27
|
end
|
25
28
|
|
26
|
-
def broadcast_message(subject:,
|
27
|
-
logger.error "\e[31m#{body}\e[0m" if subject == "error"
|
29
|
+
def broadcast_message(subject:, data: {}, error: nil)
|
28
30
|
operations << ["document", :dispatch_event]
|
29
31
|
cable_ready.dispatch_event(
|
30
32
|
name: "stimulus-reflex:server-message",
|
31
33
|
detail: {
|
32
|
-
reflexId: data
|
34
|
+
reflexId: data.delete("reflexId"),
|
35
|
+
payload: payload,
|
33
36
|
stimulus_reflex: data.merge(
|
34
37
|
morph: to_sym,
|
35
38
|
server_message: {subject: subject, body: error&.to_s}
|
@@ -12,10 +12,11 @@ module StimulusReflex
|
|
12
12
|
selectors = selectors.select { |s| document.css(s).present? }
|
13
13
|
selectors.each do |selector|
|
14
14
|
operations << [selector, :morph]
|
15
|
-
html = document.css(selector).inner_html
|
15
|
+
html = document.css(selector).inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT)
|
16
16
|
cable_ready.morph(
|
17
17
|
selector: selector,
|
18
18
|
html: html,
|
19
|
+
payload: payload,
|
19
20
|
children_only: true,
|
20
21
|
permanent_attribute_name: permanent_attribute_name,
|
21
22
|
stimulus_reflex: data.merge({
|
@@ -23,7 +24,6 @@ module StimulusReflex
|
|
23
24
|
})
|
24
25
|
)
|
25
26
|
end
|
26
|
-
|
27
27
|
cable_ready.broadcast
|
28
28
|
end
|
29
29
|
|
@@ -2,19 +2,25 @@
|
|
2
2
|
|
3
3
|
module StimulusReflex
|
4
4
|
class SelectorBroadcaster < Broadcaster
|
5
|
+
include CableReady::Identifiable
|
6
|
+
|
5
7
|
def broadcast(_, data = {})
|
6
8
|
morphs.each do |morph|
|
7
9
|
selectors, html = morph
|
8
|
-
updates = selectors.is_a?(Hash) ? selectors :
|
9
|
-
updates.each do |
|
10
|
-
html =
|
11
|
-
|
10
|
+
updates = selectors.is_a?(Hash) ? selectors : {selectors => html}
|
11
|
+
updates.each do |key, value|
|
12
|
+
html = reflex.render(key) if key.is_a?(ActiveRecord::Base) && value.nil?
|
13
|
+
html = reflex.render_collection(key) if key.is_a?(ActiveRecord::Relation) && value.nil?
|
14
|
+
html ||= value
|
15
|
+
fragment = Nokogiri::HTML.fragment(html.to_s)
|
16
|
+
selector = key.is_a?(ActiveRecord::Base) || key.is_a?(ActiveRecord::Relation) ? dom_id(key) : key.to_s
|
12
17
|
match = fragment.at_css(selector)
|
13
18
|
if match.present?
|
14
19
|
operations << [selector, :morph]
|
15
20
|
cable_ready.morph(
|
16
21
|
selector: selector,
|
17
|
-
html: match.inner_html,
|
22
|
+
html: match.inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT),
|
23
|
+
payload: payload,
|
18
24
|
children_only: true,
|
19
25
|
permanent_attribute_name: permanent_attribute_name,
|
20
26
|
stimulus_reflex: data.merge({
|
@@ -26,6 +32,7 @@ module StimulusReflex
|
|
26
32
|
cable_ready.inner_html(
|
27
33
|
selector: selector,
|
28
34
|
html: fragment.to_html,
|
35
|
+
payload: payload,
|
29
36
|
stimulus_reflex: data.merge({
|
30
37
|
morph: to_sym
|
31
38
|
})
|
@@ -1,12 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StimulusReflex
|
2
4
|
class CableReadyChannels
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
delegate :[], to: "cable_ready_channels"
|
6
|
+
|
7
|
+
def initialize(stream_name, reflex_id)
|
8
|
+
@stream_name = stream_name
|
9
|
+
@reflex_id = reflex_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def cable_ready_channels
|
13
|
+
CableReady::Channels.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def stimulus_reflex_channel
|
17
|
+
CableReady::Channels.instance[@stream_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, *args)
|
21
|
+
if stimulus_reflex_channel.respond_to?(name)
|
22
|
+
if (options = args.find_index { |a| a.is_a? Hash })
|
23
|
+
args[options][:reflex_id] = @reflex_id
|
24
|
+
elsif args.any?
|
25
|
+
args << {reflex_id: @reflex_id}
|
26
|
+
end
|
27
|
+
return stimulus_reflex_channel.public_send(name, *args)
|
28
|
+
end
|
29
|
+
super
|
30
|
+
end
|
6
31
|
|
7
|
-
def
|
8
|
-
|
9
|
-
@stimulus_reflex_channel = @cable_ready_channels[stream_name]
|
32
|
+
def respond_to_missing?(name, include_all)
|
33
|
+
stimulus_reflex_channel.respond_to?(name) || super
|
10
34
|
end
|
11
35
|
end
|
12
36
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module StimulusReflex
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
define_callbacks :process, skip_after_callbacks_if_terminated: true
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def before_reflex(*args, &block)
|
14
|
+
add_callback(:before, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_reflex(*args, &block)
|
18
|
+
add_callback(:after, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def around_reflex(*args, &block)
|
22
|
+
add_callback(:around, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepend_before_reflex(*args, &block)
|
26
|
+
prepend_callback(:before, *args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend_after_reflex(*args, &block)
|
30
|
+
prepend_callback(:after, *args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepend_around_reflex(*args, &block)
|
34
|
+
prepend_callback(:around, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def skip_before_reflex(*args, &block)
|
38
|
+
omit_callback(:before, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def skip_after_reflex(*args, &block)
|
42
|
+
omit_callback(:after, *args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def skip_around_reflex(*args, &block)
|
46
|
+
omit_callback(:around, *args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :append_before_reflex, :before_reflex
|
50
|
+
alias_method :append_around_reflex, :around_reflex
|
51
|
+
alias_method :append_after_reflex, :after_reflex
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def add_callback(kind, *args, &block)
|
56
|
+
insert_callbacks(args, block) do |name, options|
|
57
|
+
set_callback(:process, kind, name, options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepend_callback(kind, *args, &block)
|
62
|
+
insert_callbacks(args, block) do |name, options|
|
63
|
+
set_callback(:process, kind, name, options.merge(prepend: true))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def omit_callback(kind, *args, &block)
|
68
|
+
insert_callbacks(args) do |name, options|
|
69
|
+
skip_callback(:process, kind, name, options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def insert_callbacks(callbacks, block = nil)
|
74
|
+
options = callbacks.extract_options!
|
75
|
+
normalize_callback_options!(options)
|
76
|
+
|
77
|
+
callbacks.push(block) if block
|
78
|
+
|
79
|
+
callbacks.each do |callback|
|
80
|
+
yield callback, options
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def normalize_callback_options!(options)
|
85
|
+
normalize_callback_option! options, :only, :if
|
86
|
+
normalize_callback_option! options, :except, :unless
|
87
|
+
end
|
88
|
+
|
89
|
+
def normalize_callback_option!(options, from, to)
|
90
|
+
if (from = options.delete(from))
|
91
|
+
from_set = Array(from).map(&:to_s).to_set
|
92
|
+
from = proc { |reflex| from_set.include? reflex.method_name.to_s }
|
93
|
+
options[to] = Array(options[to]).unshift(from)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StimulusReflex
|
2
|
+
module ConcernEnhancer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def method_missing(name, *args)
|
7
|
+
case ancestors
|
8
|
+
when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
|
9
|
+
if (ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include? name
|
10
|
+
nil
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
|
15
|
+
if StimulusReflex::Reflex.public_methods.include? name
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(name, include_all = false)
|
26
|
+
case ancestors
|
27
|
+
when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
|
28
|
+
(ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include?(name) || super
|
29
|
+
when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
|
30
|
+
StimulusReflex::Reflex.public_methods.include?(name) || super
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -14,14 +14,16 @@ module StimulusReflex
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class Configuration
|
17
|
-
attr_accessor :on_failed_sanity_checks, :parent_channel, :logging
|
17
|
+
attr_accessor :on_failed_sanity_checks, :on_new_version_available, :parent_channel, :logging, :middleware
|
18
18
|
|
19
19
|
DEFAULT_LOGGING = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
20
20
|
|
21
21
|
def initialize
|
22
22
|
@on_failed_sanity_checks = :exit
|
23
|
+
@on_new_version_available = :ignore
|
23
24
|
@parent_channel = "ApplicationCable::Channel"
|
24
25
|
@logging = DEFAULT_LOGGING
|
26
|
+
@middleware = ActionDispatch::MiddlewareStack.new
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class StimulusReflex::Element < OpenStruct
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :attrs, :data_attrs
|
5
5
|
|
6
6
|
def initialize(data = {})
|
7
|
-
@
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
@attrs = HashWithIndifferentAccess.new(data["attrs"] || {})
|
8
|
+
datasets = data["dataset"] || {}
|
9
|
+
regular_dataset = datasets["dataset"] || {}
|
10
|
+
@data_attrs = build_data_attrs(regular_dataset, datasets["datasetAll"] || {})
|
11
|
+
all_attributes = @attrs.merge(@data_attrs)
|
12
|
+
super build_underscored(all_attributes)
|
13
|
+
@data_attrs.transform_keys! { |key| key.delete_prefix "data-" }
|
12
14
|
end
|
13
15
|
|
14
16
|
def signed
|
@@ -19,7 +21,29 @@ class StimulusReflex::Element < OpenStruct
|
|
19
21
|
@unsigned ||= ->(accessor) { GlobalID::Locator.locate(dataset[accessor]) }
|
20
22
|
end
|
21
23
|
|
24
|
+
def attributes
|
25
|
+
@attributes ||= OpenStruct.new(build_underscored(attrs))
|
26
|
+
end
|
27
|
+
|
22
28
|
def dataset
|
23
|
-
@dataset ||= OpenStruct.new(
|
29
|
+
@dataset ||= OpenStruct.new(build_underscored(data_attrs))
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :data_attributes, :dataset
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_data_attrs(dataset, dataset_all)
|
37
|
+
dataset_all.transform_keys! { |key| "data-#{key.delete_prefix("data-").pluralize}" }
|
38
|
+
|
39
|
+
dataset.each { |key, value| dataset_all[key]&.prepend(value) }
|
40
|
+
|
41
|
+
data_attrs = dataset.merge(dataset_all)
|
42
|
+
|
43
|
+
HashWithIndifferentAccess.new(data_attrs || {})
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_underscored(attrs)
|
47
|
+
attrs.merge(attrs.transform_keys(&:underscore))
|
24
48
|
end
|
25
49
|
end
|