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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b774ed1d3d704f77cbeebc55d117488eb70f52c1a00527dda727d6cb9100d72
4
- data.tar.gz: b8c6ce6b033715c768315671e6d3547925943825a100f5ea67e11d7df7b81ba6
3
+ metadata.gz: 9401d13b9a845572abccc1cd38362bb6facfb5394568a74345d856abc6db80ea
4
+ data.tar.gz: bd8c9df4efe6ee8d40d7967f06326b9950d160c824828c11537f5c44f63f200c
5
5
  SHA512:
6
- metadata.gz: ad4a579d74c471684a40b0d60cd8362325cb529d13a362c73d23f5aa67f7383d8d4c9853ebe3e9e4120a31126c4a0069688c924a38ceb8ae0d857e0ba3935b59
7
- data.tar.gz: c64f9c44d9880d0488acac9bcc0a4882780b1517024ac86c3b73177f287817bf70ea52d036b74516c0e3f7a4681453acdfccda46b1d9b98ca80f011a09857ebf
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 **a great tool for building components** in a Rails app if you want to stay close to _The Rails Way™️_ or just want to avoid the additional overhead and learning curve of libraries like [ViewComponent](https://viewcomponent.org/) or [Phlex](https://www.phlex.fun/).
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 code should feel familiar if you have used partials 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!)._
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 `.with_<slot_name>` methods on this partial object.
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
- The example above just scratches the surface of what Slotify slots can do.
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
- You can [jump to a more full-featured example here](#full-example) or read on to learn more...
66
+ ## Usage
67
67
 
68
- ## Single vs multiple value slots
68
+ <a name="defining-slots" id="defining-slots"></a>
69
+ ### Defining slots
69
70
 
70
- > _Docs coming soon..._
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
- ## Slot arguments and options
73
+ ```erb
74
+ <%# slots: (title:, summary: "No summary available", author: nil) -%>
75
+ ```
73
76
 
74
- > _Docs coming soon..._
77
+ #### Required slots
75
78
 
76
- ## Using helpers with slots
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
- > _Docs coming soon..._
82
+ ```erb
83
+ <!-- _required.html.erb -->
79
84
 
80
- ## Rendering slot contents
85
+ <%# slots: (title:) -%>
86
+ <h1><%= title %></h1>
87
+ ```
81
88
 
82
- > _Docs coming soon..._
89
+ ```erb
90
+ <%= render "required" do |partial| %>
91
+ <!-- ❌ raises an error, no content set for the `title` slot -->
92
+ <% end %>
93
+ ```
83
94
 
84
- <a name="defining-slots" id="defining-slots"></a>
85
- ## Defining slots
95
+ #### Optional slots
86
96
 
87
- 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.
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
- > _Docs coming soon..._
100
+ ```erb
101
+ <%# slots: (title: "Default title", author: nil) -%>
102
+ ```
90
103
 
91
- ### Required slots
104
+ ### Using alongside strict locals
92
105
 
93
- > _Docs coming soon..._
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
- ### Optional slots
109
+ ```erb
110
+ <!-- _article.html.erb -->
96
111
 
97
- > _Docs coming soon..._
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
- ### Setting default values
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
- ### Using alongside strict locals
225
+ ### Rendering slots
104
226
 
105
- > _Docs coming soon..._
227
+ > _Docs coming soon..._
106
228
 
107
- ## Slotify API
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: <%= partial.link_to website_link, data: {controller: "external-link"} %></p>
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
- <%= partial.li items, class: "list-item" %>
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 SlotifyHelpers
3
- def slotify_helpers(*method_names)
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 SlotifyHelpers
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}, #{key});"
45
+ code << "#{key} = partial.content_for(:#{key}, binding.local_variable_get(:#{key}));"
47
46
  end
48
47
  end
49
48
  end
@@ -1,33 +1,33 @@
1
1
  module Slotify
2
2
  class Partial
3
- include Utils
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
- @entries = []
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 content cannot be accessed from outside the partial" unless slots_defined?
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
- entries = slot_entries(slot_name)
19
- if entries.none? && !fallback_value.nil?
20
- entries = add_entries(slot_name, to_array(fallback_value))
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) ? entries.first : EntryCollection.new(entries)
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 content cannot be accessed from outside the partial" unless slots_defined?
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
- slot_entries(slot_name).any?
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 { [_1, content_for(_1).presence] }.to_h.compact
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_") || helpers.respond_to?(name)
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
- add_entry(slot_name, args, options, block)
71
+ add_value(slot_name, args, options, block)
67
72
  else
68
- add_entries(slot_name, args.first, options, block)
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 slot_entries(slot_name)
86
- @entries.filter { _1.slot_name == singularize(slot_name) }
89
+ def slot_values(slot_name)
90
+ @values.filter { _1.slot_name == singularize(slot_name) }
87
91
  end
88
92
 
89
- def add_entries(slot_name, collection, options = {}, block = nil)
90
- unless collection.respond_to?(:each)
91
- raise ArgumentError, "expected array to be passed to slot :#{slot_name} (received #{collection.class.name})"
92
- end
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 add_entry(slot_name, args = [], options = {}, block = nil)
98
- with_resolved_args(args, options, block) do |rargs, roptions, rblock|
99
- @entries << Entry.new(@view_context, singularize(slot_name), rargs, roptions, rblock)
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
- @entries.last
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 = @entries.map(&:slot_name).uniq
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
- entries = slot_entries(slot_name)
118
- raise MultipleSlotEntriesError, "slot :#{slot_name} called #{entries.size} times (expected 1)" if entries.many?
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 Entry
3
- include Utils
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, **kwargs)
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
- EntryOptions.new(@view_context, @options)
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
- Entry.new(@view_context, @slot_name, @args, options.to_h, @block, partial_path:)
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 = merge_tag_options(default_options, @options)
60
- Entry.new(@view_context, @slot_name, @args, options.to_h, @block)
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 EntryOptions < ActiveSupport::OrderedOptions
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
- EntryOptions.new(@view_context, to_h.except(...))
9
+ ValueOptions.new(@view_context, to_h.except(...))
10
10
  end
11
11
 
12
12
  def slice(...)
13
- EntryOptions.new(@view_context, to_h.slice(...))
13
+ ValueOptions.new(@view_context, to_h.slice(...))
14
14
  end
15
15
 
16
16
  def to_s
@@ -1,3 +1,3 @@
1
1
  module Slotify
2
- VERSION = "0.0.1.alpha.0"
2
+ VERSION = "0.0.1"
3
3
  end
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.alpha.0
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-04 00:00:00.000000000 Z
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/entry.rb
50
- - lib/slotify/entry_collection.rb
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/slotify_helpers.rb
59
- - lib/slotify/tag_options_merger.rb
60
- - lib/slotify/utils.rb
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: 1.3.1
79
+ version: '0'
80
80
  requirements: []
81
- rubygems_version: 3.3.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
@@ -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