phlex-slotable 0.2.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: '09e928c0d1e4cec5220e01d3e6b06ebc5fef4b50ed8f43bdf3634e89f91d201f'
4
- data.tar.gz: 726cadd0901159f4cdb083d80300658b44b00da06515946f38fc3701b2ea019b
3
+ metadata.gz: 6daa3c251793367884c7260e217d1df50891db3e5e5d07d3510dce8dc9dab868
4
+ data.tar.gz: 95a6c59d0c7571933821a8df89b76542789d16fe222677b7be48af06730bd7f1
5
5
  SHA512:
6
- metadata.gz: 42f10716289705dd92edc63b31637781f490eb02348a6f17911229a5798a31e7a7bc905c990c6b3770cbf5b1bd31110279c509649ac25f16c1b449bc757a4cdf
7
- data.tar.gz: 9c050854b88de83f4a4edbbf2ea8c05cf34938d89c5bb0a8b9c4a056c84363c93582091afa3b6f2a91f393dffcf262a6f8f81a936d229c4edfff2ad779724533
6
+ metadata.gz: 1c04f7d85f3068f064d8b45566de267975cd73d86d5fe7b34114865836b8cdf064cc218f01c86ddc5ffce9b942d39b1ca63f4188af23222e86f4cd6570ad516b
7
+ data.tar.gz: 14ac74deabd99d4af0b9370279bb0bf592d9116426c7037f40cee94c198f2d92a3dd034577218b6aaa0fa118cc7e54869966177ada7564ee96b00ca2d0e78687
data/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
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
+
3
44
  ## [0.2.0] - 2024-02-13
4
45
 
5
46
  - Allow view slots using string as class name
data/README.md CHANGED
@@ -252,7 +252,7 @@ class MyPage < Phlex::HTML
252
252
  end
253
253
  ```
254
254
 
255
- You can access the internal view state within lambda slots.For example:
255
+ You can access the internal view state within lambda slots. For example:
256
256
  ```ruby
257
257
  class BlogComponent < Phlex::HTML
258
258
  include Phlex::Slotable
@@ -273,10 +273,84 @@ class MyPage < Phlex::HTML
273
273
  end
274
274
  ```
275
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
282
+
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
+
276
350
  ## Roadmap
277
351
  - ✅ ~~Accept Strings as view class name~~
278
352
  - ✅ ~~Allow lambda slots~~
279
- - 🕐 Allow polymorphic slots
353
+ - ~~Allow polymorphic slots~~
280
354
 
281
355
  ## Development
282
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.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -9,63 +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 callable.is_a?(Proc)
16
- define_method :"__call_#{slot_name}__", &callable
17
- private :"__call_#{slot_name}__"
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)
18
21
  end
22
+ define_predicate_method(slot_name, many: many)
23
+ define_getter_method(slot_name, many: many)
24
+ end
19
25
 
20
- if many
21
- define_method :"with_#{slot_name}" do |*args, **kwargs, &block|
22
- instance_variable_set(:"@#{slot_name}_slots", []) unless instance_variable_defined?(:"@#{slot_name}_slots")
26
+ private
23
27
 
24
- value = case callable
25
- when nil
26
- block
27
- when String
28
- self.class.const_get(callable).new(*args, **kwargs, &block)
29
- when Proc
30
- -> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) }
31
- else
32
- callable.new(*args, **kwargs, &block)
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)}
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)}
33
42
  end
43
+ RUBY
44
+ end
34
45
 
35
- instance_variable_get(:"@#{slot_name}_slots") << value
36
- 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
37
49
 
38
- define_method :"#{slot_name}_slots?" do
39
- !send(:"#{slot_name}_slots").empty?
40
- end
41
- 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
42
54
 
43
- define_method :"#{slot_name}_slots" do
44
- instance_variable_get(:"@#{slot_name}_slots") || instance_variable_set(:"@#{slot_name}_slots", [])
45
- end
46
- private :"#{slot_name}_slots"
47
- else
48
- define_method :"with_#{slot_name}" do |*args, **kwargs, &block|
49
- value = case callable
50
- when nil
51
- block
52
- when String
53
- self.class.const_get(callable).new(*args, **kwargs, &block)
54
- when Proc
55
- -> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) }
56
- else
57
- 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 ||= []
58
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
59
69
 
60
- instance_variable_set(:"@#{slot_name}_slot", value)
61
- end
70
+ class_eval(getter_method, __FILE__, __LINE__ + 1)
71
+ end
62
72
 
63
- define_method :"#{slot_name}_slot?" do
64
- !instance_variable_get(:"@#{slot_name}_slot").nil?
65
- end
66
- 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
67
85
 
68
- 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))
69
97
  end
70
98
  end
71
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.2.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-13 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