line-message-builder 0.8.0 → 0.10.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.rubocop.yml +5 -0
  4. data/CHANGELOG.md +41 -0
  5. data/CLAUDE.md +124 -0
  6. data/CONVENTIONS.md +36 -0
  7. data/README.md +61 -43
  8. data/claudekit.json +11 -0
  9. data/docs/rubrics/rdoc.md +169 -0
  10. data/lib/line/message/builder/actions/message.rb +52 -31
  11. data/lib/line/message/builder/actions/postback.rb +63 -33
  12. data/lib/line/message/builder/actions/uri.rb +139 -0
  13. data/lib/line/message/builder/actions.rb +6 -3
  14. data/lib/line/message/builder/base.rb +92 -67
  15. data/lib/line/message/builder/container.rb +64 -69
  16. data/lib/line/message/builder/context.rb +55 -80
  17. data/lib/line/message/builder/flex/actionable.rb +53 -32
  18. data/lib/line/message/builder/flex/box.rb +366 -79
  19. data/lib/line/message/builder/flex/bubble.rb +78 -51
  20. data/lib/line/message/builder/flex/builder.rb +47 -36
  21. data/lib/line/message/builder/flex/button.rb +96 -48
  22. data/lib/line/message/builder/flex/carousel.rb +57 -29
  23. data/lib/line/message/builder/flex/icon.rb +152 -0
  24. data/lib/line/message/builder/flex/image.rb +103 -42
  25. data/lib/line/message/builder/flex/partial.rb +58 -55
  26. data/lib/line/message/builder/flex/position.rb +187 -100
  27. data/lib/line/message/builder/flex/separator.rb +84 -0
  28. data/lib/line/message/builder/flex/size.rb +67 -47
  29. data/lib/line/message/builder/flex/span.rb +184 -0
  30. data/lib/line/message/builder/flex/text.rb +210 -54
  31. data/lib/line/message/builder/flex.rb +31 -26
  32. data/lib/line/message/builder/quick_reply.rb +174 -4
  33. data/lib/line/message/builder/text.rb +70 -3
  34. data/lib/line/message/builder/version.rb +1 -1
  35. data/lib/line/message/builder.rb +16 -13
  36. data/lib/line/message/rspec/matchers/have_flex_bubble.rb +1 -1
  37. data/lib/line/message/rspec/matchers/have_flex_component.rb +26 -5
  38. data/lib/line/message/rspec/matchers/have_flex_message.rb +1 -1
  39. data/lib/line/message/rspec/matchers/have_flex_separator.rb +20 -0
  40. data/lib/line/message/rspec/matchers/have_quick_reply.rb +1 -1
  41. data/lib/line/message/rspec/matchers/have_text_message.rb +1 -1
  42. data/lib/line/message/rspec/matchers.rb +1 -0
  43. data/llm.txt +367 -45
  44. data/release-please-config.json +3 -1
  45. metadata +12 -3
@@ -4,67 +4,93 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # Represents a "carousel" container in a LINE Flex Message.
8
- # A carousel is a horizontally scrollable sequence of {Bubble} components.
7
+ # Represents a carousel container in a LINE Flex Message.
8
+ # A carousel is a horizontally scrollable sequence of Bubble components.
9
9
  # Each bubble in the carousel is a distinct message unit. Users can swipe
10
10
  # left or right to view the different bubbles.
11
11
  #
12
12
  # Carousels are ideal for presenting multiple items, such as products,
13
13
  # articles, or options, in a compact and interactive way.
14
14
  #
15
- # @example Creating a carousel with two bubbles
16
- # Line::Message::Builder.with do |root|
17
- # root.flex alt_text: "Product Showcase" do |flex_builder|
18
- # flex_builder.carousel do |carousel_container| # carousel_container is an instance of Flex::Carousel
19
- # carousel_container.bubble size: :mega do |bubble1|
20
- # bubble1.hero_image "https://example.com/product1.jpg"
21
- # bubble1.body { |b| b.text "Product 1" }
15
+ # == Example
16
+ #
17
+ # Line::Message::Builder.with do
18
+ # flex alt_text: "Product Showcase" do
19
+ # carousel do
20
+ # bubble size: :mega do
21
+ # hero_image "https://example.com/product1.jpg"
22
+ # body { text "Product 1" }
22
23
  # end
23
- # carousel_container.bubble size: :mega do |bubble2|
24
- # bubble2.hero_image "https://example.com/product2.jpg"
25
- # bubble2.body { |b| b.text "Product 2" }
24
+ # bubble size: :mega do
25
+ # hero_image "https://example.com/product2.jpg"
26
+ # body { text "Product 2" }
26
27
  # end
27
28
  # end
28
29
  # end
29
30
  # end
30
31
  #
31
- # @see https://developers.line.biz/en/reference/messaging-api/#carousel
32
- # @see Bubble For the structure of individual items within the carousel.
33
- # @see HasPartial While included, direct usage on Carousel itself is limited;
34
- # partials are more commonly used within the individual bubbles.
32
+ # See also:
33
+ # - https://developers.line.biz/en/reference/messaging-api/#carousel
34
+ # - Bubble for the structure of individual items within the carousel
35
+ # - HasPartial (included but direct usage on Carousel is limited; partials
36
+ # are more commonly used within bubbles)
35
37
  class Carousel < Line::Message::Builder::Base
36
- include HasPartial # Allows including predefined partial component sets (more relevant for bubbles within).
38
+ include HasPartial
37
39
 
38
- # @!attribute [r] contents
39
- # @return [Array<Flex::Bubble>] An array holding the {Bubble} components
40
- # that form the items of this carousel.
40
+ # An array holding the Bubble components that form the items of this carousel.
41
41
  attr_reader :contents
42
42
 
43
43
  # Initializes a new Flex Message Carousel container.
44
44
  # The provided block is instance-eval'd, allowing DSL methods like
45
- # {#bubble} to be called to add bubbles to the carousel.
45
+ # +bubble+ to be called to add bubbles to the carousel.
46
+ #
47
+ # [context]
48
+ # An optional context for the builder
49
+ # [options]
50
+ # A hash of options (currently none specific to Carousel itself,
51
+ # but available for future extensions or via Base)
52
+ # [block]
53
+ # A block to define the bubbles within this carousel
54
+ #
55
+ # == Example
46
56
  #
47
- # @param context [Object, nil] An optional context for the builder.
48
- # @param options [Hash] A hash of options (currently none specific to Carousel itself,
49
- # but available for future extensions or via `Base`).
50
- # @param block [Proc, nil] A block to define the bubbles within this carousel.
57
+ # carousel = Carousel.new do
58
+ # bubble { body { text "First" } }
59
+ # bubble { body { text "Second" } }
60
+ # end
51
61
  def initialize(context: nil, **options, &)
52
62
  @contents = [] # Holds an array of Bubble objects
53
63
 
54
64
  super # Calls Base#initialize, sets options, and evals block
55
65
  end
56
66
 
57
- # Adds a new {Bubble} to this carousel's contents.
67
+ # Adds a new Bubble to this carousel's contents.
58
68
  # Each call to this method appends another bubble to the horizontal sequence.
59
69
  #
60
- # @param options [Hash] Options for the Bubble. See {Bubble#initialize}.
61
- # @param block [Proc] A block to define the sections and content of the Bubble.
62
- # @return [Flex::Bubble] The newly created Bubble object that was added to the carousel.
70
+ # [options]
71
+ # Options for the Bubble (see Bubble#initialize)
72
+ # [block]
73
+ # A block to define the sections and content of the Bubble
74
+ #
75
+ # [return]
76
+ # The newly created Bubble object that was added to the carousel
77
+ #
78
+ # == Example
79
+ #
80
+ # carousel do
81
+ # bubble size: :mega do
82
+ # body { text "Product 1" }
83
+ # end
84
+ # bubble size: :mega do
85
+ # body { text "Product 2" }
86
+ # end
87
+ # end
63
88
  def bubble(**options, &)
64
89
  # The maximum number of bubbles is validated in `to_h` as per LINE API limits.
65
90
  @contents << Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
66
91
  end
67
92
 
93
+ # :nodoc:
68
94
  def to_h
69
95
  raise RequiredError, "Carousel contents must have at least 1 bubble." if @contents.empty?
70
96
  # LINE API as of 2023-10-10 allows up to 12 bubbles in a carousel.
@@ -77,6 +103,7 @@ module Line
77
103
 
78
104
  private
79
105
 
106
+ # :nodoc:
80
107
  def to_api
81
108
  {
82
109
  type: "carousel",
@@ -84,6 +111,7 @@ module Line
84
111
  }.compact # compact is likely unnecessary here as contents is always present.
85
112
  end
86
113
 
114
+ # :nodoc:
87
115
  def to_sdkv2
88
116
  {
89
117
  type: "carousel",
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # Represents an "icon" component in a LINE Flex Message.
8
+ #
9
+ # Icons render a small image that decorates the text next to them. They are
10
+ # the standard way to build rating stars, labelled metadata rows, and other
11
+ # icon-and-text pairs.
12
+ #
13
+ # An icon can only be placed in a box whose +layout+ is +:baseline+. Unlike
14
+ # Image, an icon cannot have an action attached to it.
15
+ #
16
+ # == Example: Rating stars beside a score
17
+ #
18
+ # Line::Message::Builder.with do
19
+ # flex alt_text: "Review" do
20
+ # bubble do
21
+ # body do
22
+ # box layout: :baseline do
23
+ # icon "https://example.com/star_on.png", size: :sm
24
+ # icon "https://example.com/star_on.png", size: :sm
25
+ # icon "https://example.com/star_off.png", size: :sm
26
+ # text "3.0", size: :sm, margin: :md
27
+ # end
28
+ # end
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # === Example: Following the reader's font size
34
+ #
35
+ # box layout: :baseline do
36
+ # icon "https://example.com/pin.png", scaling: true
37
+ # text "Taipei", scaling: true
38
+ # end
39
+ #
40
+ # See also:
41
+ # - https://developers.line.biz/en/reference/messaging-api/#icon
42
+ # - Position::Margin for +margin+ property
43
+ # - Position::Offset for offset properties
44
+ # - Size::Shared for common +size+ keywords (e.g., +:sm+, +:xl+)
45
+ class Icon < Line::Message::Builder::Base
46
+ include Position::Margin # Adds `margin` option.
47
+ include Position::Offset # Adds offset options.
48
+ include Size::Shared # Adds `size` option (e.g., :sm, :md, :xl).
49
+
50
+ # The URL of the icon image (must be HTTPS). This is a required attribute.
51
+ attr_reader :url
52
+
53
+ # :method: aspect_ratio
54
+ # :call-seq:
55
+ # aspect_ratio() -> String or nil
56
+ # aspect_ratio(value) -> String
57
+ #
58
+ # Sets or gets the aspect ratio of the icon (width:height).
59
+ #
60
+ # [value]
61
+ # The aspect ratio string (e.g., <code>"1:1"</code>, <code>"2:1"</code>).
62
+ # Default is <code>"1:1"</code>
63
+ option :aspect_ratio, default: nil # API key: aspectRatio
64
+
65
+ # :method: scaling
66
+ # :call-seq:
67
+ # scaling() -> Boolean or nil
68
+ # scaling(value) -> Boolean
69
+ #
70
+ # Sets or gets whether the icon size follows the font size setting of the
71
+ # LINE app.
72
+ #
73
+ # [value]
74
+ # +true+ to scale the icon, +false+ (default) to keep it fixed
75
+ option :scaling, default: nil
76
+
77
+ # Initializes a new Flex Message Icon component.
78
+ #
79
+ # [url]
80
+ # The HTTPS URL of the icon image (required)
81
+ # [context]
82
+ # An optional context for the builder (default: +nil+)
83
+ # [options]
84
+ # A hash of options to set instance variables (e.g., +:size+,
85
+ # +:aspect_ratio+, and options from included modules)
86
+ # [block]
87
+ # An optional block for further configuration
88
+ #
89
+ # Raises RequiredError if +url+ is +nil+ when building the message.
90
+ #
91
+ # == Example
92
+ #
93
+ # Line::Message::Builder::Flex::Icon.new(
94
+ # "https://example.com/star.png",
95
+ # size: :sm
96
+ # )
97
+ def initialize(url, context: nil, **options, &)
98
+ @url = url # The icon URL is mandatory.
99
+
100
+ super(context: context, **options, &)
101
+ end
102
+
103
+ private
104
+
105
+ # :nodoc:
106
+ def to_api # rubocop:disable Metrics/MethodLength
107
+ raise RequiredError, "url is required for an icon component" if url.nil?
108
+
109
+ {
110
+ type: "icon",
111
+ url: url,
112
+ # Position::Margin
113
+ margin: margin,
114
+ # Position::Offset
115
+ position: position,
116
+ offsetTop: offset_top,
117
+ offsetBottom: offset_bottom,
118
+ offsetStart: offset_start,
119
+ offsetEnd: offset_end,
120
+ # Size::Shared
121
+ size: size,
122
+ aspectRatio: aspect_ratio, # From option (maps to API key)
123
+ scaling: scaling # From option
124
+ }.compact
125
+ end
126
+
127
+ # :nodoc:
128
+ def to_sdkv2 # rubocop:disable Metrics/MethodLength
129
+ raise RequiredError, "url is required for an icon component" if url.nil?
130
+
131
+ {
132
+ type: "icon",
133
+ url: url,
134
+ # Position::Margin
135
+ margin: margin,
136
+ # Position::Offset
137
+ position: position,
138
+ offset_top: offset_top,
139
+ offset_bottom: offset_bottom,
140
+ offset_start: offset_start,
141
+ offset_end: offset_end,
142
+ # Size::Shared
143
+ size: size,
144
+ aspect_ratio: aspect_ratio, # From option (maps to API key)
145
+ scaling: scaling # From option
146
+ }.compact
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -8,34 +8,47 @@ module Line
8
8
  #
9
9
  # Images are specified by a URL and can be included in various parts of a
10
10
  # Flex Message, such as a box, a bubble's hero section, etc. They offer
11
- # several properties to control their appearance, including `size`,
12
- # `aspect_ratio`, and `aspect_mode`. An image can also have an
13
- # {Actionable#action action} to make it tappable.
11
+ # several properties to control their appearance, including +size+,
12
+ # +aspect_ratio+, +aspect_mode+ and +background_color+. An image can also
13
+ # have an action to make it tappable.
14
14
  #
15
- # @example Creating an image component within a box
16
- # Line::Message::Builder.with do |root|
17
- # root.flex alt_text: "Image Example" do |flex|
18
- # flex.bubble do |bubble|
19
- # bubble.body do |body_box|
20
- # body_box.image "https://example.com/image.png",
21
- # aspect_ratio: "16:9",
22
- # aspect_mode: :cover,
23
- # size: :full do |img_action|
24
- # img_action.message "View Details", text: "Show details for image"
15
+ # == Example
16
+ #
17
+ # Line::Message::Builder.with do
18
+ # flex alt_text: "Image Example" do
19
+ # bubble do
20
+ # body do
21
+ # image "https://example.com/image.png",
22
+ # aspect_ratio: "16:9",
23
+ # aspect_mode: :cover,
24
+ # size: :full
25
25
  # end
26
26
  # end
27
27
  # end
28
28
  # end
29
29
  # end
30
30
  #
31
- # @see https://developers.line.biz/en/reference/messaging-api/#image
32
- # @see Actionable For making the image tappable.
33
- # @see Position::Horizontal For `align` property.
34
- # @see Position::Vertical For `gravity` property.
35
- # @see Position::Margin For `margin` property.
36
- # @see Position::Offset For offset properties.
37
- # @see Size::Flex For `flex` sizing property.
38
- # @see Size::Image For `size` (image specific keywords), `aspect_ratio`, `aspect_mode`.
31
+ # === Example: Playing an animated image on a tinted backdrop
32
+ #
33
+ # Line::Message::Builder.with do
34
+ # flex alt_text: "Image Example" do
35
+ # bubble do
36
+ # hero_image "https://example.com/loading.png",
37
+ # animated: true,
38
+ # background_color: "#FFFFFF"
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # See also:
44
+ # - https://developers.line.biz/en/reference/messaging-api/#image
45
+ # - Actionable for making the image tappable
46
+ # - Position::Horizontal for +align+ property
47
+ # - Position::Vertical for +gravity+ property
48
+ # - Position::Margin for +margin+ property
49
+ # - Position::Offset for offset properties
50
+ # - Size::Flex for +flex+ sizing property
51
+ # - Size::Image for +size+, +aspect_ratio+, and +aspect_mode+ properties
39
52
  class Image < Line::Message::Builder::Base
40
53
  include Actionable # Enables defining an action for the image.
41
54
  include Position::Horizontal # Adds `align` option for horizontal alignment.
@@ -45,35 +58,77 @@ module Line
45
58
  include Size::Flex # Adds `flex` option for sizing within a parent box.
46
59
  include Size::Image # Adds image-specific sizing options like `size`, `aspect_ratio`, `aspect_mode`.
47
60
 
48
- # @!attribute [r] url
49
- # @return [String] The URL of the image. Must be HTTPS.
50
- # This is a required attribute.
61
+ # The URL of the image (must be HTTPS). This is a required attribute.
51
62
  attr_reader :url
52
63
 
53
- # Specifies the aspect ratio of the image (width:height).
54
- # E.g., "1:1", "16:9", "20:13". Default is "1:1".
55
- # @!method aspect_ratio(value)
56
- # @param value [String, nil] The aspect ratio string.
57
- # @return [String, nil] The current aspect ratio.
64
+ # :method: aspect_ratio
65
+ # :call-seq:
66
+ # aspect_ratio() -> String or nil
67
+ # aspect_ratio(value) -> String
68
+ #
69
+ # Sets or gets the aspect ratio of the image (width:height).
70
+ #
71
+ # [value]
72
+ # The aspect ratio string (e.g., <code>"1:1"</code>, <code>"16:9"</code>,
73
+ # <code>"20:13"</code>). Default is <code>"1:1"</code>.
58
74
  option :aspect_ratio, default: nil
59
75
 
60
- # Specifies how the image should be displayed within the area defined by `aspect_ratio`.
61
- # @!method aspect_mode(value)
62
- # @param value [Symbol, String, nil] Aspect mode. Can be `:cover` (default)
63
- # or `:fit`.
64
- # @return [Symbol, String, nil] The current aspect mode.
76
+ # :method: aspect_mode
77
+ # :call-seq:
78
+ # aspect_mode() -> Symbol or nil
79
+ # aspect_mode(value) -> Symbol
80
+ #
81
+ # Sets or gets how the image should be displayed within the area defined by +aspect_ratio+.
82
+ #
83
+ # [value]
84
+ # The aspect mode (can be +:cover+ (default) or +:fit+)
65
85
  option :aspect_mode, default: nil # :cover, :fit
66
86
 
87
+ # :method: background_color
88
+ # :call-seq:
89
+ # background_color() -> String or nil
90
+ # background_color(value) -> String
91
+ #
92
+ # Sets or gets the background color shown behind the image.
93
+ #
94
+ # [value]
95
+ # Hexadecimal color code (e.g., <code>"#RRGGBB"</code>)
96
+ option :background_color, default: nil # API key: backgroundColor
97
+
98
+ # :method: animated
99
+ # :call-seq:
100
+ # animated() -> Boolean or nil
101
+ # animated(value) -> Boolean
102
+ #
103
+ # Sets or gets whether an animated image (APNG) is played back. A single
104
+ # message may mark at most 10 images as animated, and frames larger than
105
+ # 300 KB are not played.
106
+ #
107
+ # [value]
108
+ # +true+ to play the animation, +false+ (default) to show a still frame
109
+ option :animated, default: nil
110
+
67
111
  # Initializes a new Flex Message Image component.
68
112
  #
69
- # @param url [String] The HTTPS URL of the image. This is required.
70
- # @param context [Object, nil] An optional context for the builder.
71
- # @param options [Hash] A hash of options to set instance variables
72
- # (e.g., `:aspect_ratio`, `:aspect_mode`, `:size`, and options from included modules).
73
- # @param block [Proc, nil] An optional block, typically used to define an
74
- # {Actionable#action action} for the image.
75
- # @raise [ArgumentError] if `url` is nil (though the more specific `RequiredError`
76
- # is raised in `to_h`).
113
+ # [url]
114
+ # The HTTPS URL of the image (required)
115
+ # [context]
116
+ # An optional context for the builder (default: +nil+)
117
+ # [options]
118
+ # A hash of options to set instance variables (e.g., +:aspect_ratio+,
119
+ # +:aspect_mode+, +:size+, and options from included modules)
120
+ # [block]
121
+ # An optional block, typically used to define an action for the image
122
+ #
123
+ # Raises RequiredError if +url+ is +nil+ when building the message.
124
+ #
125
+ # == Example
126
+ #
127
+ # image = Line::Message::Builder::Flex::Image.new(
128
+ # "https://example.com/image.png",
129
+ # aspect_ratio: "16:9",
130
+ # aspect_mode: :cover
131
+ # )
77
132
  def initialize(url, context: nil, **options, &)
78
133
  @url = url # The image URL is mandatory.
79
134
 
@@ -82,6 +137,7 @@ module Line
82
137
 
83
138
  private
84
139
 
140
+ # :nodoc:
85
141
  def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
86
142
  raise RequiredError, "url is required for an image component" if url.nil?
87
143
 
@@ -105,11 +161,14 @@ module Line
105
161
  size: size,
106
162
  aspectRatio: aspect_ratio, # From option
107
163
  aspectMode: aspect_mode, # From option
164
+ backgroundColor: background_color, # From option (maps to API key)
165
+ animated: animated, # From option
108
166
  # Actionable
109
167
  action: action&.to_h # From Actionable module
110
168
  }.compact
111
169
  end
112
170
 
171
+ # :nodoc:
113
172
  def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
114
173
  raise RequiredError, "url is required for an image component" if url.nil?
115
174
 
@@ -133,6 +192,8 @@ module Line
133
192
  size: size,
134
193
  aspect_ratio: aspect_ratio, # From option
135
194
  aspect_mode: aspect_mode, # From option
195
+ background_color: background_color, # From option (maps to API key)
196
+ animated: animated, # From option
136
197
  # Actionable
137
198
  action: action&.to_h # From Actionable module
138
199
  }.compact
@@ -4,44 +4,52 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The `HasPartial` module provides functionality for Flex Message components
8
- # (like {Box}, {Bubble}) to render reusable "partials". Partials are defined
9
- # by creating classes that inherit from {Partial}.
7
+ # The HasPartial module provides functionality for Flex Message components
8
+ # (like Box, Bubble) to render reusable "partials". Partials are defined
9
+ # by creating classes that inherit from Partial.
10
10
  #
11
- # Including this module into a component class gives it the {#partial!} method.
11
+ # Including this module into a component class gives it the +partial!+ method.
12
+ #
13
+ # == Example
12
14
  #
13
- # @example Defining and using a partial
14
15
  # # Define a reusable partial
15
16
  # class MyButtonPartial < Line::Message::Builder::Flex::Partial
16
- # def call(label:, data:)
17
- # # `button` method is available from the context (e.g., a Box)
18
- # button style: :primary do |btn|
19
- # btn.postback data, label: label
17
+ # def call
18
+ # # +button+ method is available from the context (e.g., a Box)
19
+ # button style: :primary do
20
+ # # +data+ and +label+ are available from <code>context.assigns</code>
21
+ # postback data, label: label
20
22
  # end
21
23
  # end
22
24
  # end
23
25
  #
24
26
  # # Use the partial within a Box component
25
- # a_box_component.instance_eval do
26
- # # ... other box content ...
27
- # partial! MyButtonPartial, label: "Action", data: "action=do_something"
28
- # # ... other box content ...
27
+ # Line::Message::Builder.with do
28
+ # flex do
29
+ # bubble do
30
+ # body do
31
+ # partial! MyButtonPartial, label: "Action", data: "action=do_something"
32
+ # end
33
+ # end
34
+ # end
29
35
  # end
30
36
  #
31
- # @see Partial
37
+ # See also:
38
+ # - Partial
32
39
  module HasPartial
33
- # Renders a given {Partial} class within the current component's context.
40
+ # Renders a given Partial class within the current component's context.
34
41
  #
35
- # This method temporarily makes the provided `assigns` available to the
36
- # partial through the `context.assigns` mechanism. After the partial's
37
- # `call` method completes, the original `context.assigns` are restored.
42
+ # This method temporarily makes the provided +assigns+ available to the
43
+ # partial through the <code>context.assigns</code> mechanism. After the partial's
44
+ # +call+ method completes, the original <code>context.assigns</code> are restored.
38
45
  #
39
- # @param partial_class [Class] The class of the partial to render. Must be a
40
- # subclass of {Partial}.
41
- # @param assigns [Hash] A hash of key-value pairs that will be made
42
- # available as `assigns` within the partial's `call` method.
43
- # @return [void]
44
- # @raise [ArgumentError] if `partial_class` is not a subclass of {Partial}.
46
+ # [partial_class]
47
+ # The class of the partial to render. Must be a subclass of Partial.
48
+ # [assigns]
49
+ # A hash of key-value pairs that will be made available as +assigns+
50
+ # within the partial's +call+ method.
51
+ #
52
+ # Raises ArgumentError if +partial_class+ is not a subclass of Partial.
45
53
  def partial!(partial_class, **assigns)
46
54
  unless partial_class < Partial
47
55
  raise ArgumentError,
@@ -57,60 +65,55 @@ module Line
57
65
  end
58
66
  end
59
67
 
60
- # The `Partial` class is an abstract base for creating reusable snippets
68
+ # The Partial class is an abstract base for creating reusable snippets
61
69
  # of Flex Message content. To define a partial, create a new class that
62
- # inherits from `Partial` and implements the {#call} instance method.
70
+ # inherits from Partial and implements the +call+ instance method.
63
71
  #
64
- # Within the `call` method, you can use the standard Flex Message builder
65
- # DSL methods (e.g., `text`, `box`, `button`) as if you were directly inside
72
+ # Within the +call+ method, you can use the standard Flex Message builder
73
+ # DSL methods (e.g., +text+, +box+, +button+) as if you were directly inside
66
74
  # the component where the partial is being rendered. This is possible because
67
- # the `Partial` instance delegates unknown method calls to the component
68
- # instance that is rendering it (passed as `context` during initialization).
75
+ # the Partial instance delegates unknown method calls to the component
76
+ # instance that is rendering it (passed as +context+ during initialization).
77
+ #
78
+ # Subclass and implement +call+ to create a concrete partial.
69
79
  #
70
- # @abstract Subclass and implement {#call} to create a concrete partial.
71
- # @see HasPartial For how to render partials.
80
+ # See also:
81
+ # - HasPartial for how to render partials
72
82
  class Partial
73
83
  # Initializes a new Partial instance.
74
- # This is typically called by the {HasPartial#partial!} method.
84
+ # This is typically called by the HasPartial#partial! method.
75
85
  #
76
- # @param context [Object] The component instance (e.g., {Box}, {Bubble})
77
- # within which this partial is being rendered. This context provides
78
- # the DSL methods (like `text`, `box`) used in the partial's `call` method.
86
+ # [context]
87
+ # The component instance (e.g., Box, Bubble) within which this partial
88
+ # is being rendered. This context provides the DSL methods (like +text+,
89
+ # +box+) used in the partial's +call+ method.
79
90
  def initialize(context:)
80
91
  @context = context # The component (e.g., Box, Bubble) rendering this partial
81
92
  end
82
93
 
83
94
  # This method must be implemented by subclasses to define the content of
84
95
  # the partial. Inside this method, use the Flex Message builder DSL methods
85
- # (e.g., `text "Hello"`, `button { ... }`) which will be delegated to the
86
- # rendering context.
96
+ # (e.g., <code>text "Hello"</code>, <code>button { ... }</code>) which will be
97
+ # delegated to the rendering context.
98
+ #
99
+ # Any arguments passed to +partial!+ as +assigns+ are available via
100
+ # <code>context.assigns</code> or directly if the rendering context's +method_missing+
101
+ # handles +assigns+ lookup (as Line::Message::Builder::Context does).
87
102
  #
88
- # Any arguments passed to `partial!` as `assigns` are available via
89
- # `context.assigns` or directly if the rendering context's `method_missing`
90
- # handles `assigns` lookup (as {Line::Message::Builder::Context} does).
103
+ # Arguments passed from the +partial!+ call's +assigns+ can be explicitly defined
104
+ # as parameters here, or accessed via <code>context.assigns</code>.
91
105
  #
92
- # @param ... [Object] Arguments passed from the `partial!` call's `assigns`
93
- # can be explicitly defined as parameters here, or accessed via `context.assigns`.
94
- # @raise [NotImplementedError] if a subclass does not implement this method.
106
+ # Raises NotImplementedError if a subclass does not implement this method.
95
107
  def call(*)
96
108
  raise NotImplementedError,
97
109
  "The #{self.class.name} class must implement the #call method to define its content."
98
110
  end
99
111
 
100
- # @!visibility private
101
- # Part of Ruby's dynamic method dispatch. It's overridden here to declare
102
- # that instances of `Partial` can respond to methods to which the
103
- # wrapped `@context` object responds.
104
- def respond_to_missing?(method_name, include_private = false)
112
+ def respond_to_missing?(method_name, include_private = false) # :nodoc:
105
113
  @context.respond_to?(method_name, include_private) || super
106
114
  end
107
115
 
108
- # @!visibility private
109
- # Handles calls to methods not explicitly defined on the `Partial` class
110
- # by delegating them to the wrapped `@context` object (the rendering component).
111
- # This allows the `call` method of a partial to use DSL methods like `text`,
112
- # `box`, `button`, etc., as if they were defined directly in the partial.
113
- def method_missing(method_name, ...)
116
+ def method_missing(method_name, ...) # :nodoc:
114
117
  if @context.respond_to?(method_name)
115
118
  @context.public_send(method_name, ...)
116
119
  else