slotify 0.0.1.alpha.0 → 0.0.1
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 +156 -37
- data/lib/slotify/{slotify_helpers.rb → concerns/helpers_concern.rb} +3 -5
- data/lib/slotify/concerns/inflection_helper.rb +18 -0
- data/lib/slotify/error.rb +3 -6
- data/lib/slotify/extensions/base.rb +4 -6
- data/lib/slotify/extensions/partial_renderer.rb +1 -2
- data/lib/slotify/extensions/template.rb +1 -2
- data/lib/slotify/partial.rb +36 -34
- data/lib/slotify/services/method_args_resolver.rb +26 -0
- data/lib/slotify/{entry.rb → value.rb} +8 -8
- data/lib/slotify/value_collection.rb +31 -0
- data/lib/slotify/{entry_options.rb → value_options.rb} +3 -3
- data/lib/slotify/version.rb +1 -1
- data/lib/slotify.rb +5 -3
- metadata +12 -12
- data/lib/slotify/entry_collection.rb +0 -31
- data/lib/slotify/helpers.rb +0 -30
- data/lib/slotify/utils.rb +0 -50
- /data/lib/slotify/{tag_options_merger.rb → services/tag_options_merger.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9401d13b9a845572abccc1cd38362bb6facfb5394568a74345d856abc6db80ea
|
4
|
+
data.tar.gz: bd8c9df4efe6ee8d40d7967f06326b9950d160c824828c11537f5c44f63f200c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1af7661c421919e5bc90069a75781075e27b9f97d1d767148f5ece3abe773dc1b206ea59435e511c7e1b1805a34124b19b4e1d8354fc7cf70e27dfbcd4e7445a
|
7
|
+
data.tar.gz: 2c3f889e7f55d65af6be315c9b013a21ab63b8cee3c9304cf3ebd947b3324c899c0830e5ea420d1aac7bda0d54603fe69d97eb87fdc765c1a8c0f8f70ec9af69
|
data/README.md
CHANGED
@@ -1,28 +1,30 @@
|
|
1
1
|
<img src=".github/assets/slotify_wordmark.svg" width="200">
|
2
2
|
|
3
|
-
#### Superpowered slots for ActionView partials
|
4
3
|
|
5
|
-
|
4
|
+
|
5
|
+
### Superpowered slots for ActionView partials
|
6
|
+
|
7
|
+
---
|
6
8
|
|
7
9
|
## Overview
|
8
10
|
|
9
|
-
Slotify adds an unobtrusive but powerful **content slot API** to ActionView partials.
|
11
|
+
Slotify adds an unobtrusive (but powerful!) **content slot API** to ActionView partials.
|
10
12
|
|
11
13
|
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.
|
12
14
|
|
13
|
-
Slotified partials are
|
15
|
+
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/).
|
14
16
|
|
15
17
|
###
|
16
18
|
|
17
19
|
## Slotify basics
|
18
20
|
|
19
|
-
Slotify slots are defined using a [strict locals](https://guides.rubyonrails.org/action_view_overview.html#strict-locals)-style magic comment at the top of partial templates ([more details here](#defining-slots)).
|
21
|
+
Slotify slots are defined using a **[strict locals](https://guides.rubyonrails.org/action_view_overview.html#strict-locals)-style magic comment** at the top of partial templates ([more details here](#defining-slots)).
|
20
22
|
|
21
23
|
```erb
|
22
24
|
<%# slots: (slot_name: "default value", optional_slot_name: nil, required_slot_name:) -%>
|
23
25
|
```
|
24
26
|
|
25
|
-
Slot content is accessed via standard local variables within the partial. So a simple slot-enabled `article` partial template might look something like this:
|
27
|
+
Slot content is accessed via **standard local variables** within the partial. So a simple, slot-enabled `article` partial template might look something like this:
|
26
28
|
|
27
29
|
```erb
|
28
30
|
<!-- _article.html.erb -->
|
@@ -40,15 +42,13 @@ Slot content is accessed via standard local variables within the partial. So a s
|
|
40
42
|
```
|
41
43
|
|
42
44
|
> [!NOTE]
|
43
|
-
> _The above
|
45
|
+
> _The above should feel familiar to anyone who has partials (and strict locals) in the past. This is just regular partial syntax but with `slots` defined instead of `locals` (don't worry - you can still define locals too!)._
|
44
46
|
|
45
|
-
When the partial is rendered, a special `partial` object is yielded as an argument to the block. Slot content is set by calling the appropriate
|
47
|
+
When the partial is rendered, a special `partial` object is yielded as an argument to the block. Slot content is set by calling the appropriate `#with_<slot_name>` methods on this partial object.
|
46
48
|
|
47
49
|
For example, here our `article` partial is being rendered with content for the `heading` and `body` slots that were defined above:
|
48
50
|
|
49
51
|
```erb
|
50
|
-
<!-- index.html.erb -->
|
51
|
-
|
52
52
|
<%= render "article" do |partial| %>
|
53
53
|
<% partial.with_heading "This is a title" %>
|
54
54
|
<% partial.with_body do %>
|
@@ -61,50 +61,172 @@ For example, here our `article` partial is being rendered with content for the `
|
|
61
61
|
> [!NOTE]
|
62
62
|
> _If you've ever used [ViewComponent](https://viewcomponent.org) then the above code should also feel quite familiar to you - it's pretty much the same syntax used to provide content to [component slots](https://viewcomponent.org/guide/slots.html)._
|
63
63
|
|
64
|
-
|
64
|
+
But this example just scratches the surface of what Slotify slots can do! Read on to learn more (or [jump to a more full-featured example here](#full-example)).
|
65
65
|
|
66
|
-
|
66
|
+
## Usage
|
67
67
|
|
68
|
-
|
68
|
+
<a name="defining-slots" id="defining-slots"></a>
|
69
|
+
### Defining slots
|
69
70
|
|
70
|
-
|
71
|
+
Slots are defined using a [strict locals](https://guides.rubyonrails.org/action_view_overview.html#strict-locals)-style magic comment at the top of the partial template. The `slots:` signature uses the same syntax as standard Ruby method signatures:
|
71
72
|
|
72
|
-
|
73
|
+
```erb
|
74
|
+
<%# slots: (title:, summary: "No summary available", author: nil) -%>
|
75
|
+
```
|
73
76
|
|
74
|
-
|
77
|
+
#### Required slots
|
75
78
|
|
76
|
-
|
79
|
+
Required slots are defined without a default value.
|
80
|
+
If no content is provided for a required slot then a `StrictSlotsError` exception will be raised.
|
77
81
|
|
78
|
-
|
82
|
+
```erb
|
83
|
+
<!-- _required.html.erb -->
|
79
84
|
|
80
|
-
|
85
|
+
<%# slots: (title:) -%>
|
86
|
+
<h1><%= title %></h1>
|
87
|
+
```
|
81
88
|
|
82
|
-
|
89
|
+
```erb
|
90
|
+
<%= render "required" do |partial| %>
|
91
|
+
<!-- ❌ raises an error, no content set for the `title` slot -->
|
92
|
+
<% end %>
|
93
|
+
```
|
83
94
|
|
84
|
-
|
85
|
-
## Defining slots
|
95
|
+
#### Optional slots
|
86
96
|
|
87
|
-
|
97
|
+
If a default value is set then the slot becomes _optional_. If no content is provided when rendering the partial then
|
98
|
+
the default value will be used instead.
|
88
99
|
|
89
|
-
|
100
|
+
```erb
|
101
|
+
<%# slots: (title: "Default title", author: nil) -%>
|
102
|
+
```
|
90
103
|
|
91
|
-
###
|
104
|
+
### Using alongside strict locals
|
92
105
|
|
93
|
-
|
106
|
+
Strict locals can be defined in 'slotified' partial templates in the same way as usual,
|
107
|
+
either above or below the `slots` definition.
|
94
108
|
|
95
|
-
|
109
|
+
```erb
|
110
|
+
<!-- _article.html.erb -->
|
96
111
|
|
97
|
-
|
112
|
+
<%# locals: (title:) -%>
|
113
|
+
<%# slots: (summary: "No summary available") -%>
|
114
|
+
|
115
|
+
<article>
|
116
|
+
<h1><%= title %></h1>
|
117
|
+
<div><%= summary %></div>
|
118
|
+
</article>
|
119
|
+
```
|
120
|
+
|
121
|
+
Locals are provided when rendering the partial in the usual way.
|
122
|
+
|
123
|
+
```erb
|
124
|
+
<%= render "article", title: "Article title here" do |partial| %>
|
125
|
+
<% partial.with_summary do %>
|
126
|
+
<p>Summary content here...</p>
|
127
|
+
<% end %>
|
128
|
+
<% end %>
|
129
|
+
```
|
130
|
+
|
131
|
+
### Slot content and options
|
98
132
|
|
99
|
-
|
133
|
+
Content is passed into slots using dynamically generated `partial#with_<slot_name>` methods.
|
134
|
+
|
135
|
+
Content can be provided as either the **first argument** or **as a block** when calling these methods at render time.
|
136
|
+
The following `#with_title` calls are both equivalent:
|
137
|
+
|
138
|
+
```erb
|
139
|
+
<%= render "example" do |partial| %>
|
140
|
+
<% partial.with_title "Title passed as argument" %>
|
141
|
+
<% partial.with_title do %>
|
142
|
+
Title passed as block content
|
143
|
+
<% end %>
|
144
|
+
<% end %>
|
145
|
+
```
|
146
|
+
|
147
|
+
### Slot types
|
148
|
+
|
149
|
+
There are two types of slots.
|
150
|
+
|
151
|
+
* **Single-value** slots can only be called **once** and return **a single value**.
|
152
|
+
* **Multiple-value** slots can be called **many times** and return **an array of values**.
|
153
|
+
|
154
|
+
#### Single-value slots
|
155
|
+
|
156
|
+
Single-value slots are defined using a **singlular** slot name:
|
157
|
+
|
158
|
+
```erb
|
159
|
+
<%# slots: (item: nil) -%>
|
160
|
+
```
|
161
|
+
|
162
|
+
Single-value slots can be called once (at most)
|
163
|
+
and their corresponding template variable represents a single value:
|
164
|
+
|
165
|
+
```erb
|
166
|
+
<%= render "example" do |partial| %>
|
167
|
+
<% partial.with_item "Item one" %>
|
168
|
+
<% end %>
|
169
|
+
```
|
170
|
+
|
171
|
+
```erb
|
172
|
+
<%# slots: (item: nil) -%>
|
173
|
+
<div>
|
174
|
+
<%= item %> <!-- "Item one" -->
|
175
|
+
</div>
|
176
|
+
```
|
177
|
+
|
178
|
+
> [!WARNING]
|
179
|
+
> Calling a single-value slot more than once when rendering a partial will raise an error:
|
180
|
+
>
|
181
|
+
> ```erb
|
182
|
+
> <%= render "example" do |partial| %>
|
183
|
+
> <% partial.with_item "Item one" %>
|
184
|
+
> <% partial.with_item "Item two" %> # ❌ raises an error!
|
185
|
+
> <% end %>
|
186
|
+
> ```
|
187
|
+
|
188
|
+
#### Multiple-value slots
|
189
|
+
|
190
|
+
Multiple-value slots are defined using a **plural** slot name:
|
191
|
+
|
192
|
+
```erb
|
193
|
+
<%# slots: (items: nil) -%>
|
194
|
+
```
|
195
|
+
|
196
|
+
Multiple-value slots can be called as many times as needed
|
197
|
+
and their corresponding template variable represents an array of values:
|
198
|
+
|
199
|
+
```erb
|
200
|
+
<%= render "example" do |partial| %>
|
201
|
+
<% partial.with_item "Item one" %>
|
202
|
+
<% partial.with_item "Item two" %>
|
203
|
+
<% partial.with_item "Item three" %>
|
204
|
+
<% end %>
|
205
|
+
```
|
206
|
+
|
207
|
+
```erb
|
208
|
+
<%# slots: (items: nil) -%>
|
209
|
+
|
210
|
+
<%= items %> <!-- ["Item one", "Item two", "Item three"] -->
|
211
|
+
|
212
|
+
<ul>
|
213
|
+
<% items.each do |item| %>
|
214
|
+
<li>
|
215
|
+
<% item %>
|
216
|
+
</li>
|
217
|
+
<% end %>
|
218
|
+
</ul>
|
219
|
+
```
|
220
|
+
|
221
|
+
### Passing slot content to helpers
|
100
222
|
|
101
223
|
> _Docs coming soon..._
|
102
224
|
|
103
|
-
###
|
225
|
+
### Rendering slots
|
104
226
|
|
105
|
-
> _Docs coming soon..._
|
227
|
+
> _Docs coming soon..._
|
106
228
|
|
107
|
-
|
229
|
+
### Slot value objects
|
108
230
|
|
109
231
|
> _Docs coming soon..._
|
110
232
|
|
@@ -129,11 +251,8 @@ Slotify was inspired by the excellent [nice_partials gem](https://github.com/bul
|
|
129
251
|
|
130
252
|
`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)!
|
131
253
|
|
132
|
-
<br>
|
133
|
-
|
134
254
|
---
|
135
255
|
|
136
|
-
<br>
|
137
256
|
<a name="full-example" id="full-example"></a>
|
138
257
|
|
139
258
|
## A more full-featured example
|
@@ -149,7 +268,7 @@ Slotify was inspired by the excellent [nice_partials gem](https://github.com/bul
|
|
149
268
|
<%= title %>
|
150
269
|
</h1>
|
151
270
|
|
152
|
-
<p>Example link: <%=
|
271
|
+
<p>Example link: <%= link_to website_link, data: {controller: "external-link"} %></p>
|
153
272
|
|
154
273
|
<%= render lists, title: "Default title" %>
|
155
274
|
|
@@ -175,7 +294,7 @@ Slotify was inspired by the excellent [nice_partials gem](https://github.com/bul
|
|
175
294
|
|
176
295
|
<% if items.any? %>
|
177
296
|
<%= tag.ul class: "list" do %>
|
178
|
-
<%=
|
297
|
+
<%= content_tag :li, items, class: "list-item" %>
|
179
298
|
<% end %>
|
180
299
|
<% end %>
|
181
300
|
```
|
@@ -1,14 +1,12 @@
|
|
1
1
|
module Slotify
|
2
|
-
module
|
3
|
-
def
|
2
|
+
module HelpersConcern
|
3
|
+
def make_compatible_with_slots(*method_names)
|
4
4
|
proxy = Module.new
|
5
5
|
method_names.each do |name|
|
6
6
|
proxy.define_method(name) do |*args, **kwargs, &block|
|
7
7
|
return super(*args, **kwargs, &block) if args.none?
|
8
|
-
results = Utils.with_resolved_args(args, kwargs, block) do
|
9
|
-
super(*_1, **_2.to_h, &_3 || block)
|
10
|
-
end
|
11
8
|
|
9
|
+
results = MethodArgsResolver.call(args, kwargs, block) { super(*_1, **_2, &_3) }
|
12
10
|
results.reduce(ActiveSupport::SafeBuffer.new) { _1 << _2 }
|
13
11
|
end
|
14
12
|
end
|
@@ -0,0 +1,18 @@
|
|
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
|
data/lib/slotify/error.rb
CHANGED
@@ -2,18 +2,15 @@ module Slotify
|
|
2
2
|
class UnknownSlotError < NameError
|
3
3
|
end
|
4
4
|
|
5
|
-
class MissingRequiredSlotError < ArgumentError
|
6
|
-
end
|
7
|
-
|
8
|
-
class MultipleSlotEntriesError < ArgumentError
|
9
|
-
end
|
10
|
-
|
11
5
|
class SlotsAccessError < RuntimeError
|
12
6
|
end
|
13
7
|
|
14
8
|
class UndefinedSlotError < StandardError
|
15
9
|
end
|
16
10
|
|
11
|
+
class MultipleSlotEntriesError < ArgumentError
|
12
|
+
end
|
13
|
+
|
17
14
|
class SlotArgumentError < ArgumentError
|
18
15
|
end
|
19
16
|
|
@@ -1,12 +1,7 @@
|
|
1
1
|
module Slotify
|
2
2
|
module Extensions
|
3
3
|
module Base
|
4
|
-
extend
|
5
|
-
|
6
|
-
slotify_helpers(
|
7
|
-
*ActionView::Helpers::UrlHelper.instance_methods(false),
|
8
|
-
*ActionView::Helpers::TagHelper.instance_methods(false)
|
9
|
-
)
|
4
|
+
extend HelpersConcern
|
10
5
|
|
11
6
|
attr_reader :partial
|
12
7
|
|
@@ -31,6 +26,9 @@ module Slotify
|
|
31
26
|
ensure
|
32
27
|
@partial = inner_partial
|
33
28
|
end
|
29
|
+
|
30
|
+
make_compatible_with_slots :url_for, :link_to, :button_to, :link_to_unless_current,
|
31
|
+
:link_to_unless, :link_to_if, :mail_to, :sms_to, :phone_to, :tag, :content_tag
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
@@ -5,7 +5,6 @@ module Slotify
|
|
5
5
|
return super unless template.strict_slots?
|
6
6
|
|
7
7
|
partial = view.partial
|
8
|
-
|
9
8
|
view.capture_with_outer_partial_access(&block) if block
|
10
9
|
partial.with_strict_slots(template.strict_slots_keys)
|
11
10
|
locals = locals.merge(partial.slot_locals)
|
@@ -32,7 +31,7 @@ module Slotify
|
|
32
31
|
end
|
33
32
|
|
34
33
|
def missing_strict_locals_error?(error)
|
35
|
-
(defined?(ActionView::StrictLocalsError) && error.cause.is_a?(ActionView::StrictLocalsError)) ||
|
34
|
+
error.template && (defined?(ActionView::StrictLocalsError) && error.cause.is_a?(ActionView::StrictLocalsError)) ||
|
36
35
|
(error.cause.is_a?(ArgumentError) && error.cause.message.match(/missing local/))
|
37
36
|
end
|
38
37
|
end
|
@@ -41,9 +41,8 @@ module Slotify
|
|
41
41
|
|
42
42
|
def locals_code
|
43
43
|
return super unless strict_slots?
|
44
|
-
|
45
44
|
strict_slots_keys.each_with_object(+super) do |key, code|
|
46
|
-
code << "#{key} = partial.content_for(:#{key},
|
45
|
+
code << "#{key} = partial.content_for(:#{key}, binding.local_variable_get(:#{key}));"
|
47
46
|
end
|
48
47
|
end
|
49
48
|
end
|
data/lib/slotify/partial.rb
CHANGED
@@ -1,33 +1,33 @@
|
|
1
1
|
module Slotify
|
2
2
|
class Partial
|
3
|
-
include
|
3
|
+
include InflectionHelper
|
4
4
|
|
5
5
|
attr_reader :outer_partial
|
6
6
|
|
7
7
|
def initialize(view_context)
|
8
8
|
@view_context = view_context
|
9
9
|
@outer_partial = view_context.partial
|
10
|
-
@
|
10
|
+
@values = []
|
11
11
|
@strict_slots = nil
|
12
12
|
end
|
13
13
|
|
14
14
|
def content_for(slot_name, fallback_value = nil)
|
15
|
-
raise SlotsAccessError, "slot
|
15
|
+
raise SlotsAccessError, "slot values cannot be accessed from outside the partial" unless slots_defined?
|
16
16
|
raise UnknownSlotError, "unknown slot :#{slot_name}" unless slot_defined?(slot_name)
|
17
17
|
|
18
|
-
|
19
|
-
if
|
20
|
-
|
18
|
+
values = slot_values(slot_name)
|
19
|
+
if values.none? && !fallback_value.nil?
|
20
|
+
values = add_values(slot_name, Array(fallback_value))
|
21
21
|
end
|
22
22
|
|
23
|
-
singular?(slot_name) ?
|
23
|
+
singular?(slot_name) ? values.first : ValueCollection.new(values)
|
24
24
|
end
|
25
25
|
|
26
26
|
def content_for?(slot_name)
|
27
|
-
raise SlotsAccessError, "slot
|
27
|
+
raise SlotsAccessError, "slot values cannot be accessed from outside the partial" unless slots_defined?
|
28
28
|
raise UnknownSlotError, "unknown slot :#{slot_name}" unless slot_defined?(slot_name)
|
29
29
|
|
30
|
-
|
30
|
+
slot_values(slot_name).any?
|
31
31
|
end
|
32
32
|
|
33
33
|
def capture(*args, &block)
|
@@ -43,7 +43,16 @@ module Slotify
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def slot_locals
|
46
|
-
@strict_slots.map
|
46
|
+
pairs = @strict_slots.map do |slot_name|
|
47
|
+
values = slot_values(slot_name)
|
48
|
+
values = singular?(slot_name) ? values&.first : values
|
49
|
+
[slot_name, values]
|
50
|
+
end
|
51
|
+
pairs.filter do |key, value|
|
52
|
+
# keep empty strings as local value but filter out empty arrays
|
53
|
+
# and objects so they don't override any default values set via strict slots.
|
54
|
+
value.is_a?(String) || value&.present?
|
55
|
+
end.to_h
|
47
56
|
end
|
48
57
|
|
49
58
|
def with_strict_slots(strict_slot_names)
|
@@ -51,24 +60,19 @@ module Slotify
|
|
51
60
|
validate_slots!
|
52
61
|
end
|
53
62
|
|
54
|
-
def helpers
|
55
|
-
@helpers || Helpers.new(@view_context)
|
56
|
-
end
|
57
|
-
|
58
63
|
def respond_to_missing?(name, include_private = false)
|
59
|
-
name.start_with?("with_")
|
64
|
+
name.start_with?("with_")
|
60
65
|
end
|
61
66
|
|
62
67
|
def method_missing(name, *args, **options, &block)
|
63
68
|
if name.start_with?("with_")
|
64
69
|
slot_name = name.to_s.delete_prefix("with_")
|
65
70
|
if singular?(slot_name)
|
66
|
-
|
71
|
+
add_value(slot_name, args, options, block)
|
67
72
|
else
|
68
|
-
|
73
|
+
collection = args.first
|
74
|
+
add_values(slot_name, collection, options, block)
|
69
75
|
end
|
70
|
-
else
|
71
|
-
helpers.public_send(name, *args, **options, &block)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
@@ -82,31 +86,29 @@ module Slotify
|
|
82
86
|
slot_name && slots_defined? && @strict_slots.include?(slot_name.to_sym)
|
83
87
|
end
|
84
88
|
|
85
|
-
def
|
86
|
-
@
|
89
|
+
def slot_values(slot_name)
|
90
|
+
@values.filter { _1.slot_name == singularize(slot_name) }
|
87
91
|
end
|
88
92
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
collection.map { add_entry(slot_name, _1, options, block) }
|
93
|
+
def add_values(slot_name, collection, options = {}, block = nil)
|
94
|
+
collection.map { add_value(slot_name, _1, options, block) }
|
95
|
+
rescue NoMethodError
|
96
|
+
raise SlotArgumentError, "expected array to be passed to slot :#{slot_name} (received #{collection.class.name})"
|
95
97
|
end
|
96
98
|
|
97
|
-
def
|
98
|
-
|
99
|
-
@
|
99
|
+
def add_value(slot_name, args = [], options = {}, block = nil)
|
100
|
+
MethodArgsResolver.call(args, options, block) do
|
101
|
+
@values << Value.new(@view_context, singularize(slot_name), _1, _2, _3)
|
100
102
|
end
|
101
103
|
|
102
|
-
@
|
104
|
+
@values.last
|
103
105
|
end
|
104
106
|
|
105
107
|
def validate_slots!
|
106
108
|
return if @strict_slots.nil?
|
107
109
|
|
108
110
|
singular_slots = @strict_slots.map { singularize(_1) }
|
109
|
-
slots_called = @
|
111
|
+
slots_called = @values.map(&:slot_name).uniq
|
110
112
|
undefined_slots = slots_called - singular_slots
|
111
113
|
|
112
114
|
if undefined_slots.any?
|
@@ -114,8 +116,8 @@ module Slotify
|
|
114
116
|
end
|
115
117
|
|
116
118
|
@strict_slots.filter { singular?(_1) }.each do |slot_name|
|
117
|
-
|
118
|
-
raise MultipleSlotEntriesError, "slot :#{slot_name} called #{
|
119
|
+
values = slot_values(slot_name)
|
120
|
+
raise MultipleSlotEntriesError, "slot :#{slot_name} called #{values.size} times (expected 1)" if values.many?
|
119
121
|
end
|
120
122
|
end
|
121
123
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Slotify
|
2
|
+
module MethodArgsResolver
|
3
|
+
class << self
|
4
|
+
def call(args = [], options = {}, block = nil)
|
5
|
+
args = args.is_a?(Array) ? args.clone : [args]
|
6
|
+
value_index = args.index { _1.is_a?(ValueCollection) || _1.is_a?(Value) }
|
7
|
+
if value_index.nil?
|
8
|
+
[yield(args, options, block)]
|
9
|
+
else
|
10
|
+
target = args[value_index]
|
11
|
+
values = target.is_a?(ValueCollection) ? target : [target]
|
12
|
+
values.map do |value|
|
13
|
+
cloned_args = args.clone
|
14
|
+
cloned_args[value_index, 1] = value.args.clone
|
15
|
+
|
16
|
+
yield(
|
17
|
+
cloned_args,
|
18
|
+
TagOptionsMerger.call(options, value.options),
|
19
|
+
value.block || block
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,22 +1,22 @@
|
|
1
1
|
module Slotify
|
2
|
-
class
|
3
|
-
include
|
2
|
+
class Value
|
3
|
+
include InflectionHelper
|
4
4
|
|
5
5
|
attr_reader :slot_name, :args, :block
|
6
6
|
|
7
7
|
delegate :presence, to: :@content
|
8
8
|
|
9
|
-
def initialize(view_context, slot_name, args = [], options = {}, block = nil, partial_path: nil
|
9
|
+
def initialize(view_context, slot_name, args = [], options = {}, block = nil, partial_path: nil)
|
10
10
|
@view_context = view_context
|
11
11
|
@slot_name = slot_name.to_sym
|
12
12
|
@args = args
|
13
|
-
@options = options
|
13
|
+
@options = options.to_h
|
14
14
|
@block = block
|
15
15
|
@partial_path = partial_path
|
16
16
|
end
|
17
17
|
|
18
18
|
def options
|
19
|
-
|
19
|
+
ValueOptions.new(@view_context, @options)
|
20
20
|
end
|
21
21
|
|
22
22
|
def content
|
@@ -52,12 +52,12 @@ module Slotify
|
|
52
52
|
alias_method :to_hash, :to_h
|
53
53
|
|
54
54
|
def with_partial_path(partial_path)
|
55
|
-
|
55
|
+
Value.new(@view_context, @slot_name, @args, options, @block, partial_path:)
|
56
56
|
end
|
57
57
|
|
58
58
|
def with_default_options(default_options)
|
59
|
-
options =
|
60
|
-
|
59
|
+
options = TagOptionsMerger.call(default_options, @options)
|
60
|
+
Value.new(@view_context, @slot_name, @args, options, @block)
|
61
61
|
end
|
62
62
|
|
63
63
|
def respond_to_missing?(name, include_private = false)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Slotify
|
2
|
+
class ValueCollection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
delegate_missing_to :@values
|
6
|
+
|
7
|
+
def initialize(values = [])
|
8
|
+
@values = values.is_a?(Value) ? [values] : values
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_default_options(...)
|
12
|
+
ValueCollection.new(map { _1.with_default_options(...) })
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_partial_path(...)
|
16
|
+
ValueCollection.new(map { _1.with_partial_path(...) })
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@values.reduce(ActiveSupport::SafeBuffer.new) do |buffer, value|
|
21
|
+
buffer << value.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_in(view_context, &block)
|
26
|
+
@values.reduce(ActiveSupport::SafeBuffer.new) do |buffer, value|
|
27
|
+
buffer << value.render_in(view_context, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module Slotify
|
2
|
-
class
|
2
|
+
class ValueOptions < ActiveSupport::OrderedOptions
|
3
3
|
def initialize(view_context, options = {})
|
4
4
|
@view_context = view_context
|
5
5
|
merge!(options)
|
6
6
|
end
|
7
7
|
|
8
8
|
def except(...)
|
9
|
-
|
9
|
+
ValueOptions.new(@view_context, to_h.except(...))
|
10
10
|
end
|
11
11
|
|
12
12
|
def slice(...)
|
13
|
-
|
13
|
+
ValueOptions.new(@view_context, to_h.slice(...))
|
14
14
|
end
|
15
15
|
|
16
16
|
def to_s
|
data/lib/slotify/version.rb
CHANGED
data/lib/slotify.rb
CHANGED
@@ -6,14 +6,16 @@ require_relative "slotify/error"
|
|
6
6
|
loader = Zeitwerk::Loader.for_gem
|
7
7
|
loader.tag = "slotify"
|
8
8
|
loader.push_dir("#{__dir__}/slotify", namespace: Slotify)
|
9
|
+
loader.collapse("#{__dir__}/slotify/concerns")
|
10
|
+
loader.collapse("#{__dir__}/slotify/services")
|
9
11
|
loader.enable_reloading if ENV["RAILS_ENV"] == "development"
|
10
12
|
loader.setup
|
11
13
|
|
12
|
-
module Slotify
|
13
|
-
end
|
14
|
-
|
15
14
|
ActiveSupport.on_load :action_view do
|
16
15
|
prepend Slotify::Extensions::Base
|
17
16
|
ActionView::Template.prepend Slotify::Extensions::Template
|
18
17
|
ActionView::PartialRenderer.prepend Slotify::Extensions::PartialRenderer
|
19
18
|
end
|
19
|
+
|
20
|
+
module Slotify
|
21
|
+
end
|
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.1
|
4
|
+
version: 0.0.1
|
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-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -46,18 +46,18 @@ extra_rdoc_files: []
|
|
46
46
|
files:
|
47
47
|
- README.md
|
48
48
|
- lib/slotify.rb
|
49
|
-
- lib/slotify/
|
50
|
-
- lib/slotify/
|
51
|
-
- lib/slotify/entry_options.rb
|
49
|
+
- lib/slotify/concerns/helpers_concern.rb
|
50
|
+
- lib/slotify/concerns/inflection_helper.rb
|
52
51
|
- lib/slotify/error.rb
|
53
52
|
- lib/slotify/extensions/base.rb
|
54
53
|
- lib/slotify/extensions/partial_renderer.rb
|
55
54
|
- lib/slotify/extensions/template.rb
|
56
|
-
- lib/slotify/helpers.rb
|
57
55
|
- lib/slotify/partial.rb
|
58
|
-
- lib/slotify/
|
59
|
-
- lib/slotify/tag_options_merger.rb
|
60
|
-
- lib/slotify/
|
56
|
+
- lib/slotify/services/method_args_resolver.rb
|
57
|
+
- lib/slotify/services/tag_options_merger.rb
|
58
|
+
- lib/slotify/value.rb
|
59
|
+
- lib/slotify/value_collection.rb
|
60
|
+
- lib/slotify/value_options.rb
|
61
61
|
- lib/slotify/version.rb
|
62
62
|
homepage:
|
63
63
|
licenses:
|
@@ -74,11 +74,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
74
|
version: 3.1.0
|
75
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
|
-
- - "
|
77
|
+
- - ">="
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
79
|
+
version: '0'
|
80
80
|
requirements: []
|
81
|
-
rubygems_version: 3.
|
81
|
+
rubygems_version: 3.5.10
|
82
82
|
signing_key:
|
83
83
|
specification_version: 4
|
84
84
|
summary: Superpowered slots for your Rails partials.
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Slotify
|
2
|
-
class EntryCollection
|
3
|
-
include Enumerable
|
4
|
-
|
5
|
-
delegate_missing_to :@entries
|
6
|
-
|
7
|
-
def initialize(entries = [])
|
8
|
-
@entries = entries.is_a?(Entry) ? [entries] : entries
|
9
|
-
end
|
10
|
-
|
11
|
-
def with_default_options(...)
|
12
|
-
EntryCollection.new(map { _1.with_default_options(...) })
|
13
|
-
end
|
14
|
-
|
15
|
-
def with_partial_path(...)
|
16
|
-
EntryCollection.new(map { _1.with_partial_path(...) })
|
17
|
-
end
|
18
|
-
|
19
|
-
def to_s
|
20
|
-
@entries.reduce(ActiveSupport::SafeBuffer.new) do |buffer, entry|
|
21
|
-
buffer << entry.to_s
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def render_in(view_context, &block)
|
26
|
-
@entries.reduce(ActiveSupport::SafeBuffer.new) do |buffer, entry|
|
27
|
-
buffer << entry.render_in(view_context, &block)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/lib/slotify/helpers.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
module Slotify
|
2
|
-
class Helpers
|
3
|
-
include Utils
|
4
|
-
|
5
|
-
def initialize(view_context)
|
6
|
-
@view_context = view_context
|
7
|
-
end
|
8
|
-
|
9
|
-
def respond_to_missing?(name, include_private = false)
|
10
|
-
@view_context.respond_to?(name) || @view_context.tag.respond_to?(name)
|
11
|
-
end
|
12
|
-
|
13
|
-
def method_missing(name, *args, **options, &block)
|
14
|
-
results = with_resolved_args(args, options, block) do |rargs, roptions, rblock|
|
15
|
-
call_helper(name, *rargs, **roptions.to_h, &rblock)
|
16
|
-
end
|
17
|
-
results.reduce(ActiveSupport::SafeBuffer.new) { _1 << _2 }
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def call_helper(name, ...)
|
23
|
-
if @view_context.respond_to?(name)
|
24
|
-
@view_context.public_send(name, ...)
|
25
|
-
else
|
26
|
-
@view_context.tag.public_send(name, ...)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/slotify/utils.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
module Slotify
|
2
|
-
module Utils
|
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
|
-
|
18
|
-
def to_array(input)
|
19
|
-
input.is_a?(Array) ? input : [input]
|
20
|
-
end
|
21
|
-
|
22
|
-
def merge_tag_options(...)
|
23
|
-
TagOptionsMerger.call(...)
|
24
|
-
end
|
25
|
-
|
26
|
-
def with_resolved_args(args = [], options = {}, block = nil)
|
27
|
-
args = args.is_a?(Array) ? args.clone : [args]
|
28
|
-
entry_index = args.index { _1.is_a?(EntryCollection) || _1.is_a?(Entry) }
|
29
|
-
if entry_index.nil?
|
30
|
-
[yield(args, options, block)]
|
31
|
-
else
|
32
|
-
target = args[entry_index]
|
33
|
-
entries = target.is_a?(EntryCollection) ? target : [target]
|
34
|
-
entries.map do |entry|
|
35
|
-
cloned_args = args.clone
|
36
|
-
cloned_args[entry_index, 1] = entry.args.clone
|
37
|
-
|
38
|
-
yield(
|
39
|
-
cloned_args,
|
40
|
-
merge_tag_options(options, entry.options),
|
41
|
-
entry.block || block
|
42
|
-
)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
module_function :merge_tag_options
|
48
|
-
module_function :with_resolved_args
|
49
|
-
end
|
50
|
-
end
|
File without changes
|