tag_options 0.9.3 → 1.0.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: fb408340ce9d19af1ea31d33d3589bf341cead0595fae98905f183370e27fe5e
4
- data.tar.gz: 40f0b893d5a513bf5d4a15a66e984ba2d331cfe674832b408b28a09f3f2da073
3
+ metadata.gz: 900cd0b42ae1f34b69b7cf227cb4f6bc91cb560f3b04424cdf3e2fd41e5d3f61
4
+ data.tar.gz: b4af6738e8d551d795bab1ef057bb47f49de8d73ba32068c839d04bf50e78a43
5
5
  SHA512:
6
- metadata.gz: 88bee13cbc9235499350735d61299f9aa0f06db4227200f07c93708672133b7e7bd378871376fa0c221405aeb8487df85b60b1aede65356ef48383a31ab75529
7
- data.tar.gz: f883c852b8704e940e59c43f067aaac197ba1bf6857977b1676a76e5d1b155cbe7aa13e300a4c55dad2bac466b3468cbc787a68869d777398306e7bd9f7cf3bf
6
+ metadata.gz: 3322cbce916927b777b7e915e181102cdbc7497c42095edc0e11f009d9a6a3884e8649ce6826d0199ba0ac3e9fe1a8c039bc5bd8c490a6db027714f08e6d88c4
7
+ data.tar.gz: 0d1ebc95b92d489d19c49b33acead9abe88a9fc5932865941454100e11bb877db6baacf7380a85f0495c3d47d3807d02e6d8bbfe08d654dbfb6b17a2940c8393
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.0.0] - 2022-06-14
6
+
7
+ - Rewrote and simplified TagOptions::Hash and supporting classes.
8
+ - BREAKING CHANGES, read documentation for updated usage before updating
9
+
5
10
  ## [0.9.3] - 2021-11-11
6
11
 
7
12
  - Added TagOptions::Hash() ease-of-use method
data/README.md CHANGED
@@ -1,17 +1,20 @@
1
1
  # Tag Options
2
2
 
3
- Simple library for manipulating options passed to the Rails `tag`, `content_tag`, and other tag helpers.
3
+ Simple library for manipulating options passed to the Rails `tag`,
4
+ `content_tag`, and other tag helpers.
4
5
 
5
- This library provides a simple class to make authoring resuable helpers and [View Components](https://viewcomponent.org)
6
- easier when you want to allow for the input of properties on HTML elements, but also need to add/set your own.
6
+ This library provides a simple class to make authoring resuable helpers and
7
+ [View Components](https://viewcomponent.org) easier when you want to allow for
8
+ the input of properties on HTML elements, but also need to add/set your own.
7
9
 
8
- `TagOptions::Hash` is an object that normalizes the options passed to Rails helper, while providing helpful methods
9
- to manipulate the values of HTML properties:
10
+ `TagOptions::Hash` is an object that normalizes the options passed to Rails
11
+ helper, while providing helpful methods to manipulate the values of HTML
12
+ properties:
10
13
 
11
14
  ```ruby
12
15
  def external_link_to(name, url, options={})
13
16
  options = TagOptions::Hash.new(options)
14
- options.combine_with_class!('external-link')
17
+ options.at(:class).combine!("external-link")
15
18
  link_to(name, url, options)
16
19
  end
17
20
  ```
@@ -19,7 +22,7 @@ end
19
22
  Called with:
20
23
 
21
24
  ```ruby
22
- external_link_to('Example', 'https://example.com', class: 'ml-2')
25
+ external_link_to("Example", "https://example.com", class: "ml-2")
23
26
  ```
24
27
 
25
28
  Would render:
@@ -31,12 +34,11 @@ Would render:
31
34
  ## Table of Contents
32
35
 
33
36
  - [Installation](#installation)
34
- - [Configuration](#configuration)
35
37
  - [General Usage](#general-usage)
36
- - [combine_with!](#combinewith)
37
- - [override!](#override)
38
+ - [combine!](#combine)
39
+ - [set!](#set)
38
40
  - [Conditional Usage](#conditional-usage)
39
- - [Custom Property Handling](#custom-property-handling)
41
+ - [Property Resolvers](#property-resolvers)
40
42
  - [Development](#development)
41
43
  - [Contributing](#contributing)
42
44
  - [To Do](#to-do)
@@ -47,7 +49,7 @@ Would render:
47
49
  Add this line to your application's Gemfile:
48
50
 
49
51
  ```ruby
50
- gem 'tag_options'
52
+ gem "tag_options"
51
53
  ```
52
54
 
53
55
  And then execute:
@@ -56,33 +58,6 @@ And then execute:
56
58
  bundle install
57
59
  ```
58
60
 
59
- ## Configuration
60
-
61
- Generate an initializer to customize the default configuration:
62
-
63
- ```sh
64
- rails generate arc_options:install
65
- ```
66
-
67
- ```ruby
68
- TagOptions.configure do |config|
69
- # fallback_property_handler
70
- #
71
- # Defines the default behavior of how values are treated on HTML properties. `TagOptions::PropertyHandler::Generic`
72
- # allows for multiple, unique, values seperated by spaces.
73
- config.fallback_property_handler = 'TagOptions::PropertyHandler::Generic'
74
-
75
- # property_handlers
76
- #
77
- # Allows of the custom handling of HTML properties that match the defined property handler. Properties are handled by
78
- # the first matching property handler.
79
- config.property_handlers = [
80
- 'TagOptions::PropertyHandler::Singular',
81
- 'TagOptions::PropertyHandler::Style'
82
- ]
83
- end
84
- ```
85
-
86
61
  ## General Usage
87
62
 
88
63
  Initialize a `TagOptions::Hash` directly or by passing an existing `Hash`.
@@ -91,155 +66,137 @@ Initialize a `TagOptions::Hash` directly or by passing an existing `Hash`.
91
66
  TagOptions::Hash.new
92
67
  => {}
93
68
 
94
- hash = {class: 'flex'}
69
+ hash = {class: "flex"}
95
70
  TagOptions::Hash.new(hash)
96
71
  => {:class=>"flex"}
97
72
  ```
98
73
 
99
- ### combine_with!
74
+ ### combine!
100
75
 
101
- Combine HTML attributes with an existing `TagOptions::Hash` using `combine_with!`
76
+ Combine HTML attributes with an existing `TagOptions::Hash` by chaining `at` and
77
+ `combine!`
102
78
 
103
79
  ```ruby
104
- options = TagOptions::Hash.new(class: 'flex')
105
- options.combine_with!(class: 'mt-1')
80
+ options = TagOptions::Hash.new(class: "flex")
81
+ options.at(:class).combine!("mt-1")
106
82
  => {:class=>"flex mt-1"}
107
83
  ```
108
84
 
109
85
  Values can also be specified as arrays.
110
86
 
111
87
  ```ruby
112
- options = TagOptions::Hash.new(class: 'flex')
113
- options.combine_with!(class: ['mt-1', 'mx-2'])
88
+ options = TagOptions::Hash.new(class: "flex")
89
+ options.at(:class).combine!(["mt-1", "mx-2"])
114
90
  => {:class=>"flex mt-1 mx-2"}
115
91
  ```
116
92
 
117
93
  HTML attributes are only added if they don't already exist.
118
94
 
119
95
  ```ruby
120
- options = TagOptions::Hash.new(class: 'flex')
121
- options.combine_with!(class: 'flex flex-col')
96
+ options = TagOptions::Hash.new(class: "flex")
97
+ options.at(:class).combine!("flex flex-col")
122
98
  => {:class=>"flex flex-col"}
123
99
  ```
124
100
 
125
- You can also combine multiple HTML attributes in one operation.
101
+ You can also combine values on nested hashes.
126
102
 
127
103
  ```ruby
128
- options = TagOptions::Hash.new(class: 'flex')
129
- options.combine_with!(class: 'mt-1', 'data-controller': 'dropdown')
130
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
104
+ options = TagOptions::Hash.new(class: "flex", data: {controller: "dropdown"})
105
+ options.at(:data, :controller).combine!("toggle")
106
+ => {:class=>"flex", :data=>{:controller=>"dropdown toggle"}
131
107
  ```
132
108
 
133
- Dash seperated HTML attributes, such as `data-controller` can also be specified as nested hashes.
109
+ If a nested hash doesn't already exist it will be automatically added.
134
110
 
135
111
  ```ruby
136
- options = TagOptions::Hash.new(class: 'flex')
137
- options.combine_with!(class: 'mt-1', data: { controller: 'dropdown' })
138
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
112
+ options = TagOptions::Hash.new(class: "flex")
113
+ options.at(:data, :controller).combine!("dropdown")
114
+ => {:class=>"flex", :data=>{:controller=>"dropdown"}
139
115
  ```
140
116
 
141
- Dash seperated HTML attributes can also be specified using underscores.
142
-
143
- ```ruby
144
- options = TagOptions::Hash.new(class: 'flex')
145
- options.combine_with!(class: 'mt-1', data_controller: 'dropdown')
146
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
147
- ```
117
+ ### set!
148
118
 
149
- For ease of use, you can also combine values into a single HTML attribute using `combine_with_<attribute>!`, where
150
- `<attribute>` is the name of the HTML attribute. Dash seperated HTML attributes should be specified using underscores.
119
+ Chaining `at` and `set!` functions nearly the same as `combine!` with all same
120
+ usage patterns. The major difference is that the set method will override any
121
+ existing values.
151
122
 
152
123
  ```ruby
153
- options = TagOptions::Hash.new(class: 'flex')
154
- options.combine_with_class! 'mt-1'
155
- options.combine_with_data_controller! 'dropdown'
156
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
124
+ options = TagOptions::Hash.new(class: "flex")
125
+ options.at(:class).set!("block")
126
+ => {:class=>"block"}
157
127
  ```
158
128
 
159
- ### override!
160
-
161
- The method `override!` functions nearly the same as `combine_with!` with all same usage patterns, including ease of
162
- use methods, such as `override_class!`. The major difference is that the override methods will not combine specified
163
- values with existing values. Any specified values will override any existing values.
164
-
165
129
  ## Conditional Usage
166
130
 
167
- Both the `combine_with!` and `override!` allow for values to be conditionally added to HTML attributes using an argument
168
- array. Where the values are added unconditionally and key/value pairs have their key added _IF_ the value is true.
131
+ Both the `combine!` and `set!` allow for values to be conditionally added to
132
+ HTML attributes using an argument array. Where the values are added
133
+ unconditionally and key/value pairs have their key added _IF_ the value is true.
169
134
 
170
135
  ```ruby
171
136
  # assuming `centered?` returns `true`
172
- options = TagOptions::Hash.new(class: 'flex')
173
- options.combine_with!(class: ['mt-1', 'mx-auto': centered?, 'mx-2': !centered?])
137
+ options = TagOptions::Hash.new(class: "flex")
138
+ options.at(:class).combine!("mt-1", "mx-auto": centered?, "mx-2": !centered?)
174
139
  => {:class=>"flex mt-1 mx-auto"}
175
140
  ```
176
141
 
177
- The syntax of the `combine_with_<attribute>!` and `override_<attribute>!` ease of use method allow for values and
178
- conditional values to be combined more naturally using a more typical argument pattern.
179
-
180
- ```ruby
181
- # assuming `centered?` returns `true`
182
- options = TagOptions::Hash.new(class: 'flex')
183
- options.combine_with_class!('mt-1', 'mx-auto': centered?, 'mx-2': !centered?)
184
- => {:class=>"flex mt-1 mx-auto"}
185
- ```
142
+ ## Custom Property Resolvers
186
143
 
187
- ## Custom Property Handling
144
+ Chaining `at` to `combine!` or `set!` processes HTML properties similar to
145
+ `class`.
188
146
 
189
- Tag Options ships with several property handlers enabled by default.
147
+ - Multiple are allowed
148
+ - Multiple values are seperated by a space
149
+ - Duplicate values are not added
190
150
 
191
- - `TagOptions::PropertyHandler::Generic` - Processes all HTML properties that don't match another property handler.
192
- Handles HTML properties similar to `class`.
193
- - Multiple unique values are allowed
194
- - Multiple values are seperated by a space
195
- - `TagOptions::PropertyHandler::Singular` - Processes `id`, `role`, and `aria-*` properties.
196
- - Both `combine_with!` and `override!` function the same on these attributes.
197
- - The last specified/resolved value is assigned to the property.
198
- - `TagOptions::PropertyHandler::Style` - Processes the `style` property. Allows for the parsing of HTML style property.
199
- - Multiple values are allowed
200
- - Values must be specified as `property: value;`
201
- - The `combine_with!` method will overwrite an existing style property if it exists, but leave the reamining style
202
- properties untouched.
203
- - The `override!` method will overwrite all existing style properties.
151
+ Tag Options also ships with a special `style` resolver, which can be used by
152
+ pass `as: :style` to `at`.
204
153
 
205
- Tag Options also ships with an optional property handler for sorting Tailwind CSS classes inspired by/based on the
206
- VS Code extension [Headwind](https://github.com/heybourn/headwind). To enable this optional property handler,
207
- [generate a configuration](#configuration) and then add it as a property handler:
154
+ - Multiple values are allowed
155
+ - Values must be specified as `property: value;`
156
+ - Duplicate `property: value;` pairs are not added
157
+ - The `combine!` method will overwrite an existing style property if it exists,
158
+ add properties that don't exist, and leave the remaining properties untouched.
159
+ - The `set!` method will overwrite all existing style properties.
208
160
 
209
161
  ```ruby
210
- require 'tag_options/property_handler/tailwind_css'
162
+ options = TagOptions::Hash.new(style: "display: block; margin-left: 0;")
163
+ options.at(:style, as: :style).combine!("display: flex; margin-right: 0;")
164
+ => {:style=>"display: flex; margin-left: 0; margin-right: 0;"}
165
+ ```
166
+
167
+ A `TagOptions::Resolver` class is available if you wish to implement your own
168
+ custom handlers. For examples on doing so, see the [built-in
169
+ handlers](https://github.com/wamonroe/tag_options/tree/main/lib/tag_options/resolvers).
211
170
 
171
+ To register a custom handler:
172
+
173
+ ```ruby
174
+ # config/initializers/tag_options.rb
212
175
  TagOptions.configure do |config|
213
- config.property_handlers = [
214
- 'TagOptions::PropertyHandler::TailwindCSS',
215
- 'TagOptions::PropertyHandler::Singular',
216
- 'TagOptions::PropertyHandler::Style'
217
- ]
176
+ config.register_resolver :custom, "MyCustomResolver"
218
177
  end
219
178
  ```
220
179
 
221
- A `TagOptions::PropertyHandler::Base` class is available if you wish to implement your own custom handlers. For
222
- examples on doing so, see the
223
- [built-in handlers](https://github.com/wamonroe/tag_options/tree/main/lib/tag_options/property_handler).
224
-
225
180
  ## Development
226
181
 
227
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can
228
- also run:
182
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
183
+ `bin/rspec` to run the tests. You can also run:
229
184
 
230
185
  - `bin/console` for an interactive prompt that will allow you to experiment
231
186
  - `bin/rubocop` to run RuboCop to check the code style and formatting
232
- - `bin/update_tailwindcss` to update the sort order from the latest [Headwind](https://github.com/heybourn/headwind)
233
- configuration.
234
187
 
235
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
236
- version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
237
- push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
188
+ To install this gem onto your local machine, run `bundle exec rake install`. To
189
+ release a new version, update the version number in `version.rb`, and then run
190
+ `bundle exec rake release`, which will create a git tag for the version, push
191
+ git commits and the created tag, and push the `.gem` file to
192
+ [rubygems.org](https://rubygems.org).
238
193
 
239
194
  ## Contributing
240
195
 
241
- Bug reports and pull requests are welcome on GitHub at https://github.com/wamonroe/tag_options.
196
+ Bug reports and pull requests are welcome on GitHub at
197
+ https://github.com/wamonroe/tag_options.
242
198
 
243
199
  ## License
244
200
 
245
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
201
+ The gem is available as open source under the terms of the [MIT
202
+ License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup'
3
+ require "bundler/setup"
4
4
 
5
- require 'bundler/gem_tasks'
6
- require 'rake/testtask'
5
+ require "bundler/gem_tasks"
6
+ require "rake/testtask"
7
7
 
8
8
  Rake::TestTask.new(:test) do |t|
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
11
  t.verbose = false
12
12
  end
13
13
 
@@ -1,4 +1,6 @@
1
- # frozen_string_literal: true
1
+ require "tag_options/resolvers/default"
2
+ require "tag_options/resolvers/style"
3
+ require "tag_options/errors/resolver_error"
2
4
 
3
5
  module TagOptions
4
6
  class << self
@@ -11,24 +13,25 @@ module TagOptions
11
13
  def configure
12
14
  yield(configuration)
13
15
  end
14
-
15
- def reset_configuration
16
- @configuration = TagOptions::Configuration.new
17
- end
18
16
  end
19
17
 
20
18
  class Configuration
21
- attr_writer :fallback_property_handler, :property_handlers
19
+ def initialize
20
+ @resolvers = {
21
+ default: "TagOptions::Resolvers::Default",
22
+ style: "TagOptions::Resolvers::Style"
23
+ }
24
+ end
22
25
 
23
- def fallback_property_handler
24
- @fallback_property_handler ||= 'TagOptions::PropertyHandler::Generic'
26
+ def resolver(name)
27
+ unless (resolver_name = @resolvers[name])
28
+ raise TagOptions::Errors::ResolverError, name
29
+ end
30
+ Object.const_get(resolver_name)
25
31
  end
26
32
 
27
- def property_handlers
28
- @property_handlers ||= [
29
- 'TagOptions::PropertyHandler::Singular',
30
- 'TagOptions::PropertyHandler::Style'
31
- ]
33
+ def register_resolver(name, class_name)
34
+ @resolvers[name] = class_name
32
35
  end
33
36
  end
34
37
  end
@@ -0,0 +1,4 @@
1
+ module TagOptions
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ require "tag_options/error"
2
+
3
+ module TagOptions
4
+ module Errors
5
+ class NotHashError < Error
6
+ def initialize(key, *keys, type:)
7
+ hash_path = [key, *keys].join("=>")
8
+ super("unsupport type `#{type}` is already located at #{hash_path}")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require "tag_options/error"
2
+
3
+ module TagOptions
4
+ module Errors
5
+ class ResolverError < Error
6
+ def initialize(name)
7
+ super("a resolver named `#{name}` has not been registered")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,140 +1,33 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/string'
4
- require 'active_support/core_ext/array'
5
- require 'forwardable'
6
- require 'tag_options/property_handler/resolve_value'
1
+ require "tag_options/hash_at"
2
+ require "tag_options/errors/not_hash_error"
7
3
 
8
4
  module TagOptions
9
- class Hash
10
- extend Forwardable
11
-
12
- def_delegators :@data, :inspect, :to_h, :to_hash, :to_s, :stringify_keys, :<, :<=, :==, :>, :>=
13
-
14
- # Hashes passed into the initializer are automatically flattened, with nested keys seperated by dashes. For example,
15
- # `data: { controller: 'dropdown' }`` becomes `'data-controller': 'dropdown'`.
16
- def initialize(hash={})
17
- @data = {}
18
- flatten_hash(hash).each do |property, value|
19
- self[property] = value
20
- end
21
- end
22
-
23
- # []
24
- # Underscores in a property name is automatically coverted to dashes. Properties can be specified as strings or
25
- # symbols, both return the same value.
26
- def [](property)
27
- @data[normalize_property(property)]
28
- end
29
-
30
- # []=
31
- # Hashes assigned to a property are automatically flatten, with nested keys seperated by dashes. Underscores in a
32
- # property name are automatically converted to dashes. Propertiess can be specified as strings or symbols, both will
33
- # assign the value to the same property.
34
- def []=(property, value)
35
- if value.is_a?(::Hash)
36
- flatten_hash({ property => value }).each do |flat_property, flat_value|
37
- store(flat_property, flat_value)
38
- end
39
- else
40
- store(property, value)
5
+ class Hash < ::Hash
6
+ def initialize(hash = {})
7
+ hash.each do |key, value|
8
+ self[key] = value.is_a?(::Hash) ? TagOptions::Hash.new(value) : value
41
9
  end
42
10
  end
43
11
 
44
- # combine_with!
45
- # Allows you to combine values with multiple HTML attributes in one operation. Passed keys should be the HTML
46
- # property to combine the values with. The values can be specified as single string (e.g. `class: 'flex'`) or as an
47
- # argument array (e.g. `class: ['flex', 'mt-2', 'flex-col': layout_column?]`). Hashes in an argument array have
48
- # their keys combined only their value is true. Nested keys will automatically be flattened and combine with the
49
- # associated property (e.g. `data: { controller: 'dropdown' }` would be combined with `data-controller`).
50
- #
51
- # #combine_with!(
52
- # class: ['flex', 'mt-2', 'flex-col': layout_column?],
53
- # data: {
54
- # controller: ['dropdown', 'navbar': navbar?]
55
- # }
56
- # )
57
- #
58
- # TagOptions::Hash also responses to combine_with_<name>!, where `<name>` is the name of the HTML attribute to
59
- # combine the passed argument array with. If `<name>` is specified with a value containing underscores, the HTML
60
- # attribute is converted to dashes, for example: `combine_with_data_controller!` will result in the argument array
61
- # being combined with existing values in `data-controller`.
62
- def combine_with!(hash={})
63
- flatten_hash(hash).each do |property, args|
64
- store(property, self[property], *args)
65
- end
66
- self
67
- end
68
-
69
- # override!
70
- # Allows you to override values on multiple HTML properties in one operation. Passed keys should be the HTML
71
- # properties to override the values of. The values can be passed as single string (e.g. `class: 'flex'`) or as an
72
- # argument array (e.g. `class: ['flex', 'mt-2', 'flex-col': layout_column?]`). Hashes in an argument array have
73
- # their keys added only if their value is true. Nested keys will automatically be flattened and override the value
74
- # at the associated property (e.g. `data: { controller: 'dropdown' }` would override values at `data-controller`).
75
- #
76
- # #override!(
77
- # class: ['flex', 'mt-2', 'flex-col': layout_column?],
78
- # data: {
79
- # controller: ['dropdown', 'navbar': navbar?]
80
- # }
81
- # )
82
- #
83
- # TagOptions::Hash also responses to override_<name>!, where `<name>` is the name of the HTML attribute to override
84
- # the passed argument array on. If `<name>` is specified with a value containing underscores, the resulting HTML
85
- # attribute is automatically nested, for example `override_data_controller!` will result in the argument array
86
- # overriding the existing values in `data-controller`.
87
- def override!(hash={})
88
- flatten_hash(hash).each do |property, args|
89
- store(property, *args)
90
- end
91
- self
12
+ def at(key, *nested_keys, as: :default)
13
+ TagOptions::HashAt.new(opt_hash: self, keys: [key, *nested_keys], as: as)
92
14
  end
93
15
 
94
- private
95
-
96
- def action_matcher
97
- /\A(?<action>combine_with|override)_(?<property>.*)!\z/
16
+ def dig(*keys)
17
+ keys.size.zero? ? self : super
98
18
  end
99
19
 
100
- def flatten_hash(hash)
101
- hash.each_with_object({}) do |(property, value), result|
102
- if value.is_a?(::Hash)
103
- flatten_hash(value).map do |nested_property, nested_value|
104
- result["#{property}-#{nested_property}".to_sym] = nested_value
105
- end
106
- else
107
- result[property] = value
20
+ def populate!(*keys)
21
+ populated_keys = []
22
+ data = self
23
+ keys.each do |key|
24
+ data[key] ||= TagOptions::Hash.new
25
+ data = data[key]
26
+ unless data.is_a?(TagOptions::Hash)
27
+ raise TagOptions::Errors::NotHashError.new(populated_keys, type: data.class)
108
28
  end
109
29
  end
110
- end
111
-
112
- def method_missing(method_name, *args, &block)
113
- match_data = action_matcher.match(method_name.to_s)
114
- if match_data
115
- public_send("#{match_data['action']}!", { match_data['property'] => args }, &block)
116
- else
117
- super
118
- end
119
- end
120
-
121
- def store(property, *values)
122
- property = normalize_property(property)
123
- conditions = values.extract_options!
124
- value = resolve_value(property, *values, **conditions)
125
- value.empty? ? @data.delete(property) : @data[property] = value
126
- end
127
-
128
- def resolve_value(property, *values, **conditions)
129
- TagOptions::PropertyHandler::ResolveValue.call(property, *values, **conditions)
130
- end
131
-
132
- def normalize_property(property)
133
- property.to_s.downcase.dasherize.to_sym
134
- end
135
-
136
- def respond_to_missing?(method_name, include_private=false)
137
- action_matcher.match?(method_name.to_s) || super
30
+ self
138
31
  end
139
32
  end
140
33
  end
@@ -0,0 +1,30 @@
1
+ require "tag_options/configuration"
2
+
3
+ module TagOptions
4
+ class HashAt
5
+ def initialize(opt_hash:, keys:, as:)
6
+ @opt_hash = opt_hash
7
+ @keys = keys[..-2]
8
+ @value_key = keys[-1]
9
+ @resolver = TagOptions.configuration.resolver(as)
10
+ end
11
+
12
+ def combine!(*values, **conditions)
13
+ @opt_hash.populate!(*@keys)
14
+ current_value = @opt_hash.dig(*@keys, @value_key)
15
+ set_value! @resolver.call(current_value, *values, **conditions)
16
+ end
17
+
18
+ def set!(*values, **conditions)
19
+ @opt_hash.populate!(*@keys)
20
+ set_value! @resolver.call(*values, **conditions)
21
+ end
22
+
23
+ private
24
+
25
+ def set_value!(value)
26
+ @opt_hash.dig(*@keys)[@value_key] = value
27
+ @opt_hash
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module TagOptions
2
+ class Resolver
3
+ def initialize(*values, **conditional_values)
4
+ @values = [*values, *resolve_conditional_values(conditional_values)]
5
+ end
6
+
7
+ def self.call(...)
8
+ new(...).call
9
+ end
10
+
11
+ private
12
+
13
+ def resolve_conditional_values(conditional_values)
14
+ conditional_values.select { |_key, value| value }.keys
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ require "tag_options/resolver"
2
+
3
+ module TagOptions
4
+ module Resolvers
5
+ class Default < Resolver
6
+ def call
7
+ @values.map { |v| v.to_s.split }.flatten.compact.uniq.join(" ")
8
+ end
9
+ end
10
+ end
11
+ end