slotify 0.0.4 → 0.1.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 +4 -4
- data/README.md +346 -136
- data/lib/slotify/concerns/symbol_inflection_helper.rb +19 -0
- data/lib/slotify/error.rb +0 -9
- data/lib/slotify/extensions/base.rb +5 -12
- data/lib/slotify/extensions/partial_renderer.rb +8 -5
- data/lib/slotify/extensions/template.rb +2 -2
- data/lib/slotify/services/tag_options_merger.rb +32 -39
- data/lib/slotify/{partial.rb → slots.rb} +42 -36
- data/lib/slotify/value.rb +4 -4
- data/lib/slotify/value_store.rb +3 -5
- data/lib/slotify/version.rb +1 -1
- metadata +5 -5
- data/lib/slotify/concerns/inflection_helper.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7901ad71acf99111a450667e96a494becbfc55d78c62ab2314b7c3b8dae7e588
|
4
|
+
data.tar.gz: 73ef165ade4d766b69ef68cbba336bf488991e452038f751d3e5fb2f82c34bf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 906ce11515dcb698273445333d273233e96b9006454714b8d17d8a034f38fd333ef649c4d196e21d403e93ed1bca3c4016849aa4c1ac22c3803870d851dce1ba
|
7
|
+
data.tar.gz: d45ebbafe579ff34b4727ab14edae97898a87fd87452b841b3989f438248d927cdb16593b4ab05321bf4dce8a7f1e99f39639065798a91a7436f72f442d2b227
|
data/README.md
CHANGED
@@ -5,15 +5,15 @@
|
|
5
5
|
|
6
6
|
## Superpowered slots for ActionView partials
|
7
7
|
|
8
|
-
Slotify
|
8
|
+
Slotify brings a ViewComponent-style **content slot API** to ActionView partials.
|
9
9
|
|
10
|
-
Slots are a convenient way to pass blocks of content in to a partial without having to resort to ugly `<% capture do ... end %>` workarounds or unscoped (global) `<% content_for :foo %>` declarations.
|
10
|
+
Slots are a convenient way to pass blocks of content in to a partial without having to resort to ugly `<% capture do ... end %>` workarounds or unscoped (global) `<% content_for :foo %>` declarations.
|
11
11
|
|
12
12
|
Slotified partials are a great way to build components in a Rails app without the additional overhead and learning curve of libraries like [ViewComponent](https://viewcomponent.org/) or [Phlex](https://www.phlex.fun/).
|
13
13
|
|
14
|
-
> [!
|
15
|
-
> Slotify is
|
16
|
-
The documentation is still
|
14
|
+
> [!WARNING]
|
15
|
+
> Slotify is in an early stage of development and has not been properly battle-tested yet.
|
16
|
+
The documentation is still a work-in-progress and the API may change between point releases until it reaches `v1.0`.
|
17
17
|
|
18
18
|
###
|
19
19
|
|
@@ -25,13 +25,12 @@ Slotify slots are defined using a **[strict locals](https://guides.rubyonrails.o
|
|
25
25
|
<%# slots: (title:, body: nil, theme: "default") -%>
|
26
26
|
```
|
27
27
|
|
28
|
-
Slot content is accessed via **standard local variables** within the partial. So a simple, slot-enabled `
|
28
|
+
Slot content is accessed via **standard local variables** within the partial. So a simple, slot-enabled `example` partial template might look something like this:
|
29
29
|
|
30
30
|
```erb
|
31
|
-
|
31
|
+
# views/examples/_example.html.erb
|
32
32
|
|
33
33
|
<%# slots: (title: "Default title", body: nil) -%>
|
34
|
-
|
35
34
|
<article>
|
36
35
|
<h1><%= title %></h1>
|
37
36
|
<% if body.present? %>
|
@@ -50,7 +49,9 @@ When the partial is rendered, a special `partial` object is yielded as an argume
|
|
50
49
|
For example, here our `article` partial is being rendered with content for the `title` and `body` slots that were defined above:
|
51
50
|
|
52
51
|
```erb
|
53
|
-
|
52
|
+
# views/examples/show.html.erb
|
53
|
+
|
54
|
+
<%= render "example" do |partial| %>
|
54
55
|
<% partial.with_title "This is a title" %>
|
55
56
|
<% partial.with_body do %>
|
56
57
|
<p>You can use <%= tag.strong "markup" %> within slot content blocks without
|
@@ -68,10 +69,10 @@ But this example just scratches the surface of what Slotify slots can do. Have a
|
|
68
69
|
<summary><h4>More full-featured example</h4></summary>
|
69
70
|
|
70
71
|
```erb
|
71
|
-
|
72
|
+
# views/examples/_example.html.erb
|
72
73
|
|
73
74
|
<%# locals: (id:) -%>
|
74
|
-
<%# slots: (title: "Example title", lists:
|
75
|
+
<%# slots: (title: "Example title", lists: [], quotes: [], website_link:) -%>
|
75
76
|
|
76
77
|
<%= tag.section id: do %>
|
77
78
|
<h1 class="example-title">
|
@@ -95,10 +96,10 @@ But this example just scratches the surface of what Slotify slots can do. Have a
|
|
95
96
|
```
|
96
97
|
|
97
98
|
```erb
|
98
|
-
|
99
|
+
# views/examples/_list.html.erb
|
99
100
|
|
100
101
|
<%# locals: (title:) -%>
|
101
|
-
<%# slots: (items:
|
102
|
+
<%# slots: (items: []) -%>
|
102
103
|
|
103
104
|
<h3><%= title %></h3>
|
104
105
|
|
@@ -110,7 +111,7 @@ But this example just scratches the surface of what Slotify slots can do. Have a
|
|
110
111
|
```
|
111
112
|
|
112
113
|
```erb
|
113
|
-
|
114
|
+
# views/examples/show.html.erb
|
114
115
|
|
115
116
|
<%= render "example", id: "slotify-example" do |partial| %>
|
116
117
|
<% partial.with_subtitle do %>
|
@@ -156,13 +157,13 @@ Required slots are defined without a default value.
|
|
156
157
|
If no content is provided for a required slot then a `StrictSlotsError` exception will be raised.
|
157
158
|
|
158
159
|
```erb
|
159
|
-
|
160
|
+
# views/examples/_required.html.erb
|
160
161
|
|
161
162
|
<%# slots: (title:) -%>
|
162
163
|
<h1><%= title %></h1>
|
163
|
-
```
|
164
164
|
|
165
|
-
|
165
|
+
# views/examples/show.html.erb
|
166
|
+
|
166
167
|
<%= render "required" do |partial| %>
|
167
168
|
<!-- ❌ raises an error, no content set for the `title` slot -->
|
168
169
|
<% end %>
|
@@ -177,33 +178,6 @@ the default value will be used instead.
|
|
177
178
|
<%# slots: (title: "Default title", author: nil) -%>
|
178
179
|
```
|
179
180
|
|
180
|
-
### Using alongside strict locals
|
181
|
-
|
182
|
-
Strict locals can be defined in 'slotified' partial templates in the same way as usual,
|
183
|
-
either above or below the `slots` definition.
|
184
|
-
|
185
|
-
```erb
|
186
|
-
<!-- _article.html.erb -->
|
187
|
-
|
188
|
-
<%# locals: (title:) -%>
|
189
|
-
<%# slots: (body: "No content available") -%>
|
190
|
-
|
191
|
-
<article>
|
192
|
-
<h1><%= title %></h1>
|
193
|
-
<div><%= body %></div>
|
194
|
-
</article>
|
195
|
-
```
|
196
|
-
|
197
|
-
Locals are provided when rendering the partial in the usual way.
|
198
|
-
|
199
|
-
```erb
|
200
|
-
<%= render "article", title: "Article title here" do |partial| %>
|
201
|
-
<% partial.with_body do %>
|
202
|
-
<p>Body content here...</p>
|
203
|
-
<% end %>
|
204
|
-
<% end %>
|
205
|
-
```
|
206
|
-
|
207
181
|
### Setting slot values
|
208
182
|
|
209
183
|
Content is passed into slots using dynamically generated `partial#with_<slot_name>` writer methods.
|
@@ -215,9 +189,7 @@ The following two examples are equivalent:
|
|
215
189
|
<%= render "example" do |partial| %>
|
216
190
|
<% partial.with_title "Title passed as argument" %>
|
217
191
|
<% end %>
|
218
|
-
```
|
219
192
|
|
220
|
-
```erb
|
221
193
|
<%= render "example" do |partial| %>
|
222
194
|
<% partial.with_title do %>
|
223
195
|
Title passed as block content
|
@@ -242,38 +214,44 @@ The slot value writer methods also accept optional arbitrary keyword arguments.
|
|
242
214
|
These can then be accessed in the partial template via the `.options` method on the slot variable.
|
243
215
|
|
244
216
|
```erb
|
217
|
+
# views/examples/show.html.erb
|
218
|
+
|
245
219
|
<%= render "example" do |partial| %>
|
246
220
|
<% partial.with_title "The title", class: "color-hotpink", data: {controller: "fancy-title"} %>
|
247
221
|
<% end %>
|
248
|
-
```
|
249
222
|
|
250
|
-
|
251
|
-
<%# slots: (title:) -%>
|
223
|
+
# views/examples/_example.html.erb
|
252
224
|
|
253
|
-
|
254
|
-
<%= title %>
|
225
|
+
<%# slots: (title:) -%>
|
226
|
+
<%= title.options.keys %> ➡️ [:class, :data]
|
227
|
+
<%= title %> ➡️ "The title"
|
255
228
|
```
|
256
229
|
|
257
230
|
Slot options can be useful for providing tag attributes when rendering slot content or rendering variants
|
258
231
|
of a slot based on an option value.
|
259
232
|
|
233
|
+
```erb
|
234
|
+
<%= tag.h1 **title.options %><%= title %><% end %>
|
235
|
+
➡️ <h1 class="color-hotpink" data-controller="fancy-title">The title</h1>
|
236
|
+
```
|
237
|
+
|
260
238
|
When rendered as a string the options are passed through the Rails `tag.attributes` helper to generate an HTML tag attributes string:
|
261
239
|
|
262
240
|
```erb
|
263
241
|
<h1 <%= title.options %>><%= title %></h1>
|
264
|
-
|
242
|
+
➡️ <h1 class="color-hotpink" data-controller="fancy-title">The title</h1>
|
265
243
|
```
|
266
244
|
|
267
|
-
###
|
245
|
+
### Single- vs multi-value slots
|
268
246
|
|
269
247
|
There are two types of slots.
|
270
248
|
|
271
|
-
* **Single-value** slots can only be called **once**
|
272
|
-
* **
|
249
|
+
* **Single-value** slots can only be called **once** when rendering the partial. The corresponding variable in the template represents a single slot value.
|
250
|
+
* **Multi-value** slots can be called **many times** when rendering the partial. The corresponding variable in the template represents **a collection of slot values**.
|
273
251
|
|
274
252
|
#### Single-value slots
|
275
253
|
|
276
|
-
Single-value slots are defined using a **
|
254
|
+
Single-value slots are defined using a **singular** slot name:
|
277
255
|
|
278
256
|
```erb
|
279
257
|
<%# slots: (item: nil) -%>
|
@@ -283,15 +261,17 @@ Single-value slots can be called once (at most)
|
|
283
261
|
and their corresponding template variable represents a single value:
|
284
262
|
|
285
263
|
```erb
|
264
|
+
# views/examples/show.html.erb
|
265
|
+
|
286
266
|
<%= render "example" do |partial| %>
|
287
267
|
<% partial.with_item "Item one" %>
|
288
268
|
<% end %>
|
289
|
-
```
|
290
269
|
|
291
|
-
|
270
|
+
# views/examples/_example.html.erb
|
271
|
+
|
292
272
|
<%# slots: (item: nil) -%>
|
293
273
|
<div>
|
294
|
-
<%= item %>
|
274
|
+
<%= item %> ➡️ "Item one"
|
295
275
|
</div>
|
296
276
|
```
|
297
277
|
|
@@ -301,35 +281,37 @@ and their corresponding template variable represents a single value:
|
|
301
281
|
> ```erb
|
302
282
|
> <%= render "example" do |partial| %>
|
303
283
|
> <% partial.with_item "Item one" %>
|
304
|
-
> <% partial.with_item "Item two" %>
|
284
|
+
> <% partial.with_item "Item two" %> ❌ raises an error!
|
305
285
|
> <% end %>
|
306
286
|
> ```
|
307
287
|
|
308
|
-
####
|
288
|
+
#### Multi-value slots
|
309
289
|
|
310
|
-
|
290
|
+
Multi-value slots are defined using a **plural** slot name:
|
311
291
|
|
312
292
|
```erb
|
313
|
-
<%# slots: (items:
|
293
|
+
<%# slots: (items: []) -%>
|
314
294
|
```
|
315
295
|
|
316
|
-
|
296
|
+
Multi-value slots can be called as many times as needed
|
317
297
|
and their corresponding template variable represents an array of values.
|
318
298
|
|
319
|
-
The slot writer methods for
|
299
|
+
The slot writer methods for multi-value slots use the **singluar form** of the slot name (e.g. `#with_item` for the `items` slot).
|
320
300
|
|
321
301
|
```erb
|
302
|
+
# views/examples/show.html.erb
|
303
|
+
|
322
304
|
<%= render "example" do |partial| %>
|
323
305
|
<% partial.with_item "Item one" %>
|
324
306
|
<% partial.with_item "Item two" %>
|
325
307
|
<% partial.with_item "Item three" %>
|
326
308
|
<% end %>
|
327
|
-
```
|
328
309
|
|
329
|
-
|
330
|
-
|
310
|
+
# views/examples/_example.html.erb
|
311
|
+
|
312
|
+
<%# slots: (items: []) -%>
|
331
313
|
|
332
|
-
<%= items %>
|
314
|
+
<%= items %> ➡️ ["Item one", "Item two", "Item three"]
|
333
315
|
|
334
316
|
<ul>
|
335
317
|
<% items.each do |item| %>
|
@@ -338,28 +320,119 @@ The slot writer methods for multiple-value slots use the **singluar form** of th
|
|
338
320
|
</li>
|
339
321
|
<% end %>
|
340
322
|
</ul>
|
323
|
+
|
324
|
+
➡️ <ul><li>Item one</li><li>Item two</li><li>Item three</li></ul>
|
341
325
|
```
|
342
326
|
|
343
|
-
### Using
|
327
|
+
### Using with view helpers
|
344
328
|
|
345
|
-
|
329
|
+
Slot values can be used with Rails view helpers (such as tag helpers) in the partial templates in the usual way:
|
330
|
+
|
331
|
+
```erb
|
332
|
+
<%= tag.h1 title %>
|
333
|
+
```
|
334
|
+
|
335
|
+
[Slot options](#slot-options) can be passed to helpers alongside the content by splatting slot value `.options`:
|
336
|
+
|
337
|
+
```erb
|
338
|
+
<%= tag.h1 title, **title.options %>
|
339
|
+
```
|
340
|
+
|
341
|
+
#### Slotified helpers
|
342
|
+
|
343
|
+
Slotify patches a number of the most commonly used view helpers (such as `content_tag`, `link_to`) so that slot value arguments and options are transparently expanded and passed to the underlying helper. This means that manual args/options splatting (as described above) is not required.
|
344
|
+
|
345
|
+
```erb
|
346
|
+
# views/examples/show.html.erb
|
347
|
+
|
348
|
+
<%= render "example" do |partial| %>
|
349
|
+
<% partial.with_title "The title", class: "color-hotpink" %>
|
350
|
+
<% partial.with_website_link "Example website", "https://example.com", data: {controller: "clicker"} %>
|
351
|
+
<% end %>
|
352
|
+
|
353
|
+
# views/examples/_example.html.erb
|
354
|
+
|
355
|
+
<%# slots: (title: nil, website_link: nil) -%>
|
356
|
+
|
357
|
+
<%= content_tag :h1, title %>
|
358
|
+
➡️ <h1 class="color-hotpink">The title</h1>
|
359
|
+
|
360
|
+
<%= link_to website_link %>
|
361
|
+
➡️ <a href="https://example.com" data-controller="clicker">Example website</a>
|
362
|
+
```
|
363
|
+
|
364
|
+
Any options provided to the helper are 'smart-merged' with slot value options using the [Phlex `mix` helper](https://www.phlex.fun/sgml/helpers#mix) to ensure token list options (such as class names) are properly combined instead of being overwritten.
|
346
365
|
|
347
366
|
```erb
|
348
|
-
|
349
|
-
|
367
|
+
<%= content_tag :h1, title, class: "text-xl", id: "headline" %> <!-- options here are merged with slot value options -->
|
368
|
+
➡️ <h1 class="text-xl color-hotpink" id="headline">The title</h1>
|
350
369
|
|
351
|
-
|
352
|
-
|
370
|
+
<%= link_to website_link, target: "_blank" %>
|
371
|
+
➡️ <a href="https://example.com" data-controller="clicker" target="_blank">Example website</a>
|
353
372
|
```
|
354
373
|
|
374
|
+
If a slotified helper is provided with a slot value collection (i.e. from a [multi-value slot](#multi-value-slots)) then the helper will be run once for each value in the collection:
|
375
|
+
|
355
376
|
```erb
|
356
|
-
|
357
|
-
|
377
|
+
# views/examples/show.html.erb
|
378
|
+
|
379
|
+
<%= render "example" do |partial| %>
|
380
|
+
<% partial.with_item "Item one" %>
|
381
|
+
<% partial.with_item "Item two", class: "highlight" %>
|
382
|
+
<% end %>
|
383
|
+
|
384
|
+
# views/examples/_example.html.erb
|
385
|
+
|
386
|
+
<%# slots: (items: []) -%>
|
387
|
+
|
388
|
+
<%= content_tag :li, items %>
|
389
|
+
➡️ <li>Item one</li><li class="highlight">Item two</li>
|
390
|
+
|
391
|
+
<%= content_tag :li, items, class: "item" %>
|
392
|
+
➡️ <li class="item">Item one</li><li class="item highlight">Item two</li>
|
393
|
+
```
|
394
|
+
|
395
|
+
#### List of 'slotified' helpers
|
396
|
+
|
397
|
+
* `tag` _(top-level `tag` helper only, not the `tag.<tag_name>` shorthands)_
|
398
|
+
* `content_tag`
|
399
|
+
* `link_to`
|
400
|
+
* `link_to_if`
|
401
|
+
* `link_to_unless`
|
402
|
+
* `link_to_unless_current`
|
403
|
+
* `button_to`
|
404
|
+
* `mail_to`
|
405
|
+
* `sms_to`
|
406
|
+
* `phone_to`
|
407
|
+
* `url_for`
|
408
|
+
|
409
|
+
### Using alongside strict locals
|
410
|
+
|
411
|
+
Strict locals can be defined in 'slotified' partial templates in the same way as usual,
|
412
|
+
either above or below the `slots` definition.
|
413
|
+
|
414
|
+
```erb
|
415
|
+
# views/examples/_example.html.erb
|
416
|
+
|
417
|
+
<%# locals: (title:) -%>
|
418
|
+
<%# slots: (body: "No content available") -%>
|
419
|
+
|
420
|
+
<article>
|
421
|
+
<h1><%= title %></h1>
|
422
|
+
<div><%= body %></div>
|
423
|
+
</article>
|
424
|
+
```
|
425
|
+
|
426
|
+
Locals are provided when rendering the partial in the usual way.
|
358
427
|
|
359
|
-
|
428
|
+
```erb
|
429
|
+
# views/examples/show.html.erb
|
360
430
|
|
361
|
-
<%=
|
362
|
-
|
431
|
+
<%= render "article", title: "Article title here" do |partial| %>
|
432
|
+
<% partial.with_body do %>
|
433
|
+
<p>Body content here...</p>
|
434
|
+
<% end %>
|
435
|
+
<% end %>
|
363
436
|
```
|
364
437
|
|
365
438
|
### Rendering slots
|
@@ -371,9 +444,9 @@ The slot writer methods for multiple-value slots use the **singluar form** of th
|
|
371
444
|
**Singlular slot value variables** in partial templates are actually instances of `Slotity::Value`.
|
372
445
|
These value objects are automatically stringified so in most cases you will not even be aware of this and they can just be treated as regular string variables.
|
373
446
|
|
374
|
-
|
375
|
-
|
376
447
|
```erb
|
448
|
+
# views/examples/show.html.erb
|
449
|
+
|
377
450
|
<%= render "example" do |partial| %>
|
378
451
|
<% partial.with_title class: "color-hotpink" do %>
|
379
452
|
The title
|
@@ -382,19 +455,24 @@ These value objects are automatically stringified so in most cases you will not
|
|
382
455
|
```
|
383
456
|
|
384
457
|
```erb
|
385
|
-
|
386
|
-
<% items.is_a?(Slotify::ValueCollection) %> <!-- true -->
|
458
|
+
# views/examples/_example.html.erb
|
387
459
|
|
388
|
-
|
389
|
-
<% title.content %> <!-- "The title" -->
|
460
|
+
<%# slots: (title: nil) -%>
|
390
461
|
|
391
|
-
<% title.
|
392
|
-
|
462
|
+
<% title.is_a?(Slotify::Value) %> ➡️ true
|
463
|
+
|
464
|
+
<%= title %> ➡️ "The title"
|
465
|
+
<% title.content %> ➡️ "The title"
|
466
|
+
|
467
|
+
<% title.options %> ➡️ { class: "color-hotpink" }
|
468
|
+
<%= title.options %> ➡️ "class='color-hotpink'"
|
393
469
|
```
|
394
470
|
|
395
471
|
**Plural slot value variables** in partial templates are instances of the enumerable `Slotify::ValueCollection` class, with all items instances of `Slotity::Value`.
|
396
472
|
|
397
473
|
```erb
|
474
|
+
# views/examples/show.html.erb
|
475
|
+
|
398
476
|
<%= render "example" do |partial| %>
|
399
477
|
<% partial.with_item "Item one" %>
|
400
478
|
<% partial.with_item "Item two", class: "current" %>
|
@@ -402,14 +480,18 @@ These value objects are automatically stringified so in most cases you will not
|
|
402
480
|
```
|
403
481
|
|
404
482
|
```erb
|
405
|
-
|
483
|
+
# views/examples/_example.html.erb
|
484
|
+
|
485
|
+
<%# slots: (items: []) -%>
|
486
|
+
|
487
|
+
<% items.is_a?(Slotify::ValueCollection) %> ➡️ true
|
406
488
|
|
407
489
|
<% items.each do |item| %>
|
408
490
|
<li <%= item.options %>><%= item %></li>
|
409
491
|
<% end %>
|
410
|
-
|
492
|
+
➡️ <li>Item one</li> <li class="current">Item two</li>
|
411
493
|
|
412
|
-
<%= items %>
|
494
|
+
<%= items %> ➡️ "Item one Item two"
|
413
495
|
```
|
414
496
|
|
415
497
|
#### `Slotity::Value`
|
@@ -426,15 +508,41 @@ Returns a `Slotify::ValueOptions` instance that can be treated like a `Hash`. Ca
|
|
426
508
|
|
427
509
|
When converted to a string either explicitly (via `.to_s`) or implicitly (by outputting the value template using ERB `<%= %>` expression tags) the stringified value is generated by passing the options hash through the Rails `tag.attributes` helper.
|
428
510
|
|
511
|
+
**`.args`**
|
512
|
+
|
513
|
+
Returns an array of the arguments that were passed into the slot writer method (if any) when rendering the partial.
|
514
|
+
|
515
|
+
Slot arguments can also be accessed using hash access notation.
|
516
|
+
|
517
|
+
```erb
|
518
|
+
# views/examples/show.html.erb
|
519
|
+
|
520
|
+
<%= render "example" do |partial| %>
|
521
|
+
<% partial.with_link "Example link", "https://example.com", class: "external-link" %>
|
522
|
+
<% end %>
|
523
|
+
```
|
524
|
+
|
525
|
+
```erb
|
526
|
+
# views/examples/_example.html.erb
|
527
|
+
|
528
|
+
<%# slots: (link: nil) -%>
|
529
|
+
|
530
|
+
<% link.args %> ➡️ ["Example link", "https://example.com"]
|
531
|
+
<% link[0] %> ➡️ "Example link"
|
532
|
+
<% link[1] %> ➡️ "https://example.com"
|
533
|
+
```
|
534
|
+
|
429
535
|
**`.with_default_options(default_options)`**
|
430
536
|
|
431
|
-
Merges
|
537
|
+
Merges slot options with the `default_options` hash provided. Returns a new `Slotity::Value` instance with the merged options set.
|
538
|
+
|
539
|
+
Options are 'smart-merged' using the [Phlex `mix` helper](https://www.phlex.fun/sgml/helpers#mix) to ensure token list options (such as class names) are properly combined instead of being overwritten.
|
432
540
|
|
433
541
|
```erb
|
434
|
-
<%
|
542
|
+
<% title_with_defaults = title.with_default_options(class: "size-lg", aria: {level: 1}) %>
|
435
543
|
|
436
|
-
<%
|
437
|
-
<%=
|
544
|
+
<% title_with_defaults.options %> ➡️ { class: "size-lg color-hotpink", aria: {level: 1} }
|
545
|
+
<%= title_with_defaults.options %> ➡️ "class='size-lg color-hotpink' aria-level='1'"
|
438
546
|
```
|
439
547
|
|
440
548
|
## Slotify vs alternatives
|
@@ -447,10 +555,10 @@ However there are a number of key differences:
|
|
447
555
|
* Slotify requires the explicit definition of slots using 'strict locals'-style comments;
|
448
556
|
Nice partials slots are implicitly defined when rendering the partial.
|
449
557
|
* Slotify slot values are available as local variables;
|
450
|
-
with Nice partials slot values are accessed via methods
|
451
|
-
* Slotify has the concept (and enforces the use) of single-
|
558
|
+
with Nice partials slot values are accessed via methods a single `partial` variable.
|
559
|
+
* Slotify has the concept (and enforces the use) of single- vs. multi-value slots.
|
452
560
|
* Slotify slot content and options are transparently expanded and merged into defaults when using with helpers like `content_tag` and `link_to`.
|
453
|
-
* Slotify slot values are `renderable` objects
|
561
|
+
* Slotify slot values are `renderable` objects.
|
454
562
|
|
455
563
|
You might choose slotify if you prefer a stricter, 'Rails-native'-feeling slots implementation, and Nice Partials if you want more render-time flexibility and a clearer
|
456
564
|
separation of 'nice partial' functionality from ActionView-provided locals etc.
|
@@ -458,8 +566,7 @@ separation of 'nice partial' functionality from ActionView-provided locals etc.
|
|
458
566
|
#### `view_component`
|
459
567
|
|
460
568
|
Both [ViewComponent](https://viewcomponent.org/) and Slotify provide a 'slots' API for content blocks.
|
461
|
-
Slotify's slot writer syntax (i.e. `.with_<slot_name>` methods) and the concept of single-value (`renders_one`) vs
|
462
|
-
are both modelled on ViewComponent's slots implementation.
|
569
|
+
Slotify's slot writer syntax (i.e. `.with_<slot_name>` methods) and the concept of single-value (`renders_one`) vs multi-value (`renders_many`) slots are both modelled on ViewComponent's slots implementation.
|
463
570
|
|
464
571
|
However apart from that they are quite different. Slotify adds functionality to regular ActionView partials whereas ViewComponent provides a complete standalone component system.
|
465
572
|
|
@@ -484,78 +591,181 @@ And then run `bundle install`. You are good to go!
|
|
484
591
|
* `Rails 7.1+`
|
485
592
|
* `Ruby 3.1+`
|
486
593
|
|
487
|
-
##
|
594
|
+
## Testing
|
488
595
|
|
489
|
-
Slotify
|
596
|
+
Slotify uses MiniTest for its test suite.
|
490
597
|
|
491
|
-
|
598
|
+
[Appraisal](https://github.com/thoughtbot/appraisal) is used in CI to test against a matrix of Ruby/Rails versions.
|
492
599
|
|
493
|
-
|
600
|
+
#### Run tests
|
494
601
|
|
495
|
-
|
602
|
+
```shell
|
603
|
+
bundle exec bin/test
|
604
|
+
```
|
496
605
|
|
497
|
-
|
606
|
+
### Benchmarks
|
498
607
|
|
499
|
-
|
500
|
-
|
608
|
+
Some crude render performance benchmark tests for `slotify`, `view_component` and `nice_partials` can be found in the `/performance` directory.
|
609
|
+
|
610
|
+
All benchmarks use a vanilla ActionView template rendering performance measurement as the baseline for comparison against.
|
611
|
+
|
612
|
+
* The **slots** benchmarks compare the performance of rendering a partial/component that uses slots against the baseline.
|
613
|
+
* The **no slots** benchmarks compare the performance of rendering a partial/component without slots (i.e. values provided as keyword arguments) against the baseline. These results are useful for determining how much the gem being benchmarked affects rendering performance even when slots are not used.
|
614
|
+
|
615
|
+
The benchmark tests are a work in progress right now so any suggestions for improvements would be much appreciated!
|
616
|
+
|
617
|
+
#### Benchmark results summary
|
618
|
+
|
619
|
+
* `slotify`, `nice_partials` and `view_component` all result in slighly slower partial/component rendering speeds compared to the 'vanilla ActionView' baseline (as expected).
|
620
|
+
* `slotify` is currently the closest to the baseline when rendering partials/components without any slots (~1.2x slower).
|
621
|
+
* `slotify` is currently the furthest from the baseline when rendering partials/components using slots (~3x slower).
|
622
|
+
|
623
|
+
|
624
|
+
#### Running benchmarks
|
625
|
+
|
626
|
+
You can run the benchmark tests locally using the `bin/benchmark` command from the root of the repository.
|
627
|
+
|
628
|
+
```shell
|
629
|
+
bundle exec bin/benchmarks # run all benchmarks
|
630
|
+
bundle exec bin/benchmarks slotify # run specified benchmarks only (slotify / view_component / nice_partials)
|
631
|
+
bundle exec bin/benchmarks --no-slots # run 'no-slots' benchmarks
|
632
|
+
```
|
633
|
+
|
634
|
+
<details>
|
635
|
+
<summary><h4>Recent benchmark results</h4></summary>
|
636
|
+
|
637
|
+
#### With slots:
|
501
638
|
|
502
639
|
```
|
503
|
-
|
640
|
+
➜ bin/benchmark
|
641
|
+
|
642
|
+
🏁🏁 SLOTIFY 🏁🏁
|
643
|
+
|
644
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
645
|
+
Warming up --------------------------------------
|
646
|
+
baseline 11.836k i/100ms
|
647
|
+
Calculating -------------------------------------
|
648
|
+
baseline 118.334k (± 2.8%) i/s (8.45 μs/i) - 591.800k in 5.005403s
|
504
649
|
|
505
650
|
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
506
651
|
Warming up --------------------------------------
|
507
|
-
|
508
|
-
slots 10.891k i/100ms
|
652
|
+
slots 2.229k i/100ms
|
509
653
|
Calculating -------------------------------------
|
510
|
-
|
511
|
-
slots 108.468k (± 3.3%) i/s (9.22 μs/i) - 1.089M in 10.053026s
|
654
|
+
slots 26.066k (± 7.8%) i/s (38.36 μs/i) - 131.511k in 5.087467s
|
512
655
|
|
513
656
|
Comparison:
|
514
|
-
|
515
|
-
slots:
|
657
|
+
baseline: 118333.8 i/s
|
658
|
+
slots: 26066.0 i/s - 4.54x slower
|
516
659
|
|
517
660
|
|
518
|
-
|
661
|
+
🏁🏁 NICE_PARTIALS 🏁🏁
|
519
662
|
|
520
663
|
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
521
664
|
Warming up --------------------------------------
|
522
|
-
|
523
|
-
slots 4.204k i/100ms
|
665
|
+
baseline 12.072k i/100ms
|
524
666
|
Calculating -------------------------------------
|
525
|
-
|
526
|
-
|
667
|
+
baseline 114.740k (± 4.4%) i/s (8.72 μs/i) - 579.456k in 5.060487s
|
668
|
+
|
669
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
670
|
+
Warming up --------------------------------------
|
671
|
+
slots 3.626k i/100ms
|
672
|
+
Calculating -------------------------------------
|
673
|
+
slots 35.971k (± 6.1%) i/s (27.80 μs/i) - 181.300k in 5.061126s
|
527
674
|
|
528
675
|
Comparison:
|
529
|
-
|
530
|
-
slots:
|
676
|
+
baseline: 114740.4 i/s
|
677
|
+
slots: 35971.0 i/s - 3.19x slower
|
531
678
|
|
532
679
|
|
533
|
-
|
680
|
+
🏁🏁 VIEW_COMPONENT 🏁🏁
|
534
681
|
|
535
682
|
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
536
683
|
Warming up --------------------------------------
|
537
|
-
|
538
|
-
slots 7.409k i/100ms
|
684
|
+
baseline 11.991k i/100ms
|
539
685
|
Calculating -------------------------------------
|
540
|
-
|
541
|
-
|
686
|
+
baseline 118.532k (± 2.3%) i/s (8.44 μs/i) - 599.550k in 5.060901s
|
687
|
+
|
688
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
689
|
+
Warming up --------------------------------------
|
690
|
+
slots 7.493k i/100ms
|
691
|
+
Calculating -------------------------------------
|
692
|
+
slots 72.281k (± 6.3%) i/s (13.83 μs/i) - 359.664k in 5.002508s
|
542
693
|
|
543
694
|
Comparison:
|
544
|
-
|
545
|
-
slots:
|
695
|
+
baseline: 118532.2 i/s
|
696
|
+
slots: 72281.3 i/s - 1.64x slower
|
697
|
+
```
|
698
|
+
|
699
|
+
#### Without slots:
|
700
|
+
|
701
|
+
```
|
702
|
+
➜ bin/benchmark --no-slots
|
546
703
|
|
704
|
+
🏁🏁 SLOTIFY 🏁🏁
|
547
705
|
|
548
|
-
|
706
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
707
|
+
Warming up --------------------------------------
|
708
|
+
baseline 13.071k i/100ms
|
709
|
+
Calculating -------------------------------------
|
710
|
+
baseline 127.673k (± 3.6%) i/s (7.83 μs/i) - 640.479k in 5.023506s
|
549
711
|
|
550
712
|
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
551
713
|
Warming up --------------------------------------
|
552
|
-
no slots
|
553
|
-
slots 77.000 i/100ms
|
714
|
+
no slots 11.029k i/100ms
|
554
715
|
Calculating -------------------------------------
|
555
|
-
no slots 110.
|
556
|
-
slots 789.118 (± 4.1%) i/s (1.27 ms/i) - 7.931k in 10.071749s
|
716
|
+
no slots 110.253k (± 2.0%) i/s (9.07 μs/i) - 551.450k in 5.003625s
|
557
717
|
|
558
718
|
Comparison:
|
559
|
-
|
560
|
-
|
719
|
+
baseline: 127673.0 i/s
|
720
|
+
no slots: 110252.6 i/s - 1.16x slower
|
721
|
+
|
722
|
+
|
723
|
+
🏁🏁 NICE_PARTIALS 🏁🏁
|
724
|
+
|
725
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
726
|
+
Warming up --------------------------------------
|
727
|
+
baseline 13.016k i/100ms
|
728
|
+
Calculating -------------------------------------
|
729
|
+
baseline 131.103k (± 1.8%) i/s (7.63 μs/i) - 663.816k in 5.065054s
|
730
|
+
|
731
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
732
|
+
Warming up --------------------------------------
|
733
|
+
no slots 4.556k i/100ms
|
734
|
+
Calculating -------------------------------------
|
735
|
+
no slots 44.888k (± 3.7%) i/s (22.28 μs/i) - 227.800k in 5.082635s
|
736
|
+
|
737
|
+
Comparison:
|
738
|
+
baseline: 131103.4 i/s
|
739
|
+
no slots: 44888.2 i/s - 2.92x slower
|
740
|
+
|
741
|
+
|
742
|
+
🏁🏁 VIEW_COMPONENT 🏁🏁
|
743
|
+
|
744
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
745
|
+
Warming up --------------------------------------
|
746
|
+
baseline 13.454k i/100ms
|
747
|
+
Calculating -------------------------------------
|
748
|
+
baseline 128.817k (± 5.7%) i/s (7.76 μs/i) - 645.792k in 5.038036s
|
749
|
+
|
750
|
+
ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
|
751
|
+
Warming up --------------------------------------
|
752
|
+
no slots 17.335k i/100ms
|
753
|
+
Calculating -------------------------------------
|
754
|
+
no slots 203.191k (± 2.2%) i/s (4.92 μs/i) - 1.023M in 5.036040s
|
755
|
+
|
756
|
+
Comparison:
|
757
|
+
no slots: 203190.7 i/s
|
758
|
+
baseline: 128817.3 i/s - 1.58x slower
|
561
759
|
```
|
760
|
+
|
761
|
+
</details>
|
762
|
+
|
763
|
+
## Credits
|
764
|
+
|
765
|
+
Slotify was inspired by the excellent [nice_partials gem](https://github.com/bullet-train-co/nice_partials) as well as ViewComponent's [slots implementation](https://viewcomponent.org/guide/slots.html).
|
766
|
+
|
767
|
+
`nice_partials` provides very similar functionality to Slotify but takes a slightly different approach/style. So if you are not convinced by Slotify then definitely [check it out](https://github.com/bullet-train-co/nice_partials)!
|
768
|
+
|
769
|
+
## License
|
770
|
+
|
771
|
+
The `slotify` gem is available as open source under the terms of the MIT License.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Slotify
|
2
|
+
module SymbolInflectionHelper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
mattr_accessor :singularizations, default: {}
|
6
|
+
|
7
|
+
def singular?(sym)
|
8
|
+
singularize(sym.to_sym) == sym.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def plural?(sym)
|
12
|
+
!singular?(sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
def singularize(sym)
|
16
|
+
singularizations[sym.to_sym] ||= sym.to_s.singularize.to_sym
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/slotify/error.rb
CHANGED
@@ -2,18 +2,9 @@ module Slotify
|
|
2
2
|
class UnknownSlotError < NameError
|
3
3
|
end
|
4
4
|
|
5
|
-
class SlotsDefinedError < RuntimeError
|
6
|
-
end
|
7
|
-
|
8
|
-
class UndefinedSlotError < StandardError
|
9
|
-
end
|
10
|
-
|
11
5
|
class MultipleSlotEntriesError < ArgumentError
|
12
6
|
end
|
13
7
|
|
14
|
-
class SlotArgumentError < ArgumentError
|
15
|
-
end
|
16
|
-
|
17
8
|
class StrictSlotsError < ArgumentError
|
18
9
|
end
|
19
10
|
|
@@ -3,20 +3,13 @@ module Slotify
|
|
3
3
|
module Base
|
4
4
|
include SlotCompatability
|
5
5
|
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :slotify
|
7
7
|
|
8
|
-
def
|
9
|
-
@
|
10
|
-
|
8
|
+
def capture_with_outer_slotify_access(*args, &block)
|
9
|
+
inner_slotify, @slotify = slotify, slotify.outer_slotify
|
10
|
+
inner_slotify.capture(*args, &block)
|
11
11
|
ensure
|
12
|
-
@
|
13
|
-
end
|
14
|
-
|
15
|
-
def capture_with_outer_partial_access(*args, &block)
|
16
|
-
inner_partial, @partial = partial, partial.outer_partial
|
17
|
-
inner_partial.capture(*args, &block)
|
18
|
-
ensure
|
19
|
-
@partial = inner_partial
|
12
|
+
@slotify = inner_slotify
|
20
13
|
end
|
21
14
|
|
22
15
|
make_compatible_with_slots :url_for, :link_to, :button_to, :link_to_unless_current,
|
@@ -4,17 +4,20 @@ module Slotify
|
|
4
4
|
def render_partial_template(view, locals, template, layout, block)
|
5
5
|
return super unless template.strict_slots?
|
6
6
|
|
7
|
-
view.
|
7
|
+
view.slotify = Slotify::Slots.new(view, template.strict_slots_keys)
|
8
8
|
|
9
|
-
view.
|
10
|
-
|
9
|
+
view.capture_with_outer_slotify_access(&block) if block
|
10
|
+
|
11
|
+
locals = locals.merge(view.slotify.slot_locals)
|
11
12
|
|
12
13
|
decorate_strict_slots_errors do
|
13
14
|
super(view, locals, template, layout, block)
|
14
15
|
end
|
16
|
+
ensure
|
17
|
+
view.slotify = view.slotify.outer_slotify if view.slotify
|
15
18
|
end
|
16
19
|
|
17
|
-
def decorate_strict_slots_errors
|
20
|
+
def decorate_strict_slots_errors
|
18
21
|
yield
|
19
22
|
rescue ActionView::Template::Error => error
|
20
23
|
if missing_strict_locals_error?(error)
|
@@ -31,7 +34,7 @@ module Slotify
|
|
31
34
|
end
|
32
35
|
|
33
36
|
def missing_strict_locals_error?(error)
|
34
|
-
error.template &&
|
37
|
+
error.template && defined?(ActionView::StrictLocalsError) && error.cause.is_a?(ActionView::StrictLocalsError) ||
|
35
38
|
(error.cause.is_a?(ArgumentError) && error.cause.message.match(/missing local/))
|
36
39
|
end
|
37
40
|
end
|
@@ -31,7 +31,7 @@ module Slotify
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def strict_slots_keys
|
34
|
-
@strict_slots_keys ||= strict_slots!.scan(STRICT_SLOTS_KEYS_REGEX).map(&:first)
|
34
|
+
@strict_slots_keys ||= strict_slots!.scan(STRICT_SLOTS_KEYS_REGEX).map(&:first).freeze
|
35
35
|
end
|
36
36
|
|
37
37
|
def strict_locals!
|
@@ -44,7 +44,7 @@ module Slotify
|
|
44
44
|
return super unless strict_slots?
|
45
45
|
|
46
46
|
strict_slots_keys.each_with_object(+super) do |key, code|
|
47
|
-
code << "
|
47
|
+
code << "slotify.set_slot_default(:#{key}, binding.local_variable_get(:#{key})); #{key} = slotify.content_for(:#{key});"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -1,53 +1,46 @@
|
|
1
1
|
module Slotify
|
2
2
|
module TagOptionsMerger
|
3
3
|
class << self
|
4
|
-
include ::ActionView::Helpers::TagHelper
|
5
|
-
|
6
4
|
def call(original, target)
|
7
|
-
original
|
8
|
-
target = target.to_h.deep_symbolize_keys
|
9
|
-
|
10
|
-
target.each do |key, value|
|
11
|
-
original[key] = case key
|
12
|
-
when :data
|
13
|
-
merge_data_options(original[key], value)
|
14
|
-
when :class
|
15
|
-
merge_class_options(original[key], value)
|
16
|
-
else
|
17
|
-
value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
original
|
5
|
+
mix(original, target)
|
22
6
|
end
|
23
7
|
|
24
8
|
private
|
25
9
|
|
26
|
-
|
27
|
-
|
10
|
+
# https://www.phlex.fun/sgml/helpers#mix
|
11
|
+
def mix(*args)
|
12
|
+
args.each_with_object({}) do |object, result|
|
13
|
+
result.merge!(object) do |_key, old, new|
|
14
|
+
case [old, new].freeze
|
15
|
+
in [Array, Array] | [Set, Set]
|
16
|
+
old + new
|
17
|
+
in [Array, Set]
|
18
|
+
old + new.to_a
|
19
|
+
in [Array, String]
|
20
|
+
old + [new]
|
21
|
+
in [Hash, Hash]
|
22
|
+
mix(old, new)
|
23
|
+
in [Set, Array]
|
24
|
+
old.to_a + new
|
25
|
+
in [Set, String]
|
26
|
+
old.to_a + [new]
|
27
|
+
in [String, Array]
|
28
|
+
[old] + new
|
29
|
+
in [String, Set]
|
30
|
+
[old] + new.to_a
|
31
|
+
in [String, String]
|
32
|
+
"#{old} #{new}"
|
33
|
+
in [_, nil]
|
34
|
+
old
|
35
|
+
else
|
36
|
+
new
|
37
|
+
end
|
38
|
+
end
|
28
39
|
|
29
|
-
|
30
|
-
|
31
|
-
original_data[key] = if key.in?([:controller, :action]) && all_kind_of?(String, values)
|
32
|
-
merge_strings(values)
|
33
|
-
else
|
34
|
-
value
|
40
|
+
result.transform_keys! do |key|
|
41
|
+
key.end_with?("!") ? key.name.chop.to_sym : key
|
35
42
|
end
|
36
43
|
end
|
37
|
-
|
38
|
-
original_data
|
39
|
-
end
|
40
|
-
|
41
|
-
def merge_class_options(original_classes, target_classes)
|
42
|
-
class_names(original_classes, target_classes)
|
43
|
-
end
|
44
|
-
|
45
|
-
def merge_strings(*args)
|
46
|
-
args.map(&:presence).compact.join(" ")
|
47
|
-
end
|
48
|
-
|
49
|
-
def all_kind_of?(kind, values)
|
50
|
-
values.none? { !_1.is_a?(kind) }
|
51
44
|
end
|
52
45
|
end
|
53
46
|
end
|
@@ -1,19 +1,21 @@
|
|
1
1
|
module Slotify
|
2
|
-
class
|
3
|
-
include
|
2
|
+
class Slots
|
3
|
+
include SymbolInflectionHelper
|
4
4
|
|
5
5
|
RESERVED_SLOT_NAMES = [
|
6
6
|
:content, :slot, :value, :content_for,
|
7
7
|
:capture, :yield, :partial
|
8
8
|
]
|
9
9
|
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :outer_slotify
|
11
11
|
|
12
|
-
def initialize(view_context)
|
12
|
+
def initialize(view_context, slots = [])
|
13
13
|
@view_context = view_context
|
14
|
-
@
|
14
|
+
@outer_slotify = view_context.slotify
|
15
15
|
@values = ValueStore.new(@view_context)
|
16
16
|
@defined_slots = nil
|
17
|
+
|
18
|
+
define_slots!(slots)
|
17
19
|
end
|
18
20
|
|
19
21
|
def content_for(slot_name)
|
@@ -50,8 +52,6 @@ module Slotify
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def slot_locals
|
53
|
-
validate_slots!
|
54
|
-
|
55
55
|
pairs = @defined_slots.map do |slot_name|
|
56
56
|
slot_values = values.for(slot_name)
|
57
57
|
slot_values = singular?(slot_name) ? slot_values&.first : slot_values
|
@@ -65,50 +65,56 @@ module Slotify
|
|
65
65
|
end.to_h
|
66
66
|
end
|
67
67
|
|
68
|
-
|
69
|
-
raise SlotsDefinedError, "Slots cannot be redefined" unless @defined_slots.nil?
|
68
|
+
private
|
70
69
|
|
70
|
+
attr_reader :values
|
71
|
+
|
72
|
+
def slot?(slot_name)
|
73
|
+
slot_name && @defined_slots.include?(slot_name.to_sym)
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_slots!(slot_names)
|
71
77
|
@defined_slots = slot_names.map(&:to_sym).each do |slot_name|
|
72
78
|
if RESERVED_SLOT_NAMES.include?(singularize(slot_name))
|
73
|
-
raise ReservedSlotNameError,
|
79
|
+
raise ReservedSlotNameError,
|
80
|
+
":#{slot_name} is a reserved word and cannot be used as a slot name"
|
74
81
|
end
|
75
|
-
end
|
76
|
-
end
|
77
82
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
if name.start_with?("with_")
|
84
|
-
values.add(name.to_s.delete_prefix("with_"), args, options, block)
|
85
|
-
elsif slot?(name)
|
86
|
-
content_for(name)
|
87
|
-
else
|
88
|
-
super
|
83
|
+
if singular?(slot_name)
|
84
|
+
define_single_value_slot_method(slot_name)
|
85
|
+
else
|
86
|
+
define_multi_value_slot_methods(slot_name)
|
87
|
+
end
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
92
|
-
|
91
|
+
def define_single_value_slot_method(slot_name)
|
92
|
+
method_name = :"with_#{slot_name}"
|
93
93
|
|
94
|
-
|
94
|
+
return if respond_to?(method_name)
|
95
95
|
|
96
|
-
|
97
|
-
|
96
|
+
self.class.define_method(method_name) do |*args, **options, &block|
|
97
|
+
if values.for(slot_name).any?
|
98
|
+
raise MultipleSlotEntriesError,
|
99
|
+
"slot :#{slot_name} is defined as a single-value slot but was called multiple times"
|
100
|
+
end
|
101
|
+
|
102
|
+
values.add(slot_name, args, options, block)
|
103
|
+
end
|
98
104
|
end
|
99
105
|
|
100
|
-
def
|
101
|
-
|
106
|
+
def define_multi_value_slot_methods(slot_name)
|
107
|
+
method_name = :"with_#{slot_name}"
|
108
|
+
singular_slot_name = singularize(slot_name)
|
109
|
+
|
110
|
+
return if respond_to?(method_name)
|
102
111
|
|
103
|
-
|
104
|
-
|
105
|
-
raise UndefinedSlotError,
|
106
|
-
"missing slot #{"definition".pluralize(undefined_slots.size)} for `#{undefined_slots.map { ":#{_1}(s)" }.join(", ")}`"
|
112
|
+
self.class.define_method(method_name) do |*args, **options, &block|
|
113
|
+
values.add(slot_name, args, options, block)
|
107
114
|
end
|
108
115
|
|
109
|
-
|
110
|
-
|
111
|
-
raise MultipleSlotEntriesError, "slot :#{slot_name} called #{slot_values.size} times (expected 1)" if slot_values.many?
|
116
|
+
self.class.define_method(:"with_#{singular_slot_name}") do |*args, **options, &block|
|
117
|
+
values.add(singular_slot_name, args, options, block)
|
112
118
|
end
|
113
119
|
end
|
114
120
|
end
|
data/lib/slotify/value.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Slotify
|
2
2
|
class Value
|
3
|
-
include
|
3
|
+
include SymbolInflectionHelper
|
4
4
|
|
5
5
|
attr_reader :slot_name, :args, :block
|
6
6
|
|
@@ -47,7 +47,7 @@ module Slotify
|
|
47
47
|
alias_method :to_hash, :to_h
|
48
48
|
|
49
49
|
def with_partial_path(partial_path)
|
50
|
-
Value.new(@view_context, @args, options, @block, slot_name: @slot_name, partial_path:)
|
50
|
+
Value.new(@view_context, @args, @options, @block, slot_name: @slot_name, partial_path:)
|
51
51
|
end
|
52
52
|
|
53
53
|
def with_default_options(default_options)
|
@@ -61,7 +61,7 @@ module Slotify
|
|
61
61
|
|
62
62
|
def method_missing(name, ...)
|
63
63
|
if name.start_with?("to_")
|
64
|
-
|
64
|
+
content.public_send(name, ...)
|
65
65
|
else
|
66
66
|
super
|
67
67
|
end
|
@@ -72,7 +72,7 @@ module Slotify
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def render_in(view_context, &block)
|
75
|
-
view_context.render partial_path, **@options.to_h,
|
75
|
+
view_context.render partial_path, **@options.to_h, &block || @block
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
data/lib/slotify/value_store.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Slotify
|
2
2
|
class ValueStore
|
3
|
-
include
|
3
|
+
include SymbolInflectionHelper
|
4
4
|
|
5
5
|
def initialize(view_context)
|
6
6
|
@view_context = view_context
|
@@ -13,11 +13,9 @@ module Slotify
|
|
13
13
|
|
14
14
|
def add(slot_name, args = [], options = {}, block = nil)
|
15
15
|
if plural?(slot_name)
|
16
|
-
Array.wrap(args.
|
16
|
+
Array.wrap(args.shift).map { add(singularize(slot_name), [_1, *args], options, block) }
|
17
17
|
else
|
18
|
-
|
19
|
-
@values << Value.new(@view_context, _1, _2, _3, slot_name: singularize(slot_name))
|
20
|
-
end
|
18
|
+
@values << Value.new(@view_context, args, options, block, slot_name: singularize(slot_name))
|
21
19
|
end
|
22
20
|
end
|
23
21
|
|
data/lib/slotify/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slotify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Perkins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -46,15 +46,15 @@ extra_rdoc_files: []
|
|
46
46
|
files:
|
47
47
|
- README.md
|
48
48
|
- lib/slotify.rb
|
49
|
-
- lib/slotify/concerns/inflection_helper.rb
|
50
49
|
- lib/slotify/concerns/slot_compatability.rb
|
50
|
+
- lib/slotify/concerns/symbol_inflection_helper.rb
|
51
51
|
- lib/slotify/error.rb
|
52
52
|
- lib/slotify/extensions/base.rb
|
53
53
|
- lib/slotify/extensions/partial_renderer.rb
|
54
54
|
- lib/slotify/extensions/template.rb
|
55
|
-
- lib/slotify/partial.rb
|
56
55
|
- lib/slotify/services/method_args_resolver.rb
|
57
56
|
- lib/slotify/services/tag_options_merger.rb
|
57
|
+
- lib/slotify/slots.rb
|
58
58
|
- lib/slotify/value.rb
|
59
59
|
- lib/slotify/value_collection.rb
|
60
60
|
- lib/slotify/value_options.rb
|
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: '0'
|
81
81
|
requirements: []
|
82
|
-
rubygems_version: 3.
|
82
|
+
rubygems_version: 3.5.10
|
83
83
|
signing_key:
|
84
84
|
specification_version: 4
|
85
85
|
summary: Superpowered slots for your Rails partials.
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Slotify
|
2
|
-
module InflectionHelper
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
def singular?(str)
|
6
|
-
str = str.to_s
|
7
|
-
str.singularize == str && str.pluralize != str
|
8
|
-
end
|
9
|
-
|
10
|
-
def singularize(sym)
|
11
|
-
sym.to_s.singularize.to_sym
|
12
|
-
end
|
13
|
-
|
14
|
-
def plural?(str)
|
15
|
-
!singular?(str)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|