phlex-slotable 0.2.0 → 0.3.1

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: 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