phlex-slotable 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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