line-message-builder 0.7.0 → 0.8.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.
@@ -4,32 +4,98 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The Builder class is used to build quick reply buttons.
7
+ # The `Builder` class is the main entry point for constructing a complete
8
+ # LINE Flex Message. A Flex Message is a highly customizable message type
9
+ # that can contain one main content element, which is either a single
10
+ # {Bubble} or a {Carousel} of bubbles.
11
+ #
12
+ # This builder requires `alt_text` (alternative text) for accessibility and
13
+ # for display on devices or LINE versions that cannot render Flex Messages.
14
+ # It can also have a `quickReply` attached to it.
15
+ #
16
+ # @example Creating a Flex Message with a single bubble
17
+ # Line::Message::Builder.with do |root|
18
+ # root.flex alt_text: "My Product" do |flex_builder| # flex_builder is an instance of Flex::Builder
19
+ # flex_builder.bubble size: :giga do |bubble_content|
20
+ # bubble_content.hero_image "https://example.com/product.jpg"
21
+ # bubble_content.body do |b|
22
+ # b.text "Product Name", weight: :bold, size: :xl
23
+ # end
24
+ # end
25
+ # flex_builder.quick_reply do |qr|
26
+ # qr.button action: :message, label: "Learn More", text: "Tell me more"
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # @see https://developers.line.biz/en/reference/messaging-api/#flex-messages
32
+ # @see Bubble
33
+ # @see Carousel
34
+ # @see QuickReply
8
35
  class Builder < Line::Message::Builder::Base
36
+ # Alternative text for the Flex Message. Displayed on devices that
37
+ # cannot render Flex Messages or used for accessibility.
38
+ # This is a required field for a valid Flex Message.
39
+ # @!method alt_text(value)
40
+ # @param value [String, nil] The alternative text. Max 400 characters.
41
+ # @return [String, nil] The current alternative text.
9
42
  option :alt_text, default: nil
10
43
 
44
+ # Initializes a new Flex Message Builder.
45
+ # The provided block is instance-eval'd, allowing DSL methods like
46
+ # {#bubble} or {#carousel} to define the main content.
47
+ #
48
+ # @param context [Object, nil] An optional context for the builder.
49
+ # @param options [Hash] A hash of options to set instance variables (e.g., `:alt_text`).
50
+ # @param block [Proc, nil] A block to define the content (bubble or carousel)
51
+ # of this Flex Message.
11
52
  def initialize(context: nil, **options, &)
12
- @contents = nil
53
+ @contents = nil # Will hold either a Bubble or Carousel instance
13
54
 
14
- super
55
+ super # Calls Base#initialize, sets options (like @alt_text), and evals block
15
56
  end
16
57
 
58
+ # Defines the content of this Flex Message as a single {Bubble}.
59
+ # If called, this will overwrite any previously defined `carousel` content.
60
+ #
61
+ # @param options [Hash] Options for the Bubble. See {Bubble#initialize}.
62
+ # @param block [Proc] A block to define the sections of the Bubble.
63
+ # @return [Flex::Bubble] The newly created Bubble object.
17
64
  def bubble(**options, &)
18
65
  @contents = Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
19
66
  end
20
67
 
68
+ # Defines the content of this Flex Message as a {Carousel} of bubbles.
69
+ # If called, this will overwrite any previously defined `bubble` content.
70
+ #
71
+ # @param options [Hash] Options for the Carousel. See {Carousel#initialize}.
72
+ # @param block [Proc] A block to define the bubbles within the Carousel.
73
+ # @return [Flex::Carousel] The newly created Carousel object.
21
74
  def carousel(**options, &)
22
75
  @contents = Line::Message::Builder::Flex::Carousel.new(context: context, **options, &)
23
76
  end
24
77
 
25
- def to_h
26
- raise Error, "contents should be bubble or carousel" if @contents.nil?
78
+ private
79
+
80
+ def to_api
81
+ raise Error, "Flex Message contents (bubble or carousel) must be defined." if @contents.nil?
82
+
83
+ {
84
+ type: "flex",
85
+ altText: alt_text, # From option
86
+ contents: @contents.to_h, # Serializes the Bubble or Carousel
87
+ quickReply: @quick_reply&.to_h # From Base class's quick_reply method
88
+ }.compact
89
+ end
90
+
91
+ def to_sdkv2
92
+ raise Error, "Flex Message contents (bubble or carousel) must be defined." if @contents.nil?
27
93
 
28
94
  {
29
95
  type: "flex",
30
- altText: @alt_text,
31
- contents: @contents.to_h,
32
- quickReply: @quick_reply&.to_h
96
+ alt_text: alt_text, # From option
97
+ contents: @contents.to_h, # Serializes the Bubble or Carousel
98
+ quick_reply: @quick_reply&.to_h # From Base class's quick_reply method
33
99
  }.compact
34
100
  end
35
101
  end
@@ -4,30 +4,87 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The button is a component of the Flex message.
7
+ # Represents a "button" component in a LINE Flex Message.
8
+ #
9
+ # Buttons are interactive elements that users can tap to trigger an {Actionable#action action}
10
+ # (e.g., open a URL, send a message, or trigger a postback). They have
11
+ # various styling options, including `style` (primary, secondary, link) and
12
+ # `height`.
13
+ #
14
+ # An action is mandatory for a button component.
15
+ #
16
+ # @example Creating a button that sends a message
17
+ # Line::Message::Builder.with do |root|
18
+ # root.flex alt_text: "Button Example" do |flex|
19
+ # flex.bubble do |bubble|
20
+ # bubble.body do |body_box|
21
+ # body_box.button style: :primary, height: :sm do |btn|
22
+ # btn.message "Buy Now", label: "Buy" # Action definition
23
+ # end
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # @see https://developers.line.biz/en/reference/messaging-api/#button
30
+ # @see Actionable For defining the button's action (mandatory).
31
+ # @see Position::Vertical For `gravity` property.
32
+ # @see Position::Padding For padding properties.
33
+ # @see Position::Margin For margin property.
34
+ # @see Position::Offset For offset properties.
35
+ # @see Size::Flex For flex sizing property.
36
+ # @see Size::AdjustMode For `adjust_mode` property.
8
37
  class Button < Line::Message::Builder::Base
9
- include Actionable
10
- include Position::Vertical
11
- include Position::Padding
12
- include Position::Margin
13
- include Position::Offset
14
- include Size::Flex
15
- include Size::AdjustMode
38
+ include Actionable # Defines the action performed when the button is tapped.
39
+ include Position::Vertical # Adds `gravity` option for vertical alignment.
40
+ include Position::Padding # Adds padding options.
41
+ include Position::Margin # Adds `margin` option.
42
+ include Position::Offset # Adds offset options.
43
+ include Size::Flex # Adds `flex` option for sizing within a parent box.
44
+ include Size::AdjustMode # Adds `adjust_mode` option.
16
45
 
46
+ # Specifies the style of the button.
47
+ # @!method style(value)
48
+ # @param value [Symbol, String] Button style.
49
+ # Can be `:primary`, `:secondary`, `:link` (default).
50
+ # String values should match these keywords.
51
+ # @return [Symbol, String] The current button style.
17
52
  option :style, default: :link
53
+
54
+ # Specifies the height of the button.
55
+ # @!method height(value)
56
+ # @param value [Symbol, String] Button height.
57
+ # Can be `:sm` (small) or `:md` (medium, default).
58
+ # @return [Symbol, String] The current button height.
18
59
  option :height, default: :md, validator: Validators::Enum.new(:sm, :md)
19
60
 
20
- def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
21
- raise RequiredError, "action is required" if action.nil?
61
+ # Initializes a new Flex Message Button component.
62
+ # The provided block is instance-eval'd, primarily used to define the
63
+ # button's action using methods from the {Actionable} module (e.g., `message "label"`).
64
+ #
65
+ # @param context [Object, nil] An optional context for the builder.
66
+ # @param options [Hash] A hash of options to set instance variables
67
+ # (e.g., `:style`, `:height`, and options from included modules).
68
+ # @param block [Proc, nil] A block to define the button's action and other properties.
69
+ # This block is where you should call an action method like `message` or `postback`.
70
+ def initialize(context: nil, **options, &)
71
+ super # Calls Base#initialize, sets options, and evals block (which should define the action)
72
+ end
73
+
74
+ private
75
+
76
+ def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
77
+ raise RequiredError, "action is required for a button" if action.nil?
22
78
 
23
79
  {
24
80
  type: "button",
25
- action: action.to_h,
26
- height: height,
27
- # Position
28
- grivity: gravity,
81
+ action: action.to_h, # From Actionable module
82
+ style: style, # From option
83
+ height: height, # From option
84
+ # Position::Vertical
85
+ gravity: gravity,
29
86
  # Position::Padding
30
- paddingAll: padding,
87
+ paddingAll: padding || padding_all,
31
88
  paddingTop: padding_top,
32
89
  paddingBottom: padding_bottom,
33
90
  paddingStart: padding_start,
@@ -43,8 +100,38 @@ module Line
43
100
  # Size::Flex
44
101
  flex: flex,
45
102
  # Size::AdjustMode
46
- adjustMode: adjust_mode,
47
- style: style
103
+ adjustMode: adjust_mode
104
+ }.compact
105
+ end
106
+
107
+ def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
108
+ raise RequiredError, "action is required for a button" if action.nil?
109
+
110
+ {
111
+ type: "button",
112
+ action: action.to_h, # From Actionable module
113
+ style: style, # From option
114
+ height: height, # From option
115
+ # Position::Vertical
116
+ gravity: gravity,
117
+ # Position::Padding
118
+ padding_all: padding || padding_all,
119
+ padding_top: padding_top,
120
+ padding_bottom: padding_bottom,
121
+ padding_start: padding_start,
122
+ padding_end: padding_end,
123
+ # Position::Margin
124
+ margin: margin,
125
+ # Position::Offset
126
+ position: position,
127
+ offset_top: offset_top,
128
+ offset_bottom: offset_bottom,
129
+ offset_start: offset_start,
130
+ offset_end: offset_end,
131
+ # Size::Flex
132
+ flex: flex,
133
+ # Size::AdjustMode
134
+ adjust_mode: adjust_mode
48
135
  }.compact
49
136
  end
50
137
  end
@@ -4,28 +4,91 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The carousel is multiple bubbles in a single Flex message.
7
+ # Represents a "carousel" container in a LINE Flex Message.
8
+ # A carousel is a horizontally scrollable sequence of {Bubble} components.
9
+ # Each bubble in the carousel is a distinct message unit. Users can swipe
10
+ # left or right to view the different bubbles.
11
+ #
12
+ # Carousels are ideal for presenting multiple items, such as products,
13
+ # articles, or options, in a compact and interactive way.
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" }
22
+ # 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" }
26
+ # end
27
+ # end
28
+ # end
29
+ # end
30
+ #
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.
8
35
  class Carousel < Line::Message::Builder::Base
9
- include HasPartial
36
+ include HasPartial # Allows including predefined partial component sets (more relevant for bubbles within).
10
37
 
38
+ # @!attribute [r] contents
39
+ # @return [Array<Flex::Bubble>] An array holding the {Bubble} components
40
+ # that form the items of this carousel.
41
+ attr_reader :contents
42
+
43
+ # Initializes a new Flex Message Carousel container.
44
+ # The provided block is instance-eval'd, allowing DSL methods like
45
+ # {#bubble} to be called to add bubbles to the carousel.
46
+ #
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.
11
51
  def initialize(context: nil, **options, &)
12
- @contents = []
52
+ @contents = [] # Holds an array of Bubble objects
13
53
 
14
- super
54
+ super # Calls Base#initialize, sets options, and evals block
15
55
  end
16
56
 
57
+ # Adds a new {Bubble} to this carousel's contents.
58
+ # Each call to this method appends another bubble to the horizontal sequence.
59
+ #
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.
17
63
  def bubble(**options, &)
64
+ # The maximum number of bubbles is validated in `to_h` as per LINE API limits.
18
65
  @contents << Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
19
66
  end
20
67
 
21
68
  def to_h
22
- raise RequiredError, "contents should have at least 1 bubble" if @contents.empty?
23
- raise ValidationError, "contents should have at most 12 bubbles" if @contents.size > 12
69
+ raise RequiredError, "Carousel contents must have at least 1 bubble." if @contents.empty?
70
+ # LINE API as of 2023-10-10 allows up to 12 bubbles in a carousel.
71
+ raise ValidationError, "Carousel contents can have at most 12 bubbles." if @contents.size > 12
72
+
73
+ return to_sdkv2 if context.sdkv2?
74
+
75
+ to_api
76
+ end
77
+
78
+ private
79
+
80
+ def to_api
81
+ {
82
+ type: "carousel",
83
+ contents: @contents.map(&:to_h) # Serializes each Bubble in the array
84
+ }.compact # compact is likely unnecessary here as contents is always present.
85
+ end
24
86
 
87
+ def to_sdkv2
25
88
  {
26
89
  type: "carousel",
27
- contents: @contents.map(&:to_h)
28
- }.compact
90
+ contents: @contents.map(&:to_h) # Serializes each Bubble in the array
91
+ }.compact # compact is likely unnecessary here as contents is always present.
29
92
  end
30
93
  end
31
94
  end
@@ -4,36 +4,93 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The image is a component for the Flex message.
7
+ # Represents an "image" component in a LINE Flex Message.
8
+ #
9
+ # Images are specified by a URL and can be included in various parts of a
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.
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"
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+ # end
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`.
8
39
  class Image < Line::Message::Builder::Base
9
- include Actionable
10
- include Position::Horizontal
11
- include Position::Vertical
12
- include Position::Margin
13
- include Position::Offset
14
- include Size::Flex
15
- include Size::Image
40
+ include Actionable # Enables defining an action for the image.
41
+ include Position::Horizontal # Adds `align` option for horizontal alignment.
42
+ include Position::Vertical # Adds `gravity` option for vertical alignment.
43
+ include Position::Margin # Adds `margin` option.
44
+ include Position::Offset # Adds offset options.
45
+ include Size::Flex # Adds `flex` option for sizing within a parent box.
46
+ include Size::Image # Adds image-specific sizing options like `size`, `aspect_ratio`, `aspect_mode`.
16
47
 
48
+ # @!attribute [r] url
49
+ # @return [String] The URL of the image. Must be HTTPS.
50
+ # This is a required attribute.
17
51
  attr_reader :url
18
52
 
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.
19
58
  option :aspect_ratio, default: nil
20
- option :aspect_mode, default: nil
21
59
 
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.
65
+ option :aspect_mode, default: nil # :cover, :fit
66
+
67
+ # Initializes a new Flex Message Image component.
68
+ #
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`).
22
77
  def initialize(url, context: nil, **options, &)
23
- @url = url
78
+ @url = url # The image URL is mandatory.
24
79
 
25
- super(context: context, **options, &)
80
+ super(context: context, **options, &) # Sets options and evals block (for action).
26
81
  end
27
82
 
28
- def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
29
- raise RequiredError, "url is required" if url.nil?
83
+ private
84
+
85
+ def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
86
+ raise RequiredError, "url is required for an image component" if url.nil?
30
87
 
31
88
  {
32
89
  type: "image",
33
90
  url: url,
34
- # Position
35
- align: align,
36
- gravity: gravity,
91
+ # Position::Horizontal & Position::Vertical
92
+ align: align, # From Position::Horizontal
93
+ gravity: gravity, # From Position::Vertical
37
94
  # Position::Margin
38
95
  margin: margin,
39
96
  # Position::Offset
@@ -44,11 +101,40 @@ module Line
44
101
  offsetEnd: offset_end,
45
102
  # Size::Flex
46
103
  flex: flex,
47
- # Size::Image
104
+ # Size::Image (includes aspect_ratio, aspect_mode, and specific image size keywords)
105
+ size: size,
106
+ aspectRatio: aspect_ratio, # From option
107
+ aspectMode: aspect_mode, # From option
108
+ # Actionable
109
+ action: action&.to_h # From Actionable module
110
+ }.compact
111
+ end
112
+
113
+ def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
114
+ raise RequiredError, "url is required for an image component" if url.nil?
115
+
116
+ {
117
+ type: "image",
118
+ url: url,
119
+ # Position::Horizontal & Position::Vertical
120
+ align: align, # From Position::Horizontal
121
+ gravity: gravity, # From Position::Vertical
122
+ # Position::Margin
123
+ margin: margin,
124
+ # Position::Offset
125
+ position: position,
126
+ offset_top: offset_top,
127
+ offset_bottom: offset_bottom,
128
+ offset_start: offset_start,
129
+ offset_end: offset_end,
130
+ # Size::Flex
131
+ flex: flex,
132
+ # Size::Image (includes aspect_ratio, aspect_mode, and specific image size keywords)
48
133
  size: size,
49
- aspectRatio: aspect_ratio,
50
- aspectMode: aspect_mode,
51
- action: action&.to_h
134
+ aspect_ratio: aspect_ratio, # From option
135
+ aspect_mode: aspect_mode, # From option
136
+ # Actionable
137
+ action: action&.to_h # From Actionable module
52
138
  }.compact
53
139
  end
54
140
  end
@@ -4,32 +4,112 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # Define a partial that can be reused in flex message.
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
+ #
11
+ # Including this module into a component class gives it the {#partial!} method.
12
+ #
13
+ # @example Defining and using a partial
14
+ # # Define a reusable partial
15
+ # 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
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # # 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 ...
29
+ # end
30
+ #
31
+ # @see Partial
8
32
  module HasPartial
9
- def partial!(partial, **assigns)
10
- raise ArgumentError, "Not a partial" unless partial < Partial
33
+ # Renders a given {Partial} class within the current component's context.
34
+ #
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.
38
+ #
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}.
45
+ def partial!(partial_class, **assigns)
46
+ unless partial_class < Partial
47
+ raise ArgumentError,
48
+ "Argument must be a Line::Message::Builder::Flex::Partial class"
49
+ end
11
50
 
12
- prev = context.assigns
51
+ original_assigns = context.assigns
13
52
  context.assigns = assigns
14
- partial.new(context: self).call
15
- context.assigns = prev
53
+ # `self` here is the component instance (e.g., Box, Bubble) that includes HasPartial.
54
+ # This component instance becomes the `@context` for the Partial instance.
55
+ partial_class.new(context: self).call
56
+ context.assigns = original_assigns
16
57
  end
17
58
  end
18
59
 
19
- # The partial designed to be reused in flex message.
60
+ # The `Partial` class is an abstract base for creating reusable snippets
61
+ # of Flex Message content. To define a partial, create a new class that
62
+ # inherits from `Partial` and implements the {#call} instance method.
63
+ #
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
66
+ # 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).
69
+ #
70
+ # @abstract Subclass and implement {#call} to create a concrete partial.
71
+ # @see HasPartial For how to render partials.
20
72
  class Partial
73
+ # Initializes a new Partial instance.
74
+ # This is typically called by the {HasPartial#partial!} method.
75
+ #
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.
21
79
  def initialize(context:)
22
- @context = context
80
+ @context = context # The component (e.g., Box, Bubble) rendering this partial
23
81
  end
24
82
 
83
+ # This method must be implemented by subclasses to define the content of
84
+ # 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.
87
+ #
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).
91
+ #
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.
25
95
  def call(*)
26
- raise NotImplementedError, "The #{self.class} class must implement the call method"
96
+ raise NotImplementedError,
97
+ "The #{self.class.name} class must implement the #call method to define its content."
27
98
  end
28
99
 
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.
29
104
  def respond_to_missing?(method_name, include_private = false)
30
105
  @context.respond_to?(method_name, include_private) || super
31
106
  end
32
107
 
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.
33
113
  def method_missing(method_name, ...)
34
114
  if @context.respond_to?(method_name)
35
115
  @context.public_send(method_name, ...)