merge_attributes 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: db93edddbac9b60ab3f40ce8bea720ea7ed7c3e82cc247e5ffe4fe31cc0733b7
4
+ data.tar.gz: a7cb7aac14048353914ee6fd19e4247be3aa184f38c6dd309ac01d05a960d14b
5
+ SHA512:
6
+ metadata.gz: 8db5f31a56ac2409640932fcc5263049ef3f7c33f9d0061dcf64d4721432efa7a6f4810d8c833f2772da508d09545c2b4d801c02d3595aa4dfb024d1829823c0
7
+ data.tar.gz: c32d201132fbfc398958ce0dba2a0424245b895e11803046700ac8285f85f1cfeac23233da62ddb67515af99ef2fbcc8ea4e2b05f9c8fa0e7d2b515bafbf9fbd
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require rails_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.5
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.5
7
+ before_install: gem install bundler -v 1.17.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at developers@amba.co. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in merge_attributes.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,202 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ merge_attributes (0.1.1)
5
+ activesupport (>= 6.1.0)
6
+ rails (>= 6.1.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (6.1.4.1)
12
+ actionpack (= 6.1.4.1)
13
+ activesupport (= 6.1.4.1)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (>= 0.6.1)
16
+ actionmailbox (6.1.4.1)
17
+ actionpack (= 6.1.4.1)
18
+ activejob (= 6.1.4.1)
19
+ activerecord (= 6.1.4.1)
20
+ activestorage (= 6.1.4.1)
21
+ activesupport (= 6.1.4.1)
22
+ mail (>= 2.7.1)
23
+ actionmailer (6.1.4.1)
24
+ actionpack (= 6.1.4.1)
25
+ actionview (= 6.1.4.1)
26
+ activejob (= 6.1.4.1)
27
+ activesupport (= 6.1.4.1)
28
+ mail (~> 2.5, >= 2.5.4)
29
+ rails-dom-testing (~> 2.0)
30
+ actionpack (6.1.4.1)
31
+ actionview (= 6.1.4.1)
32
+ activesupport (= 6.1.4.1)
33
+ rack (~> 2.0, >= 2.0.9)
34
+ rack-test (>= 0.6.3)
35
+ rails-dom-testing (~> 2.0)
36
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
+ actiontext (6.1.4.1)
38
+ actionpack (= 6.1.4.1)
39
+ activerecord (= 6.1.4.1)
40
+ activestorage (= 6.1.4.1)
41
+ activesupport (= 6.1.4.1)
42
+ nokogiri (>= 1.8.5)
43
+ actionview (6.1.4.1)
44
+ activesupport (= 6.1.4.1)
45
+ builder (~> 3.1)
46
+ erubi (~> 1.4)
47
+ rails-dom-testing (~> 2.0)
48
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
+ activejob (6.1.4.1)
50
+ activesupport (= 6.1.4.1)
51
+ globalid (>= 0.3.6)
52
+ activemodel (6.1.4.1)
53
+ activesupport (= 6.1.4.1)
54
+ activerecord (6.1.4.1)
55
+ activemodel (= 6.1.4.1)
56
+ activesupport (= 6.1.4.1)
57
+ activestorage (6.1.4.1)
58
+ actionpack (= 6.1.4.1)
59
+ activejob (= 6.1.4.1)
60
+ activerecord (= 6.1.4.1)
61
+ activesupport (= 6.1.4.1)
62
+ marcel (~> 1.0.0)
63
+ mini_mime (>= 1.1.0)
64
+ activesupport (6.1.4.1)
65
+ concurrent-ruby (~> 1.0, >= 1.0.2)
66
+ i18n (>= 1.6, < 2)
67
+ minitest (>= 5.1)
68
+ tzinfo (~> 2.0)
69
+ zeitwerk (~> 2.3)
70
+ ast (2.4.2)
71
+ builder (3.2.4)
72
+ byebug (11.1.3)
73
+ concurrent-ruby (1.1.9)
74
+ crass (1.0.6)
75
+ diff-lcs (1.4.4)
76
+ erubi (1.10.0)
77
+ globalid (0.5.2)
78
+ activesupport (>= 5.0)
79
+ i18n (1.8.10)
80
+ concurrent-ruby (~> 1.0)
81
+ loofah (2.12.0)
82
+ crass (~> 1.0.2)
83
+ nokogiri (>= 1.5.9)
84
+ mail (2.7.1)
85
+ mini_mime (>= 0.1.1)
86
+ marcel (1.0.2)
87
+ method_source (1.0.0)
88
+ mini_mime (1.1.2)
89
+ mini_portile2 (2.6.1)
90
+ minitest (5.14.4)
91
+ nio4r (2.5.8)
92
+ nokogiri (1.12.5)
93
+ mini_portile2 (~> 2.6.1)
94
+ racc (~> 1.4)
95
+ parallel (1.22.1)
96
+ parser (3.1.2.0)
97
+ ast (~> 2.4.1)
98
+ racc (1.6.0)
99
+ rack (2.2.3)
100
+ rack-test (1.1.0)
101
+ rack (>= 1.0, < 3)
102
+ rails (6.1.4.1)
103
+ actioncable (= 6.1.4.1)
104
+ actionmailbox (= 6.1.4.1)
105
+ actionmailer (= 6.1.4.1)
106
+ actionpack (= 6.1.4.1)
107
+ actiontext (= 6.1.4.1)
108
+ actionview (= 6.1.4.1)
109
+ activejob (= 6.1.4.1)
110
+ activemodel (= 6.1.4.1)
111
+ activerecord (= 6.1.4.1)
112
+ activestorage (= 6.1.4.1)
113
+ activesupport (= 6.1.4.1)
114
+ bundler (>= 1.15.0)
115
+ railties (= 6.1.4.1)
116
+ sprockets-rails (>= 2.0.0)
117
+ rails-dom-testing (2.0.3)
118
+ activesupport (>= 4.2.0)
119
+ nokogiri (>= 1.6)
120
+ rails-html-sanitizer (1.4.2)
121
+ loofah (~> 2.3)
122
+ railties (6.1.4.1)
123
+ actionpack (= 6.1.4.1)
124
+ activesupport (= 6.1.4.1)
125
+ method_source
126
+ rake (>= 0.13)
127
+ thor (~> 1.0)
128
+ rainbow (3.1.1)
129
+ rake (10.5.0)
130
+ regexp_parser (2.5.0)
131
+ rexml (3.2.5)
132
+ rspec (3.10.0)
133
+ rspec-core (~> 3.10.0)
134
+ rspec-expectations (~> 3.10.0)
135
+ rspec-mocks (~> 3.10.0)
136
+ rspec-core (3.10.1)
137
+ rspec-support (~> 3.10.0)
138
+ rspec-expectations (3.10.1)
139
+ diff-lcs (>= 1.2.0, < 2.0)
140
+ rspec-support (~> 3.10.0)
141
+ rspec-mocks (3.10.2)
142
+ diff-lcs (>= 1.2.0, < 2.0)
143
+ rspec-support (~> 3.10.0)
144
+ rspec-rails (4.0.2)
145
+ actionpack (>= 4.2)
146
+ activesupport (>= 4.2)
147
+ railties (>= 4.2)
148
+ rspec-core (~> 3.10)
149
+ rspec-expectations (~> 3.10)
150
+ rspec-mocks (~> 3.10)
151
+ rspec-support (~> 3.10)
152
+ rspec-support (3.10.2)
153
+ rubocop (1.29.1)
154
+ parallel (~> 1.10)
155
+ parser (>= 3.1.0.0)
156
+ rainbow (>= 2.2.2, < 4.0)
157
+ regexp_parser (>= 1.8, < 3.0)
158
+ rexml (>= 3.2.5, < 4.0)
159
+ rubocop-ast (>= 1.17.0, < 2.0)
160
+ ruby-progressbar (~> 1.7)
161
+ unicode-display_width (>= 1.4.0, < 3.0)
162
+ rubocop-ast (1.18.0)
163
+ parser (>= 3.1.1.0)
164
+ rubocop-performance (1.13.3)
165
+ rubocop (>= 1.7.0, < 2.0)
166
+ rubocop-ast (>= 0.4.0)
167
+ ruby-progressbar (1.11.0)
168
+ sprockets (4.0.2)
169
+ concurrent-ruby (~> 1.0)
170
+ rack (> 1, < 3)
171
+ sprockets-rails (3.2.2)
172
+ actionpack (>= 4.0)
173
+ activesupport (>= 4.0)
174
+ sprockets (>= 3.0.0)
175
+ standard (1.12.1)
176
+ rubocop (= 1.29.1)
177
+ rubocop-performance (= 1.13.3)
178
+ standardrb (1.0.1)
179
+ standard
180
+ thor (1.1.0)
181
+ tzinfo (2.0.4)
182
+ concurrent-ruby (~> 1.0)
183
+ unicode-display_width (2.1.0)
184
+ websocket-driver (0.7.5)
185
+ websocket-extensions (>= 0.1.0)
186
+ websocket-extensions (0.1.5)
187
+ zeitwerk (2.5.1)
188
+
189
+ PLATFORMS
190
+ ruby
191
+
192
+ DEPENDENCIES
193
+ bundler (>= 2.0.0)
194
+ byebug
195
+ merge_attributes!
196
+ rake (~> 10.0)
197
+ rspec (~> 3.0)
198
+ rspec-rails (~> 4.0.0)
199
+ standardrb
200
+
201
+ BUNDLED WITH
202
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Amba Health & Care
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # MergeAttributes
2
+
3
+ Merge hashes of HTML attributes, properly aggregating `class` and other [DOMTokenList]-like fields like Stimulus' `data-controller` or `data-action` (or also ARIA's `aria-labelledby` or `aria-describedby` if you so chose).
4
+
5
+ The resulting `Hash` can then be provided to the `tag.<tag name>` helper to generate an HTML tag with the corresponding attributes (or to `content_tag`, or as [HAML attributes] with a [double splat], or whatever needs a hash of attributes)
6
+
7
+ [HAML attributes]: https://haml.info/tutorial.html#adding_attributes
8
+ [double splat]: https://michaeljherold.com/articles/using-double-splat-operator-ruby/
9
+ [DOMTokenList]: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
10
+
11
+ This allows finer control of the provenance on the attributes assigned to a given elements, like splitting its "own" attributes vs. those coming from the parent it's rendered in:
12
+
13
+ ```rb
14
+ merge_attributes(
15
+ # Styles responsible for the component's look itself
16
+ {class: 'my-component'},
17
+ {
18
+ # Styles responsible for adjusting the component
19
+ # because it's rendered inside `parent-component`
20
+ class: 'parent-component__child',
21
+ # Extra action that the element would not usually have
22
+ data: {action: 'stimulus-controller#action'}
23
+ }
24
+ )
25
+ # Creates: {class: 'my-component parent_component__child',data: {action: 'stimulus-controller#action'}}
26
+ ```
27
+
28
+ This also opens the door to abstracting specific sets of components in their own helpers to provide specific vocabulary, say for configuring specific Stimulus controllers
29
+
30
+ ```rb
31
+ merge_attributes(
32
+ {class: 'my-component'},
33
+ # Returns the right Stimulus controller/actions/values
34
+ # to properly wire the element to open the given dialog
35
+ dialog_trigger(dialog_id: 'my-dialog')
36
+ )
37
+ ```
38
+
39
+ ## Installation
40
+
41
+ ### Requirements
42
+
43
+ The helper delegate some of its behaviour to [Rails 6.1 `token_list` helper][token-list-helper].
44
+
45
+ If you're running on an older version of Rails, your could alias [Primer ViewComponent's `class_names` helper][primer-class-names-helper], which replicates the same feature.
46
+
47
+ [token-list-helper]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-token_list
48
+ [primer-class-names-helper]: https://github.com/primer/view_components/blob/60fc38f08915ce42f8965f2cc8f3474ab317438c/app/lib/primer/class_name_helper.rb
49
+
50
+ ### Installing the Gem
51
+
52
+ ```ruby
53
+ # Ideally replace the `branch: "main"` with a commit reference (`ref: COMMIT_HASH`)
54
+ # https://bundler.io/guides/git.html
55
+ gem "merge_attributes", github: "Amba-Health/merge_attributes", branch: "main"
56
+ ```
57
+
58
+ <details>
59
+ <summary>Coming soon!</summary>
60
+
61
+ Add this line to your application's Gemfile:
62
+
63
+ ```ruby
64
+ gem 'merge_attributes'
65
+ ```
66
+
67
+ And then execute:
68
+
69
+ bundle
70
+
71
+ Or install it yourself as:
72
+
73
+ gem install merge_attributes
74
+
75
+ </details>
76
+
77
+ ## Usage
78
+
79
+ ###  Providing the attributes
80
+
81
+ The method is flexible in the attributes it accepts and can be provided. All the examples bellow will generate the same final Hash of attributes:
82
+
83
+ ```rb
84
+ {
85
+ id: 'the-id',
86
+ class: 'a-class',
87
+ data:{
88
+ dialog_id: 'confirm'
89
+ }
90
+ }
91
+ ```
92
+
93
+ The method supports:
94
+
95
+ - `Hash`es
96
+
97
+ ```rb
98
+ merge_attributes({
99
+ id: 'the-id'
100
+ }, {
101
+ class: 'a-class'
102
+ },{
103
+ data: {
104
+ dialog_id: 'confirm'
105
+ }
106
+ })
107
+ ```
108
+
109
+ - an `Array` or `Array`s of `Hash`es
110
+
111
+ Nested arrays will be flattened, allowing you to directly pass a list of attributes you'd collected in another part of your app.
112
+
113
+ ```rb
114
+ merge_attributes([{
115
+ id: 'the-id'
116
+ }, [{
117
+ class: 'a-class'
118
+ }]],[{
119
+ data: {
120
+ dialog_id: 'confirm'
121
+ }
122
+ }])
123
+ ```
124
+
125
+ - Keyword arguments
126
+
127
+ Any keyword argument (aside from [`token_list_attributes`, see below](#token-list-attributes)) is treated as a final `Hash` of attributes
128
+
129
+ ```rb
130
+ merge_attributes({
131
+ id: 'the-id',
132
+ class: 'a-class'
133
+ },
134
+ data: {
135
+ dialog_id: 'confirm'
136
+ }
137
+ )
138
+ ```
139
+
140
+ - Mix'n'match
141
+
142
+ You can mix the different kind of attributes
143
+ in a single call
144
+
145
+ ```rb
146
+ merge_attributes({
147
+ id: 'the-id'
148
+ }, [[{
149
+ class: 'a-class'
150
+ }]],
151
+ data: {
152
+ dialog_id: 'confirm'
153
+ }
154
+ )
155
+ ```
156
+
157
+ Any `.blank?` value (after [pre-processing, see below](#pre-processing)) will be ignored.
158
+
159
+ ### Token list attributes
160
+
161
+ Attributes will generally be `deep_merged`, the value of the latest one in the list replacing any existing ones.
162
+
163
+ This model doesn't really work for the `class` attribute, where it's preferable that the values get concatenated with a space. Same goes for other attributes, like `data-action` or `data-controller` from Stimulus.
164
+
165
+ Out of the box, `merge_attributes` will concatenate the values of these attributes rather than replace them:
166
+
167
+ ```rb
168
+ merge_attributes({
169
+ class: 'my-component'
170
+ }, {
171
+ class: 'parent-component__child'
172
+ })
173
+ ```
174
+
175
+ generates
176
+
177
+ ```rb
178
+ {
179
+ class: 'my-component parent-component__child'
180
+ }
181
+ ```
182
+
183
+ ### Attributes format
184
+
185
+ The concatenation is done using [Rails's `token_list` helper][token-list-helper]. This means it accepts not only `Strings`, but `Array`s of `Strings` or `Hash`es with `true` or `false` values:
186
+
187
+ ```rb
188
+ merge_attributes({
189
+ class: 'my-component'
190
+ }, {
191
+ class: ['parent-component__child','my-component--variation']
192
+ }, {
193
+ class: {
194
+ active: true
195
+ }
196
+ })
197
+ ```
198
+
199
+ ###  Adding other attributes
200
+
201
+ You may want to treat other attributes that way. ARIA's `aria-labelledby` and `aria-describedby` would be great candidates for it for example.
202
+
203
+ The method accepts the `token_list_attributes` keyword argument for providing a list of attributes to concatenate:
204
+
205
+ ```rb
206
+ merge_attributes({
207
+ 'aria-labelledby': 'delete_action'
208
+ }, {
209
+ 'aria-labelledby': 'user_1'
210
+ },
211
+ token_list_attributes: [
212
+ *MergeAttributes::DEFAULT_TOKEN_LIST_ATTRIBUTES,
213
+ 'aria-labelledby'
214
+ ]
215
+ )
216
+ ```
217
+
218
+ As illustrated in the example, the `MergeAttributes::DEFAULT_TOKEN_LIST_ATTRIBUTES` will help you add to the default list.
219
+
220
+ ###  Pre-processing
221
+
222
+ The method accepts a block that'll let you process the attributes prior to their merging (but after the different arguments have been collected and flattened into a single `Array`).
223
+
224
+ This is the perfect time to call `to_h` if some of the attributes are not hashes already. Or resolve any conflicts between attributes in the `data` hash and as `data-...` keys.
225
+
226
+ The block will be provided:
227
+
228
+ - the current `Hash` being pre-processed
229
+ - its index in the whole attribute list
230
+ - the attribute list itself
231
+
232
+ It is expected to return the transformed list of attributes (or a `.blank?` value if you want to filter it out)
233
+
234
+ ```rb
235
+ merge_attributes({
236
+ class: 'my-component'
237
+ }, class: {
238
+ class: 'parent-component__child'
239
+ }) do |attributes, index, attributes_list|
240
+ attributes.merge({
241
+ # Dummy example
242
+ "data-item-#{index}": index
243
+ })
244
+ end
245
+ ```
246
+
247
+ ## Development
248
+
249
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
250
+
251
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
252
+
253
+ ## Contributing
254
+
255
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/Amba-Health/merge_attributes>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
256
+
257
+ ## License
258
+
259
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
260
+
261
+ ## Code of Conduct
262
+
263
+ Everyone interacting in the MergeAttributes project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Amba-Health/merge_attributes/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "merge_attributes"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,82 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/hash/keys"
3
+
4
+ module MergeAttributes
5
+ DEFAULT_TOKEN_LIST_ATTRIBUTES = [[:class], [:data, :controller], [:data, :action]].freeze
6
+ module Helper
7
+ def merge_attributes(
8
+ attributes, # Ensures we have at least one set of attributes to work with
9
+ *extra_attributes_list, # Collects as many other sets as needed
10
+ token_list_attributes: MergeAttributes::DEFAULT_TOKEN_LIST_ATTRIBUTES,
11
+ **extra_attributes # And keyword params as a final set of attributes
12
+ )
13
+ attributes_to_merge = extra_attributes_list.unshift(attributes)
14
+
15
+ # Ruby considers a final hash to be extra options
16
+ # rather than an argument
17
+ unless extra_attributes.blank? # Avoids having an extra hash of attributes in the processing
18
+ attributes_to_merge << extra_attributes
19
+ end
20
+
21
+ attributes_to_merge = attributes_to_merge
22
+ .flatten
23
+
24
+ if block_given?
25
+ attributes_to_merge = attributes_to_merge.each_with_index.map do |attributes, index|
26
+ yield(attributes, index, attributes_to_merge)
27
+ end
28
+ end # Handle nested arrays that may have been used for collecting series of attributes
29
+
30
+ execute_attribute_merge(
31
+ attributes_to_merge.reject { |item| item.blank? }, # No need to process blank values
32
+ token_list_attributes: token_list_attributes
33
+ )
34
+ end
35
+
36
+ protected
37
+
38
+ def execute_attribute_merge(attributes_list, token_list_attributes: [])
39
+ # Convert all keys to symbol to ensure we
40
+ # don't duplicate keys because one hash provides
41
+ # a String and another a Symbol with the same name
42
+ attributes_list.map(&:deep_symbolize_keys)
43
+
44
+ return {} if attributes_list.empty?
45
+
46
+ attributes, *attributes_to_merge = attributes_list
47
+
48
+ result = attributes
49
+
50
+ attributes_to_merge.each do |extra_attributes|
51
+ # deep_merge the attributes so we handle the data Hash properly
52
+ result = result.deep_merge extra_attributes
53
+ end
54
+
55
+ token_list_attributes.each do |attribute_path|
56
+ attribute_path = case attribute_path
57
+ when String
58
+ attribute_path.split("-").map(&:to_sym)
59
+ when Array
60
+ attribute_path.map(&:to_sym)
61
+ else
62
+ attribute_path.to_s.split("-").map(&:to_sym)
63
+ end
64
+
65
+ value = token_list(attributes.dig(*attribute_path), *attributes_to_merge.map { |attr| attr.dig(*attribute_path) })
66
+ bury(result, *attribute_path, value) unless value.blank?
67
+ end
68
+
69
+ result
70
+ end
71
+
72
+ # Opposite of `Hash#dig` for deep setting hashes values
73
+ # https://bugs.ruby-lang.org/issues/13179
74
+ def bury(hash, *where, value)
75
+ me = hash
76
+ where[0..-2].each { |key|
77
+ me = me[key] || {} # Create a new hash if the key is not found
78
+ }
79
+ me[where[-1]] = value
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,11 @@
1
+ require "rails/railtie"
2
+ module MergeAttributes
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "merge_attributes.action_view" do |app|
5
+ ActiveSupport.on_load :action_view do
6
+ require "merge_attributes/helper"
7
+ include MergeAttributes::Helper
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module MergeAttributes
2
+ def self.gem_version
3
+ Gem::Version.new VERSION::STRING
4
+ end
5
+
6
+ module VERSION
7
+ MAJOR = 0
8
+ MINOR = 1
9
+ TINY = 1
10
+ PRE = nil
11
+
12
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ require "merge_attributes/version"
2
+ require "merge_attributes/helper"
3
+
4
+ # If running inside a rails app, inject the helper
5
+ # https://api.rubyonrails.org/classes/Rails/Railtie.html
6
+ require "merge_attributes/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,47 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "merge_attributes/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "merge_attributes"
7
+ spec.version = MergeAttributes.gem_version
8
+ spec.authors = ["Romaric Pascal", "Amba Health & Care"]
9
+ spec.email = ["hello@romaricpascal.is", "developers@amba.co"]
10
+
11
+ spec.summary = "Merge hashes as HTML attributes, accounting for the specifics of `class`,`data-controller`, `data-action`"
12
+ spec.homepage = "https://github.com/Amba-Health/merge_attributes"
13
+ spec.license = "MIT"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if spec.respond_to?(:metadata)
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/Amba-Health/merge_attributes"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_dependency "activesupport", ">= 6.1.0"
36
+
37
+ spec.add_development_dependency "bundler", ">= 2.0.0"
38
+ spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ spec.add_development_dependency "byebug"
41
+ spec.add_development_dependency "rspec-rails", "~>4.0.0"
42
+ spec.add_development_dependency "standardrb"
43
+
44
+ # >= 6.1.0 so it provides the `token_list` helper
45
+ # https://github.com/rails/rails/blob/6e2247e9760da37882429b7a72dff1dd1ea5963e/actionview/lib/action_view/helpers/tag_helper.rb#L341
46
+ spec.add_runtime_dependency "rails", ">=6.1.0"
47
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: merge_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Romaric Pascal
8
+ - Amba Health & Care
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2024-07-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 6.1.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 6.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.0.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.0.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: byebug
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec-rails
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 4.0.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 4.0.0
98
+ - !ruby/object:Gem::Dependency
99
+ name: standardrb
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rails
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 6.1.0
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 6.1.0
126
+ description:
127
+ email:
128
+ - hello@romaricpascal.is
129
+ - developers@amba.co
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - ".gitignore"
135
+ - ".rspec"
136
+ - ".ruby-version"
137
+ - ".travis.yml"
138
+ - CODE_OF_CONDUCT.md
139
+ - Gemfile
140
+ - Gemfile.lock
141
+ - LICENSE.txt
142
+ - README.md
143
+ - Rakefile
144
+ - bin/console
145
+ - bin/setup
146
+ - lib/merge_attributes.rb
147
+ - lib/merge_attributes/helper.rb
148
+ - lib/merge_attributes/railtie.rb
149
+ - lib/merge_attributes/version.rb
150
+ - merge_attributes.gemspec
151
+ homepage: https://github.com/Amba-Health/merge_attributes
152
+ licenses:
153
+ - MIT
154
+ metadata:
155
+ homepage_uri: https://github.com/Amba-Health/merge_attributes
156
+ source_code_uri: https://github.com/Amba-Health/merge_attributes
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubygems_version: 3.0.3.1
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Merge hashes as HTML attributes, accounting for the specifics of `class`,`data-controller`,
176
+ `data-action`
177
+ test_files: []