phlex-slotable 0.2.0 → 0.3.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: '09e928c0d1e4cec5220e01d3e6b06ebc5fef4b50ed8f43bdf3634e89f91d201f'
4
- data.tar.gz: 726cadd0901159f4cdb083d80300658b44b00da06515946f38fc3701b2ea019b
3
+ metadata.gz: 7ed2552d628d6851db0da87a5105818d55b5a28bda04d7b5dd9dc0f6fe7357a5
4
+ data.tar.gz: dd6b66cb419fae64ee1ea963670735a3773a6d1c26bf18c2cc1be01ad43e559b
5
5
  SHA512:
6
- metadata.gz: 42f10716289705dd92edc63b31637781f490eb02348a6f17911229a5798a31e7a7bc905c990c6b3770cbf5b1bd31110279c509649ac25f16c1b449bc757a4cdf
7
- data.tar.gz: 9c050854b88de83f4a4edbbf2ea8c05cf34938d89c5bb0a8b9c4a056c84363c93582091afa3b6f2a91f393dffcf262a6f8f81a936d229c4edfff2ad779724533
6
+ metadata.gz: 0aafc0d75f3907534255f8ffdc8800e8541373884c5e149afc12dfd5f1e811644c6f037ec32640501f8faec83dace3ea6368c6a17c51fe88b2e27557813f4b70
7
+ data.tar.gz: a5d11b19a175d1228c0ec7561986dbec0f0a976829b0b0300d599c15d32e801d9afcd4fd2dabdd3a180efccf2f12bde9673ee84744c712fa9aca7b77f3a8e412
data/.standard.yml CHANGED
@@ -1,3 +1,5 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/standardrb/standard
3
- ruby_version: 3.0
3
+ ruby_version: 3.3
4
+ format: progress
5
+ parallel: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2024-02-14
4
+ - Support Ruby 2.7
5
+
6
+ *stephannv*
7
+
8
+ ## [0.3.0] - 2024-02-14
9
+
10
+ - Match Slotable peformance with DeferredRender
11
+ ```
12
+ ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]
13
+ Warming up --------------------------------------
14
+ Deferred 26.779k i/100ms
15
+ Slotable 0.2.0 21.636k i/100ms
16
+ Slotable 0.3.0 27.013k i/100ms
17
+ Calculating -------------------------------------
18
+ Deferred 267.884k (± 0.6%) i/s - 1.366M in 5.098391s
19
+ Slotable 0.2.0 216.193k (± 0.4%) i/s - 1.082M in 5.003961s
20
+ Slotable 0.3.0 270.082k (± 0.5%) i/s - 1.351M in 5.001001s
21
+ ```
22
+ *stephannv*
23
+
24
+ - Allow polymorphic slots
25
+ ```ruby
26
+ class CardComponent < Phlex::HTML
27
+ include Phlex::Slotable
28
+
29
+ slot :avatar, types: { icon: IconComponent, image: ImageComponent }
30
+
31
+ def template
32
+ if avatar_slot?
33
+ render avatar_slot
34
+ end
35
+ end
36
+ end
37
+
38
+ render CardComponent.new do |card|
39
+ if user
40
+ card.with_image_avatar(src: user.image_url)
41
+ else
42
+ card.with_icon_avatar(name: :user)
43
+ end
44
+ end
45
+ ```
46
+
47
+ *stephannv*
48
+
3
49
  ## [0.2.0] - 2024-02-13
4
50
 
5
51
  - Allow view slots using string as class name
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
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
3
 
4
4
  # Phlex::Slotable
5
+ [![CI](https://github.com/stephannv/phlex-slotable/actions/workflows/main.yml/badge.svg)](https://github.com/stephannv/phlex-slotable/actions/workflows/main.yml)
5
6
 
6
7
  Phlex::Slotable enables slots feature to [Phlex](https://www.phlex.fun/) views. Inspired by ViewComponent.
7
8
 
@@ -252,7 +253,7 @@ class MyPage < Phlex::HTML
252
253
  end
253
254
  ```
254
255
 
255
- You can access the internal view state within lambda slots.For example:
256
+ You can access the internal view state within lambda slots. For example:
256
257
  ```ruby
257
258
  class BlogComponent < Phlex::HTML
258
259
  include Phlex::Slotable
@@ -273,10 +274,84 @@ class MyPage < Phlex::HTML
273
274
  end
274
275
  ```
275
276
 
277
+ #### Polymorphic slots
278
+ 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.
279
+
280
+ ```ruby
281
+ class CardComponent < Phlex::HTML
282
+ include Phlex::Slotable
283
+
284
+ slot :avatar, types: { icon: IconComponent, image: ImageComponent }
285
+
286
+ def template
287
+ if avatar_slot?
288
+ figure id: "avatar" do
289
+ render avatar_slot
290
+ end
291
+ end
292
+ end
293
+ end
294
+ ```
295
+
296
+ This allows you to set the icon slot using `with_icon_avatar` or the image slot using `with_image_avatar`:
297
+ ```ruby
298
+ class UserCardComponent < Phlex::HTML
299
+ def initialize(user:)
300
+ @user = user
301
+ end
302
+
303
+ def template
304
+ render CardComponent.new do |card|
305
+ if @user.image?
306
+ card.with_image_avatar(src: @user.image)
307
+ else
308
+ card.with_icon_avatar(name: :user)
309
+ end
310
+ end
311
+ end
312
+ end
313
+ ```
314
+
315
+ Please note that you can still utilize the other slot definition APIs:
316
+ ```ruby
317
+ class CardComponent < Phlex::HTML
318
+ include Phlex::Slotable
319
+
320
+ slot :avatar, types: {
321
+ icon: IconComponent,
322
+ image: "ImageComponent",
323
+ text: ->(size:, &content) { span(class: "text-#{size}", &content) }
324
+ }, many: true
325
+
326
+ def template
327
+ if avatar_slots?
328
+ avatar_slots.each do |slot|
329
+ render slot
330
+ end
331
+ end
332
+
333
+ span { "Count: #{avatar_slots.size}" }
334
+ end
335
+
336
+ ...
337
+ end
338
+
339
+ class UsersCardComponent < Phlex::HTML
340
+ def template
341
+ render CardComponent.new do |card|
342
+ card.with_image_avatar(src: @user.image)
343
+ card.with_icon_avatar(name: :user)
344
+ card.with_text_avatar(size: :lg) { "SV" }
345
+ end
346
+ end
347
+ end
348
+ ```
349
+
350
+
276
351
  ## Roadmap
277
352
  - ✅ ~~Accept Strings as view class name~~
278
353
  - ✅ ~~Allow lambda slots~~
279
- - 🕐 Allow polymorphic slots
354
+ - ~~Allow polymorphic slots~~
280
355
 
281
356
  ## Development
282
357
 
data/benchmark/main.rb ADDED
@@ -0,0 +1,106 @@
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
+ x.compare!
106
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlex
4
4
  module Slotable
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
@@ -9,63 +9,97 @@ 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"
55
+ def define_getter_method(slot_name, many:)
56
+ getter_method = if many
57
+ <<-RUBY
58
+ def #{slot_name}_slots
59
+ @#{slot_name}_slots ||= []
60
+ end
61
+ private :#{slot_name}_slots
62
+ RUBY
47
63
  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)
64
+ <<-RUBY
65
+ def #{slot_name}_slot
66
+ @#{slot_name}_slot
58
67
  end
68
+ private :#{slot_name}_slot
69
+ RUBY
70
+ end
59
71
 
60
- instance_variable_set(:"@#{slot_name}_slot", value)
61
- end
72
+ class_eval(getter_method, __FILE__, __LINE__ + 1)
73
+ end
62
74
 
63
- define_method :"#{slot_name}_slot?" do
64
- !instance_variable_get(:"@#{slot_name}_slot").nil?
65
- end
66
- private :"#{slot_name}_slot?"
75
+ def define_predicate_method(slot_name, many:)
76
+ predicate_method = if many
77
+ <<-RUBY
78
+ def #{slot_name}_slots?
79
+ #{slot_name}_slots.any?
80
+ end
81
+ private :#{slot_name}_slots?
82
+ RUBY
83
+ else
84
+ <<-RUBY
85
+ def #{slot_name}_slot?
86
+ !#{slot_name}_slot.nil?
87
+ end
88
+ private :#{slot_name}_slot?
89
+ RUBY
90
+ end
67
91
 
68
- attr_reader :"#{slot_name}_slot"
92
+ class_eval(predicate_method, __FILE__, __LINE__ + 1)
93
+ end
94
+
95
+ def callable_value(slot_name, callable)
96
+ case callable
97
+ when nil
98
+ %(block)
99
+ when Proc
100
+ %(-> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) })
101
+ else
102
+ %(#{callable}.new(*args, **kwargs, &block))
69
103
  end
70
104
  end
71
105
  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.1
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