phlex-slotable 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1aa68f30783948c69ae6e3a9018e3e0fe828b8d1e3392208bbd926dead27761
4
- data.tar.gz: f60047a296679c099258024d72e23d13a252bd4f5fd38576769716218785d962
3
+ metadata.gz: 6daa3c251793367884c7260e217d1df50891db3e5e5d07d3510dce8dc9dab868
4
+ data.tar.gz: 95a6c59d0c7571933821a8df89b76542789d16fe222677b7be48af06730bd7f1
5
5
  SHA512:
6
- metadata.gz: cf5b19581bab8cb2ba50e91e44f9489b40cbdae7c0873310a98637be51cbac6212e8621a95f67bd8d5d1ad1286533b66023847c4ac4eacbe2a7f867635b2bfaa
7
- data.tar.gz: d1e6a2cb591541105802d4338b857baf99ef5817aacbdd644f5a6ad3e77a4ac60669c8b5f0d9f7bdae63565e05f6fe7cbe6852e29aac88aa31366def63a5ac01
6
+ metadata.gz: 1c04f7d85f3068f064d8b45566de267975cd73d86d5fe7b34114865836b8cdf064cc218f01c86ddc5ffce9b942d39b1ca63f4188af23222e86f4cd6570ad516b
7
+ data.tar.gz: 14ac74deabd99d4af0b9370279bb0bf592d9116426c7037f40cee94c198f2d92a3dd034577218b6aaa0fa118cc7e54869966177ada7564ee96b00ca2d0e78687
data/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2024-02-14
4
+
5
+ - Match Slotable peformance with DeferredRender
6
+ ```
7
+ ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]
8
+ Warming up --------------------------------------
9
+ Deferred 26.779k i/100ms
10
+ Slotable 0.2.0 21.636k i/100ms
11
+ Slotable 0.3.0 27.013k i/100ms
12
+ Calculating -------------------------------------
13
+ Deferred 267.884k (± 0.6%) i/s - 1.366M in 5.098391s
14
+ Slotable 0.2.0 216.193k (± 0.4%) i/s - 1.082M in 5.003961s
15
+ Slotable 0.3.0 270.082k (± 0.5%) i/s - 1.351M in 5.001001s
16
+ ```
17
+ *stephannv*
18
+
19
+ - Allow polymorphic slots
20
+ ```ruby
21
+ class CardComponent < Phlex::HTML
22
+ include Phlex::Slotable
23
+
24
+ slot :avatar, types: { icon: IconComponent, image: ImageComponent }
25
+
26
+ def template
27
+ if avatar_slot?
28
+ render avatar_slot
29
+ end
30
+ end
31
+ end
32
+
33
+ render CardComponent.new do |card|
34
+ if user
35
+ card.with_image_avatar(src: user.image_url)
36
+ else
37
+ card.with_icon_avatar(name: :user)
38
+ end
39
+ end
40
+ ```
41
+
42
+ *stephannv*
43
+
44
+ ## [0.2.0] - 2024-02-13
45
+
46
+ - Allow view slots using string as class name
47
+
48
+ *stephannv*
49
+
50
+ - Allow lambda slots
51
+
52
+ *stephannv*
53
+
3
54
  ## [0.1.0] - 2024-02-12
55
+ - Add single and multi slots
56
+
57
+ *stephannv*
58
+
59
+ - Add generic slots
60
+
61
+ *stephannv*
62
+
63
+ - Add view slots
4
64
 
5
- - Initial release
65
+ *stephannv*
data/README.md CHANGED
@@ -1,25 +1,28 @@
1
+ > [!WARNING]
2
+ > Please note that Phlex::Slotable is currently under development and may undergo changes to its API before reaching the stable release (1.0.0). As a result, there may be breaking changes that affect its usage.
3
+
1
4
  # Phlex::Slotable
2
5
 
3
- Phlex::Slotable enables slots feature to Phlex views. Inspired by ViewComponent.
6
+ Phlex::Slotable enables slots feature to [Phlex](https://www.phlex.fun/) views. Inspired by ViewComponent.
4
7
 
5
8
  ## Installation
6
9
 
7
10
  Install the gem and add to the application's Gemfile by executing:
8
11
 
9
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
12
+ $ bundle add phlex-slotable
10
13
 
11
14
  If bundler is not being used to manage dependencies, install the gem by executing:
12
15
 
13
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
16
+ $ gem install phlex-slotable
14
17
 
15
18
  ## Usage
16
19
 
17
20
  #### Basic
18
21
 
19
- Include `Phlex::Slotable` to your Phlex view and use `slot` class method to define slots.
22
+ To incorportate slots into your Phlex views, include `Phlex::Slotable` and utilize `slot` class method to define them.
20
23
 
21
- - `slot :slot_name` defines a single slot that will be rendered at most once per component
22
- - `slot :slot_name, many: true` defines a slot that can be rendered multiple times per component
24
+ - `slot :slot_name` declaration establishes a single slot intended for rendering once within a view
25
+ - `slot :slot_name, many: true` denotes a slot capable of being rendered multiple times within a view
23
26
 
24
27
  ```ruby
25
28
  class BlogComponent < Phlex::HTML
@@ -32,9 +35,9 @@ class BlogComponent < Phlex::HTML
32
35
  end
33
36
  ```
34
37
 
35
- To render a single slot, render `{slot_name}_slot`, eg. `render header_slot`.
38
+ To render a single slot, utilize the `{slot_name}_slot` method. For example, you can render the `header_slot` using `render header_slot`.
36
39
 
37
- To render a multi slot, iterate over `{slot_name}_slots` and render each element, eg. `post_slots.each { |s| render s }`
40
+ For multi-slot rendering, iterate over the `{slot_name}_slots` collection and and render each slot individually, eg. `post_slots.each { |s| render s }`.
38
41
 
39
42
  ```ruby
40
43
  class BlogComponent < Phlex::HTML
@@ -44,11 +47,11 @@ class BlogComponent < Phlex::HTML
44
47
  slot :post, many: true
45
48
 
46
49
  def template
47
- div id: "header" do
50
+ div id: "header" do
48
51
  render header_slot
49
52
  end
50
53
 
51
- div id: "main" do
54
+ div id: "main" do
52
55
  post_slots.each do |slot|
53
56
  p { render slot }
54
57
  end
@@ -59,7 +62,7 @@ class BlogComponent < Phlex::HTML
59
62
  end
60
63
  ```
61
64
 
62
- To set slot content, you need to use `with_{slot_name}` when rendering the view:
65
+ When setting slot content, ensure to utilize the `with_{slot_name}` method while rendering the view:
63
66
 
64
67
  ```ruby
65
68
  class MyPage < Phlex::HTML
@@ -96,7 +99,7 @@ This will output:
96
99
 
97
100
  #### Predicate methods
98
101
 
99
- You can check if a slot has been passed to the component using `{slot_name}_slot?` when it is a single slot, or `{slot_name}_slots?` when it is a multi slot.
102
+ You can verify whether a slot has been provided to the view using `{slot_name}_slot?` for single slots or `{slot_name}_slots?` when for multi-slots.
100
103
 
101
104
  ```ruby
102
105
  class BlogComponent < Phlex::HTML
@@ -106,13 +109,13 @@ class BlogComponent < Phlex::HTML
106
109
  slot :post, many: true
107
110
 
108
111
  def template
109
- if header_slot?
112
+ if header_slot?
110
113
  div id: "header" do
111
114
  render header_slot
112
115
  end
113
116
  end
114
117
 
115
- div id: "main" do
118
+ div id: "main" do
116
119
  if post_slots?
117
120
  post_slots.each do |slot|
118
121
  p { render slot }
@@ -129,11 +132,11 @@ end
129
132
 
130
133
  #### View slot
131
134
 
132
- Slots can render other views, you just need to pass the view class name to `slot` method.
135
+ Slots have the capability to render other views, Simply pass the view class name to the `slot` method.
133
136
 
134
137
  ```ruby
135
138
  class HeaderComponent < Phlex::HTML
136
- def initialize(size:)
139
+ def initialize(size:)
137
140
  @size = size
138
141
  end
139
142
 
@@ -143,7 +146,7 @@ class HeaderComponent < Phlex::HTML
143
146
  end
144
147
 
145
148
  class PostComponent < Phlex::HTML
146
- def initialize(featured:)
149
+ def initialize(featured:)
147
150
  @featured = featured
148
151
  end
149
152
 
@@ -159,13 +162,13 @@ class BlogComponent < Phlex::HTML
159
162
  slot :post, PostComponent, many: true
160
163
 
161
164
  def template
162
- if header_slot?
165
+ if header_slot?
163
166
  div id: "header" do
164
167
  render header_slot
165
168
  end
166
169
  end
167
170
 
168
- div id: "main" do
171
+ div id: "main" do
169
172
  if post_slots?
170
173
  post_slots.each { render slot }
171
174
 
@@ -207,11 +210,147 @@ The output:
207
210
  </div>
208
211
  ```
209
212
 
210
- ## Roadmap
213
+ You can pass the class name as a string for cases where the class isn't evaluated yet, such as with inner classes. For example:
214
+ ```ruby
215
+ class BlogComponent < Phlex::HTML
216
+ include Phlex::Slotable
217
+
218
+ # This will not work
219
+ slot :header, HeaderComponent # uninitialized constant BlogComponent::HeaderComponent
220
+ # You should do this
221
+ slot :header, "HeaderComponent"
222
+
223
+ private
224
+
225
+ class HeaderComponent < Phlex::HTML
226
+ # ...
227
+ end
228
+ end
229
+ ```
230
+
231
+ #### Lambda slots
232
+ Lambda slots are valuable when you prefer not to create another component for straightforward structures or when you need to render another view with specific parameters
233
+ ```ruby
234
+
235
+ class BlogComponent < Phlex::HTML
236
+ include Phlex::Slotable
237
+
238
+ slot :header, ->(size:, &content) { render HeaderComponent.new(size: size, color: "blue"), &content }
239
+ slot :post, ->(featured:, &content) { span(class: featured ? "featured" : nil, &content) }, many: true
240
+ end
241
+
242
+ class MyPage < Phlex::HTML
243
+ def template
244
+ render BlogComponent.new do |blog|
245
+ blog.with_header(size: :lg) { "Hello World!" }
246
+
247
+ blog.with_post(featured: true) { "Post A" }
248
+ blog.with_post { "Post B" }
249
+ blog.with_post { "Post C" }
250
+ end
251
+ end
252
+ end
253
+ ```
254
+
255
+ You can access the internal view state within lambda slots. For example:
256
+ ```ruby
257
+ class BlogComponent < Phlex::HTML
258
+ include Phlex::Slotable
259
+
260
+ slot :header, ->(size:, &content) { render HeaderComponent.new(size: size, color: @header_color), &content }
261
+
262
+ def initialize(header_color:)
263
+ @header_color = header_color
264
+ end
265
+ end
266
+
267
+ class MyPage < Phlex::HTML
268
+ def template
269
+ render BlogComponent.new(header_color: "red") do |blog|
270
+ blog.with_header(size: :lg) { "Hello World!" }
271
+ end
272
+ end
273
+ end
274
+ ```
275
+
276
+ #### Polymorphic slots
277
+ Polymorphic slots can render one of several possible slots, allowing for flexibility in component content. This feature is particularly useful when you require a fixed structure but need to accommodate different types of content. To implement this, simply pass a types hash containing the types along with corresponding slot definitions.
278
+
279
+ ```ruby
280
+ class CardComponent < Phlex::HTML
281
+ include Phlex::Slotable
211
282
 
212
- [] Accepts Strings as view class name
213
- [] Allow lambda slots
214
- [] Allow polymorphic slots
283
+ slot :avatar, types: { icon: IconComponent, image: ImageComponent }
284
+
285
+ def template
286
+ if avatar_slot?
287
+ figure id: "avatar" do
288
+ render avatar_slot
289
+ end
290
+ end
291
+ end
292
+ end
293
+ ```
294
+
295
+ This allows you to set the icon slot using `with_icon_avatar` or the image slot using `with_image_avatar`:
296
+ ```ruby
297
+ class UserCardComponent < Phlex::HTML
298
+ def initialize(user:)
299
+ @user = user
300
+ end
301
+
302
+ def template
303
+ render CardComponent.new do |card|
304
+ if @user.image?
305
+ card.with_image_avatar(src: @user.image)
306
+ else
307
+ card.with_icon_avatar(name: :user)
308
+ end
309
+ end
310
+ end
311
+ end
312
+ ```
313
+
314
+ Please note that you can still utilize the other slot definition APIs:
315
+ ```ruby
316
+ class CardComponent < Phlex::HTML
317
+ include Phlex::Slotable
318
+
319
+ slot :avatar, types: {
320
+ icon: IconComponent,
321
+ image: "ImageComponent",
322
+ text: ->(size:, &content) { span(class: "text-#{size}", &content) }
323
+ }, many: true
324
+
325
+ def template
326
+ if avatar_slots?
327
+ avatar_slots.each do |slot|
328
+ render slot
329
+ end
330
+ end
331
+
332
+ span { "Count: #{avatar_slots.size}" }
333
+ end
334
+
335
+ ...
336
+ end
337
+
338
+ class UsersCardComponent < Phlex::HTML
339
+ def template
340
+ render CardComponent.new do |card|
341
+ card.with_image_avatar(src: @user.image)
342
+ card.with_icon_avatar(name: :user)
343
+ card.with_text_avatar(size: :lg) { "SV" }
344
+ end
345
+ end
346
+ end
347
+ ```
348
+
349
+
350
+ ## Roadmap
351
+ - ✅ ~~Accept Strings as view class name~~
352
+ - ✅ ~~Allow lambda slots~~
353
+ - ✅ ~~Allow polymorphic slots~~
215
354
 
216
355
  ## Development
217
356
 
data/benchmark/main.rb ADDED
@@ -0,0 +1,105 @@
1
+ require "benchmark"
2
+ require "benchmark/ips"
3
+ require_relative "../lib/phlex/slotable"
4
+
5
+ class DeferredList < Phlex::HTML
6
+ include Phlex::DeferredRender
7
+
8
+ def initialize
9
+ @items = []
10
+ end
11
+
12
+ def template
13
+ if @header
14
+ h1(class: "header", &@header)
15
+ end
16
+
17
+ ul do
18
+ @items.each do |item|
19
+ li { render(item) }
20
+ end
21
+ end
22
+ end
23
+
24
+ def header(&block)
25
+ @header = block
26
+ end
27
+
28
+ def with_item(&content)
29
+ @items << content
30
+ end
31
+ end
32
+
33
+ class SlotableList < Phlex::HTML
34
+ include Phlex::Slotable
35
+
36
+ slot :header
37
+ slot :item, many: true
38
+
39
+ def template
40
+ if header_slot
41
+ h1(class: "header", &header_slot)
42
+ end
43
+
44
+ ul do
45
+ item_slots.each do |slot|
46
+ li { render(slot) }
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ class DeferredListExample < Phlex::HTML
53
+ def template
54
+ render DeferredList.new do |list|
55
+ list.header do
56
+ "Header"
57
+ end
58
+
59
+ list.with_item do
60
+ "One"
61
+ end
62
+
63
+ list.with_item do
64
+ "two"
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class SlotableListExample < Phlex::HTML
71
+ def template
72
+ render SlotableList.new do |list|
73
+ list.with_header do
74
+ "Header"
75
+ end
76
+
77
+ list.with_item do
78
+ "One"
79
+ end
80
+
81
+ list.with_item do
82
+ "two"
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ puts RUBY_DESCRIPTION
89
+
90
+ deferred_list = DeferredListExample.new.call
91
+ slotable_list = SlotableListExample.new.call
92
+
93
+ raise unless deferred_list == slotable_list
94
+
95
+ Benchmark.bmbm do |x|
96
+ x.report("Deferred") { 1_000_000.times { DeferredListExample.new.call } }
97
+ x.report("Slotable") { 1_000_000.times { SlotableListExample.new.call } }
98
+ end
99
+
100
+ puts
101
+
102
+ Benchmark.ips do |x|
103
+ x.report("Deferred") { DeferredListExample.new.call }
104
+ x.report("Slotable") { SlotableListExample.new.call }
105
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlex
4
4
  module Slotable
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -9,50 +9,91 @@ module Phlex
9
9
  end
10
10
 
11
11
  module ClassMethods
12
- def slot(slot_name, callable = nil, many: false)
12
+ def slot(slot_name, callable = nil, types: nil, many: false)
13
13
  include Phlex::DeferredRender
14
14
 
15
- if many
16
- define_method :"with_#{slot_name}" do |*args, **kwargs, &block|
17
- instance_variable_set(:"@#{slot_name}_slots", []) unless instance_variable_defined?(:"@#{slot_name}_slots")
15
+ if types
16
+ types.each do |type, callable|
17
+ define_setter_method(slot_name, callable, many: many, type: type)
18
+ end
19
+ else
20
+ define_setter_method(slot_name, callable, many: many)
21
+ end
22
+ define_predicate_method(slot_name, many: many)
23
+ define_getter_method(slot_name, many: many)
24
+ end
18
25
 
19
- value = case callable
20
- when nil
21
- block
22
- else
23
- callable.new(*args, **kwargs, &block)
26
+ private
27
+
28
+ def define_setter_method(slot_name, callable, many:, type: nil)
29
+ slot_name_with_type = type ? "#{type}_#{slot_name}" : slot_name
30
+
31
+ setter_method = if many
32
+ <<-RUBY
33
+ def with_#{slot_name_with_type}(*args, **kwargs, &block)
34
+ @#{slot_name}_slots ||= []
35
+ @#{slot_name}_slots << #{callable_value(slot_name_with_type, callable)}
24
36
  end
37
+ RUBY
38
+ else
39
+ <<-RUBY
40
+ def with_#{slot_name_with_type}(*args, **kwargs, &block)
41
+ @#{slot_name}_slot = #{callable_value(slot_name_with_type, callable)}
42
+ end
43
+ RUBY
44
+ end
25
45
 
26
- instance_variable_get(:"@#{slot_name}_slots") << value
27
- end
46
+ class_eval(setter_method, __FILE__, __LINE__ + 1)
47
+ define_lambda_method(slot_name_with_type, callable) if callable.is_a?(Proc)
48
+ end
28
49
 
29
- define_method :"#{slot_name}_slots?" do
30
- !send(:"#{slot_name}_slots").empty?
31
- end
32
- private :"#{slot_name}_slots?"
50
+ def define_lambda_method(slot_name, callable)
51
+ define_method :"__call_#{slot_name}__", &callable
52
+ private :"__call_#{slot_name}__"
53
+ end
33
54
 
34
- define_method :"#{slot_name}_slots" do
35
- instance_variable_get(:"@#{slot_name}_slots") || instance_variable_set(:"@#{slot_name}_slots", [])
36
- end
37
- private :"#{slot_name}_slots"
38
- else
39
- define_method :"with_#{slot_name}" do |*args, **kwargs, &block|
40
- value = case callable
41
- when nil
42
- block
43
- else
44
- callable.new(*args, **kwargs, &block)
55
+ def define_getter_method(slot_name, many:)
56
+ getter_method = if many
57
+ <<-RUBY
58
+ def #{slot_name}_slots
59
+ @#{slot_name}_slots ||= []
45
60
  end
61
+ private :#{slot_name}_slots
62
+ RUBY
63
+ else
64
+ <<-RUBY
65
+ def #{slot_name}_slot = @#{slot_name}_slot
66
+ private :#{slot_name}_slot
67
+ RUBY
68
+ end
46
69
 
47
- instance_variable_set(:"@#{slot_name}_slot", value)
48
- end
70
+ class_eval(getter_method, __FILE__, __LINE__ + 1)
71
+ end
49
72
 
50
- define_method :"#{slot_name}_slot?" do
51
- !instance_variable_get(:"@#{slot_name}_slot").nil?
52
- end
53
- private :"#{slot_name}_slot?"
73
+ def define_predicate_method(slot_name, many:)
74
+ predicate_method = if many
75
+ <<-RUBY
76
+ def #{slot_name}_slots? = #{slot_name}_slots.any?
77
+ private :#{slot_name}_slots?
78
+ RUBY
79
+ else
80
+ <<-RUBY
81
+ def #{slot_name}_slot? = !#{slot_name}_slot.nil?
82
+ private :#{slot_name}_slot?
83
+ RUBY
84
+ end
54
85
 
55
- attr_reader :"#{slot_name}_slot"
86
+ class_eval(predicate_method, __FILE__, __LINE__ + 1)
87
+ end
88
+
89
+ def callable_value(slot_name, callable)
90
+ case callable
91
+ when nil
92
+ %(block)
93
+ when Proc
94
+ %(-> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) })
95
+ else
96
+ %(#{callable}.new(*args, **kwargs, &block))
56
97
  end
57
98
  end
58
99
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-slotable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stephann
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
11
+ date: 2024-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -37,6 +37,7 @@ files:
37
37
  - LICENSE.txt
38
38
  - README.md
39
39
  - Rakefile
40
+ - benchmark/main.rb
40
41
  - lib/phlex/slotable.rb
41
42
  - lib/phlex/slotable/version.rb
42
43
  homepage: https://github.com/stephannv/phlex-slotable