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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +41 -0
- data/CLAUDE.md +124 -0
- data/CONVENTIONS.md +36 -0
- data/README.md +61 -43
- data/claudekit.json +11 -0
- data/docs/rubrics/rdoc.md +169 -0
- data/lib/line/message/builder/actions/message.rb +52 -31
- data/lib/line/message/builder/actions/postback.rb +63 -33
- data/lib/line/message/builder/actions/uri.rb +139 -0
- data/lib/line/message/builder/actions.rb +6 -3
- data/lib/line/message/builder/base.rb +92 -67
- data/lib/line/message/builder/container.rb +64 -69
- data/lib/line/message/builder/context.rb +55 -80
- data/lib/line/message/builder/flex/actionable.rb +53 -32
- data/lib/line/message/builder/flex/box.rb +366 -79
- data/lib/line/message/builder/flex/bubble.rb +78 -51
- data/lib/line/message/builder/flex/builder.rb +47 -36
- data/lib/line/message/builder/flex/button.rb +96 -48
- data/lib/line/message/builder/flex/carousel.rb +57 -29
- data/lib/line/message/builder/flex/icon.rb +152 -0
- data/lib/line/message/builder/flex/image.rb +103 -42
- data/lib/line/message/builder/flex/partial.rb +58 -55
- data/lib/line/message/builder/flex/position.rb +187 -100
- data/lib/line/message/builder/flex/separator.rb +84 -0
- data/lib/line/message/builder/flex/size.rb +67 -47
- data/lib/line/message/builder/flex/span.rb +184 -0
- data/lib/line/message/builder/flex/text.rb +210 -54
- data/lib/line/message/builder/flex.rb +31 -26
- data/lib/line/message/builder/quick_reply.rb +174 -4
- data/lib/line/message/builder/text.rb +70 -3
- data/lib/line/message/builder/version.rb +1 -1
- data/lib/line/message/builder.rb +16 -13
- data/lib/line/message/rspec/matchers/have_flex_bubble.rb +1 -1
- data/lib/line/message/rspec/matchers/have_flex_component.rb +26 -5
- data/lib/line/message/rspec/matchers/have_flex_message.rb +1 -1
- data/lib/line/message/rspec/matchers/have_flex_separator.rb +20 -0
- data/lib/line/message/rspec/matchers/have_quick_reply.rb +1 -1
- data/lib/line/message/rspec/matchers/have_text_message.rb +1 -1
- data/lib/line/message/rspec/matchers.rb +1 -0
- data/llm.txt +367 -45
- data/release-please-config.json +3 -1
- metadata +12 -3
|
@@ -3,53 +3,49 @@
|
|
|
3
3
|
module Line
|
|
4
4
|
module Message
|
|
5
5
|
module Builder
|
|
6
|
-
# The
|
|
7
|
-
# within the
|
|
6
|
+
# The Base class serves as the foundation for all message builder classes
|
|
7
|
+
# within the Line::Message::Builder DSL. It provides core functionality
|
|
8
8
|
# for defining options, handling initialization, and delegating method calls
|
|
9
9
|
# to a context object.
|
|
10
10
|
#
|
|
11
11
|
# This class is not typically used directly but is inherited by specific
|
|
12
|
-
# message type builders (e.g.,
|
|
12
|
+
# message type builders (e.g., Text, Flex::Builder).
|
|
13
13
|
class Base
|
|
14
14
|
class << self
|
|
15
|
-
#
|
|
16
|
-
# @!parse extend ClassMethods
|
|
17
|
-
def inherited(subclass)
|
|
15
|
+
def inherited(subclass) # :nodoc:
|
|
18
16
|
super
|
|
19
17
|
subclass.extend ClassMethods
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
|
|
23
|
-
# The
|
|
24
|
-
# inherits from
|
|
21
|
+
# The ClassMethods module is automatically extended by any class that
|
|
22
|
+
# inherits from Base. It provides class-level methods for defining
|
|
25
23
|
# DSL options and configurations.
|
|
26
24
|
module ClassMethods
|
|
27
|
-
|
|
28
|
-
# using the {option} method.
|
|
29
|
-
#
|
|
30
|
-
# @return [Array<Symbol>] A list of defined option names.
|
|
31
|
-
# @!visibility private
|
|
32
|
-
def options
|
|
25
|
+
def options # :nodoc:
|
|
33
26
|
@options ||= []
|
|
34
27
|
end
|
|
35
28
|
|
|
36
29
|
# Defines a new option for the builder class.
|
|
30
|
+
#
|
|
37
31
|
# This method dynamically creates an instance method on the builder class
|
|
38
|
-
# with the given
|
|
32
|
+
# with the given name. When this instance method is called:
|
|
39
33
|
# - Without arguments, it returns the current value of the option, or
|
|
40
|
-
# the
|
|
41
|
-
# - With an argument, it sets the value of the option. If a
|
|
34
|
+
# the default value if not set.
|
|
35
|
+
# - With an argument, it sets the value of the option. If a validator
|
|
42
36
|
# is provided, the value is validated before being set.
|
|
43
37
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
38
|
+
# [name]
|
|
39
|
+
# The name of the option to define. This will also be the name of the
|
|
40
|
+
# generated instance method.
|
|
41
|
+
# [default]
|
|
42
|
+
# The default value for the option if not explicitly set.
|
|
43
|
+
# [validator]
|
|
44
|
+
# An optional validator object that must respond to +valid!+ method.
|
|
45
|
+
# If the value is invalid, the validator should raise an error.
|
|
46
|
+
#
|
|
47
|
+
# == Example
|
|
48
|
+
#
|
|
53
49
|
# class MyBuilder < Base
|
|
54
50
|
# option :color, default: "red"
|
|
55
51
|
# option :size, validator: SizeValidator.new
|
|
@@ -74,24 +70,39 @@ module Line
|
|
|
74
70
|
end
|
|
75
71
|
end
|
|
76
72
|
|
|
73
|
+
# The context object used for method delegation.
|
|
74
|
+
#
|
|
75
|
+
# When methods are called on the builder that it doesn't respond to,
|
|
76
|
+
# they are delegated to this context object if it responds to them.
|
|
77
|
+
# This allows accessing helper methods and variables from the surrounding
|
|
78
|
+
# environment within the builder DSL.
|
|
77
79
|
attr_reader :context
|
|
78
80
|
|
|
79
81
|
# Initializes a new instance of a builder class.
|
|
80
82
|
#
|
|
81
|
-
#
|
|
82
|
-
# Methods not defined in the builder
|
|
83
|
-
# if it responds to them. This allows
|
|
84
|
-
# accessing data from the surrounding environment
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# is
|
|
83
|
+
# [context]
|
|
84
|
+
# An optional external context object. Methods not defined in the builder
|
|
85
|
+
# will be delegated to this context if it responds to them. This allows
|
|
86
|
+
# for using helper methods or accessing data from the surrounding environment
|
|
87
|
+
# within the builder DSL.
|
|
88
|
+
# [options]
|
|
89
|
+
# A hash of options to set on the builder instance. These options are
|
|
90
|
+
# typically defined using the +option+ method in the builder class.
|
|
91
|
+
# [block]
|
|
92
|
+
# An optional block that is instance-eval'd within the new builder instance.
|
|
93
|
+
# This is the primary way the DSL is used to define message content.
|
|
94
|
+
#
|
|
95
|
+
# == Example
|
|
96
|
+
#
|
|
97
|
+
# builder = MyBuilder.new(context: view_context) do
|
|
98
|
+
# text "Hello from context"
|
|
99
|
+
# end
|
|
91
100
|
def initialize(context: nil, **options, &block)
|
|
92
101
|
@context = context
|
|
93
102
|
@quick_reply = nil
|
|
94
103
|
|
|
104
|
+
validate_options!(options)
|
|
105
|
+
|
|
95
106
|
self.class.options.each do |option|
|
|
96
107
|
send(option, options[option]) if options.key?(option)
|
|
97
108
|
end
|
|
@@ -100,15 +111,17 @@ module Line
|
|
|
100
111
|
end
|
|
101
112
|
|
|
102
113
|
# Defines a quick reply for the message.
|
|
114
|
+
#
|
|
103
115
|
# A quick reply consists of a set of buttons that are displayed along
|
|
104
116
|
# with the message, allowing users to make quick responses.
|
|
105
117
|
#
|
|
106
|
-
# The provided block is executed in the context of a new
|
|
118
|
+
# The provided block is executed in the context of a new QuickReply
|
|
107
119
|
# instance, where you can define the quick reply buttons.
|
|
108
120
|
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
121
|
+
# :yields: quick_reply
|
|
122
|
+
#
|
|
123
|
+
# == Example
|
|
124
|
+
#
|
|
112
125
|
# text_message do
|
|
113
126
|
# text "Please choose an option:"
|
|
114
127
|
# quick_reply do
|
|
@@ -120,34 +133,11 @@ module Line
|
|
|
120
133
|
@quick_reply = QuickReply.new(context: context, &)
|
|
121
134
|
end
|
|
122
135
|
|
|
123
|
-
|
|
124
|
-
# checking if the context object can respond to it.
|
|
125
|
-
# This is part of Ruby's mechanism for `method_missing` and is used
|
|
126
|
-
# here to enable delegation to the `context` object.
|
|
127
|
-
#
|
|
128
|
-
# @param method_name [Symbol] The name of the method.
|
|
129
|
-
# @param include_private [Boolean] Whether to include private methods
|
|
130
|
-
# in the search.
|
|
131
|
-
# @return [Boolean] `true` if the builder or its context can respond to
|
|
132
|
-
# the method, `false` otherwise.
|
|
133
|
-
# @!visibility private
|
|
134
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
136
|
+
def respond_to_missing?(method_name, include_private = false) # :nodoc:
|
|
135
137
|
context.respond_to?(method_name, include_private) || super
|
|
136
138
|
end
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
# `context` object. If the `context` object responds to the method,
|
|
140
|
-
# it is called. Otherwise, it behaves like the standard `method_missing`.
|
|
141
|
-
# This allows the DSL to feel more integrated with the surrounding code
|
|
142
|
-
# by making methods from the `context` directly available within the
|
|
143
|
-
# builder block.
|
|
144
|
-
#
|
|
145
|
-
# @param method_name [Symbol] The name of the missing method.
|
|
146
|
-
# @param ... [Object] Arguments passed to the method.
|
|
147
|
-
# @raise [NoMethodError] If neither the builder nor the context can
|
|
148
|
-
# handle the method call.
|
|
149
|
-
# @!visibility private
|
|
150
|
-
def method_missing(method_name, ...)
|
|
140
|
+
def method_missing(method_name, ...) # :nodoc:
|
|
151
141
|
if context.respond_to?(method_name)
|
|
152
142
|
context.public_send(method_name, ...)
|
|
153
143
|
else
|
|
@@ -155,6 +145,20 @@ module Line
|
|
|
155
145
|
end
|
|
156
146
|
end
|
|
157
147
|
|
|
148
|
+
# Converts the builder to a hash representation.
|
|
149
|
+
#
|
|
150
|
+
# The format of the hash depends on the mode (API or SDK v2) determined
|
|
151
|
+
# by the builder's configuration. Subclasses must implement +to_api+ and
|
|
152
|
+
# +to_sdkv2+ methods to provide the actual conversion logic.
|
|
153
|
+
#
|
|
154
|
+
# [return]
|
|
155
|
+
# A hash representation of the message in the appropriate format.
|
|
156
|
+
#
|
|
157
|
+
# == Example
|
|
158
|
+
#
|
|
159
|
+
# builder = Text.new { text "Hello" }
|
|
160
|
+
# builder.to_h
|
|
161
|
+
# # => { type: "text", text: "Hello" }
|
|
158
162
|
def to_h
|
|
159
163
|
return to_sdkv2 if sdkv2?
|
|
160
164
|
|
|
@@ -163,11 +167,32 @@ module Line
|
|
|
163
167
|
|
|
164
168
|
private
|
|
165
169
|
|
|
166
|
-
|
|
170
|
+
# Rejects options the builder does not declare.
|
|
171
|
+
#
|
|
172
|
+
# An undeclared option is almost always a typo or a property the LINE API
|
|
173
|
+
# spells differently. Ignoring it produces a message that is silently
|
|
174
|
+
# missing the styling that was asked for, which nothing downstream can
|
|
175
|
+
# detect, so it is refused here instead.
|
|
176
|
+
#
|
|
177
|
+
# [options]
|
|
178
|
+
# The hash of options passed to the constructor
|
|
179
|
+
#
|
|
180
|
+
# Raises ValidationError listing the unknown option and the ones this
|
|
181
|
+
# builder accepts.
|
|
182
|
+
def validate_options!(options) # :nodoc:
|
|
183
|
+
unknown = options.keys - self.class.options
|
|
184
|
+
return if unknown.empty?
|
|
185
|
+
|
|
186
|
+
raise ValidationError,
|
|
187
|
+
"Unknown option: #{unknown.join(", ")} for #{self.class}. " \
|
|
188
|
+
"Allowed options are: #{self.class.options.join(", ")}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def to_api # :nodoc:
|
|
167
192
|
raise NotImplementedError, "#{self.class} must implement #to_api"
|
|
168
193
|
end
|
|
169
194
|
|
|
170
|
-
def to_sdkv2
|
|
195
|
+
def to_sdkv2 # :nodoc:
|
|
171
196
|
raise NotImplementedError, "#{self.class} must implement #to_sdkv2"
|
|
172
197
|
end
|
|
173
198
|
end
|
|
@@ -3,53 +3,58 @@
|
|
|
3
3
|
module Line
|
|
4
4
|
module Message
|
|
5
5
|
module Builder
|
|
6
|
-
# The
|
|
6
|
+
# The +Container+ class is the top-level entry point for constructing a batch
|
|
7
7
|
# of LINE messages using the builder DSL. It acts as a holder for one or
|
|
8
|
-
# more individual message objects (such as
|
|
8
|
+
# more individual message objects (such as Text or Flex messages).
|
|
9
9
|
#
|
|
10
|
-
# When you use
|
|
11
|
-
# the context of a
|
|
10
|
+
# When you use <code>Line::Message::Builder.with {}</code>, you are operating within
|
|
11
|
+
# the context of a +Container+ instance. This container allows you to define
|
|
12
12
|
# multiple messages that can be sent together in a single API call to LINE,
|
|
13
13
|
# although the LINE API typically expects an array of message objects,
|
|
14
14
|
# which this container helps to build.
|
|
15
15
|
#
|
|
16
16
|
# Each message added to the container can also have its own quick reply.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
18
|
+
# == Example
|
|
19
|
+
#
|
|
20
|
+
# message_payload = Line::Message::Builder.with do
|
|
21
|
+
# text "Hello, this is the first message!"
|
|
22
|
+
# flex alt_text: "This is a Flex Message" do
|
|
23
|
+
# bubble do
|
|
24
|
+
# body do
|
|
24
25
|
# body.text "This is a Flex Message body."
|
|
25
26
|
# end
|
|
26
27
|
# end
|
|
27
28
|
# end
|
|
28
29
|
# end.build # => Returns an array of message hashes
|
|
29
30
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
31
|
+
# See also:
|
|
32
|
+
# - Base
|
|
33
|
+
# - Text
|
|
34
|
+
# - Flex::Builder
|
|
35
|
+
# - QuickReply
|
|
34
36
|
class Container
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
37
|
+
# [return]
|
|
38
|
+
# The context object, which can hold external data or
|
|
39
|
+
# helper methods accessible within the builder blocks.
|
|
38
40
|
attr_reader :context
|
|
39
41
|
|
|
40
42
|
# Initializes a new message container.
|
|
41
|
-
# This is typically not called directly but through
|
|
43
|
+
# This is typically not called directly but through <code>Line::Message::Builder.with</code>.
|
|
42
44
|
# The provided block is instance-eval'd, allowing DSL methods like
|
|
43
|
-
#
|
|
45
|
+
# #text and #flex to be called directly on the container instance.
|
|
44
46
|
#
|
|
45
|
-
#
|
|
47
|
+
# [context]
|
|
48
|
+
# An optional context object that can be used
|
|
46
49
|
# to share data or helper methods within the builder block. It's wrapped
|
|
47
|
-
# in a
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
+
# in a Context object.
|
|
51
|
+
# [mode]
|
|
52
|
+
# The mode to use for building messages. Can be either
|
|
53
|
+
# <code>:api</code> (default) for direct LINE Messaging API format or <code>:sdkv2</code> for
|
|
50
54
|
# LINE Bot SDK v2 compatible format.
|
|
51
|
-
#
|
|
52
|
-
#
|
|
55
|
+
# [block]
|
|
56
|
+
# A block containing DSL calls to define messages
|
|
57
|
+
# (e.g., <code>text "Hello"</code>, <code>flex { ... }</code>).
|
|
53
58
|
def initialize(context: nil, mode: :api, &block)
|
|
54
59
|
@messages = [] # Initializes an empty array to store message objects
|
|
55
60
|
@context = Context.new(context, mode:)
|
|
@@ -57,17 +62,20 @@ module Line
|
|
|
57
62
|
instance_eval(&block) if ::Kernel.block_given?
|
|
58
63
|
end
|
|
59
64
|
|
|
60
|
-
# Creates a new
|
|
65
|
+
# Creates a new Text message and adds it to this container.
|
|
61
66
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
+
# [text]
|
|
68
|
+
# The text content of the message.
|
|
69
|
+
# [option]
|
|
70
|
+
# Additional options for the text message,
|
|
71
|
+
# such as <code>:quick_reply</code>. See Text#initialize.
|
|
72
|
+
# [block]
|
|
73
|
+
# An optional block that will be instance-eval'd
|
|
74
|
+
# in the context of the new Text message instance. This can be used
|
|
67
75
|
# to add a quick reply to the text message.
|
|
68
|
-
# @return [Text] The newly created {Text} message object.
|
|
69
76
|
#
|
|
70
|
-
#
|
|
77
|
+
# == Example
|
|
78
|
+
#
|
|
71
79
|
# root.text "Hello, world!" do
|
|
72
80
|
# quick_reply do
|
|
73
81
|
# button action: :message, label: "Hi!", text: "Hi!"
|
|
@@ -77,24 +85,27 @@ module Line
|
|
|
77
85
|
@messages << Text.new(text, context: context, **options, &)
|
|
78
86
|
end
|
|
79
87
|
|
|
80
|
-
# Creates a new
|
|
88
|
+
# Creates a new Flex::Builder for constructing a Flex Message and adds it
|
|
81
89
|
# to this container. The block is mandatory and is used to define the
|
|
82
90
|
# content of the Flex Message using the Flex Message DSL.
|
|
83
91
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
92
|
+
# [option]
|
|
93
|
+
# Options for the Flex Message, primarily <code>:alt_text</code>.
|
|
94
|
+
# See Flex::Builder#initialize. It's important to provide +alt_text+.
|
|
95
|
+
# [block]
|
|
96
|
+
# A block that will be instance-eval'd in the context
|
|
97
|
+
# of the new Flex::Builder instance. This block is used to define the
|
|
88
98
|
# structure and content of the Flex Message (e.g., bubbles, carousels).
|
|
89
|
-
# @return [Flex::Builder] The newly created {Flex::Builder} object.
|
|
90
|
-
# @raise [ArgumentError] if `alt_text` is not provided in options (validation
|
|
91
|
-
# is typically within Flex::Builder).
|
|
92
99
|
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
100
|
+
# Raises ArgumentError if +alt_text+ is not provided in options (validation
|
|
101
|
+
# is typically within Flex::Builder).
|
|
102
|
+
#
|
|
103
|
+
# == Example
|
|
104
|
+
#
|
|
105
|
+
# flex alt_text: "Important information" do
|
|
106
|
+
# bubble do
|
|
107
|
+
# header { text "Header" }
|
|
108
|
+
# body { text "Body" }
|
|
98
109
|
# end
|
|
99
110
|
# end
|
|
100
111
|
def flex(**options, &)
|
|
@@ -102,47 +113,31 @@ module Line
|
|
|
102
113
|
end
|
|
103
114
|
|
|
104
115
|
# Converts all messages held by this container into their hash representations.
|
|
105
|
-
# This method iterates over each message object (e.g.,
|
|
106
|
-
# stored in the container and calls its
|
|
116
|
+
# This method iterates over each message object (e.g., Text, Flex::Builder)
|
|
117
|
+
# stored in the container and calls its +to_h+ method. The result is an
|
|
107
118
|
# array of hashes, where each hash represents a single LINE message object
|
|
108
119
|
# ready for JSON serialization and sending to the LINE Messaging API.
|
|
109
|
-
#
|
|
110
|
-
# @return [Array<Hash>] An array of message objects, each represented as a hash.
|
|
111
|
-
# This is the format expected by the LINE API for the `messages` field
|
|
112
|
-
# in a request body.
|
|
113
120
|
def build
|
|
114
121
|
@messages.map(&:to_h)
|
|
115
122
|
end
|
|
116
123
|
|
|
117
|
-
# Converts the array of message hashes (obtained from
|
|
124
|
+
# Converts the array of message hashes (obtained from #build) into a
|
|
118
125
|
# JSON string. This is a convenience method for serializing the messages
|
|
119
126
|
# payload.
|
|
120
127
|
#
|
|
121
|
-
#
|
|
128
|
+
# [arg]
|
|
129
|
+
# Optional arguments that are passed along to +to_json+
|
|
122
130
|
# method of the underlying array.
|
|
123
|
-
# @return [String] A JSON string representing the array of message objects.
|
|
124
131
|
def to_json(*args)
|
|
125
132
|
build.to_json(*args)
|
|
126
133
|
end
|
|
127
134
|
|
|
128
|
-
#
|
|
129
|
-
# This is part of Ruby's method_missing mechanism.
|
|
130
|
-
#
|
|
131
|
-
# @param method_name [Symbol] The name of the method being checked
|
|
132
|
-
# @param include_private [Boolean] Whether to include private methods
|
|
133
|
-
# @return [Boolean] True if the method exists in the context, false otherwise
|
|
135
|
+
# :nodoc:
|
|
134
136
|
def respond_to_missing?(method_name, include_private = false)
|
|
135
137
|
context.respond_to?(method_name, include_private) || super
|
|
136
138
|
end
|
|
137
139
|
|
|
138
|
-
#
|
|
139
|
-
# This allows helper methods defined in the context to be called directly
|
|
140
|
-
# from within the builder DSL.
|
|
141
|
-
#
|
|
142
|
-
# @param method_name [Symbol] The name of the method being called
|
|
143
|
-
# @param args [Array] The arguments passed to the method
|
|
144
|
-
# @return [Object] The result of calling the method on the context
|
|
145
|
-
# @raise [NoMethodError] If the method doesn't exist in the context
|
|
140
|
+
# :nodoc:
|
|
146
141
|
def method_missing(method_name, ...)
|
|
147
142
|
return context.send(method_name, ...) if context.respond_to?(method_name)
|
|
148
143
|
|
|
@@ -3,120 +3,93 @@
|
|
|
3
3
|
module Line
|
|
4
4
|
module Message
|
|
5
5
|
module Builder
|
|
6
|
-
# The
|
|
6
|
+
# The Context class is a crucial component of the Line::Message::Builder
|
|
7
7
|
# DSL, enabling a flexible and dynamic environment for message construction.
|
|
8
8
|
# It acts as a wrapper around an optional user-provided context object and
|
|
9
|
-
# manages a separate hash of
|
|
9
|
+
# manages a separate hash of +assigns+ (local variables for the DSL).
|
|
10
10
|
#
|
|
11
|
-
# The primary purposes of the
|
|
12
|
-
# 1.
|
|
11
|
+
# The primary purposes of the Context are:
|
|
12
|
+
# 1. To provide access to helper methods: If a user passes a context object
|
|
13
13
|
# (e.g., a Rails view context, a presenter, or any custom object) during
|
|
14
|
-
# builder initialization
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# to pass it explicitly or pollute the user-provided context.
|
|
14
|
+
# builder initialization, methods defined on that object become directly
|
|
15
|
+
# callable within the DSL blocks.
|
|
16
|
+
# 2. To allow local data storage: The +assigns+ hash allows for setting and
|
|
17
|
+
# retrieving temporary data that can be shared across different parts of
|
|
18
|
+
# a complex message construction block, without needing to pass it
|
|
19
|
+
# explicitly or pollute the user-provided context.
|
|
21
20
|
#
|
|
22
21
|
# Method calls within the DSL that are not defined on the builder objects
|
|
23
|
-
# themselves are resolved by
|
|
24
|
-
# - First, it checks if the method name corresponds to a key in the
|
|
25
|
-
# - If not found in
|
|
22
|
+
# themselves are resolved by Context in the following order:
|
|
23
|
+
# - First, it checks if the method name corresponds to a key in the +assigns+ hash.
|
|
24
|
+
# - If not found in +assigns+, it checks if the wrapped user-context object
|
|
26
25
|
# responds to the method.
|
|
27
26
|
# - If neither, the call proceeds up the normal Ruby method lookup chain.
|
|
28
27
|
#
|
|
29
28
|
# This mechanism allows for a clean and powerful way to integrate external logic
|
|
30
29
|
# and data into the message building process.
|
|
31
30
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
31
|
+
# == Example: Using a custom context
|
|
32
|
+
#
|
|
33
|
+
# class MyContext
|
|
34
34
|
# def current_user_name
|
|
35
35
|
# "Alice"
|
|
36
36
|
# end
|
|
37
37
|
# end
|
|
38
38
|
#
|
|
39
|
-
#
|
|
40
|
-
# Line::Message::Builder.with(
|
|
41
|
-
# #
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# # Using assigns
|
|
45
|
-
# assigns[:item_count] = 5
|
|
46
|
-
# root.text "You have #{assigns[:item_count]} items."
|
|
39
|
+
# context = MyContext.new
|
|
40
|
+
# Line::Message::Builder.with(context) do
|
|
41
|
+
# # current_user_name is resolved from context by Context
|
|
42
|
+
# text "Hello, #{current_user_name}!"
|
|
47
43
|
# end
|
|
48
44
|
class Context
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
45
|
+
# A hash for storing arbitrary data that can be accessed within the
|
|
46
|
+
# builder DSL. This is useful for temporary variables or shared state
|
|
47
|
+
# during message construction.
|
|
48
|
+
#
|
|
49
|
+
# == Example
|
|
50
|
+
#
|
|
51
|
+
# context.assigns[:user_id] = 123
|
|
52
|
+
# puts context.assigns[:user_id] # => 123
|
|
57
53
|
attr_accessor :assigns
|
|
58
54
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# @return [Symbol] Either `:api` for direct LINE Messaging API format
|
|
63
|
-
# or `:sdkv2` for LINE Bot SDK v2 compatible format.
|
|
55
|
+
# The mode in which the builder is operating. This affects how messages
|
|
56
|
+
# are formatted in the final output. Either +:api+ for direct LINE
|
|
57
|
+
# Messaging API format or +:sdkv2+ for LINE Bot SDK v2 compatible format.
|
|
64
58
|
attr_reader :mode
|
|
65
59
|
|
|
66
60
|
# Initializes a new Context object.
|
|
67
61
|
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# builder methods will be available.
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# compatible format.
|
|
62
|
+
# [context]
|
|
63
|
+
# An optional object whose methods will be made available within the DSL.
|
|
64
|
+
# If +nil+, only +assigns+ and standard builder methods will be available.
|
|
65
|
+
# [mode]
|
|
66
|
+
# The mode of the context, which can be +:api+ (default) for direct LINE
|
|
67
|
+
# Messaging API format or +:sdkv2+ for LINE Bot SDK v2 compatible format.
|
|
68
|
+
#
|
|
69
|
+
# == Example
|
|
70
|
+
#
|
|
71
|
+
# # With a custom context object
|
|
72
|
+
# my_context = MyHelpers.new
|
|
73
|
+
# context = Line::Message::Builder::Context.new(my_context, mode: :api)
|
|
74
|
+
#
|
|
75
|
+
# === Example: Without context
|
|
76
|
+
#
|
|
77
|
+
# # Without context, using only assigns
|
|
78
|
+
# context = Line::Message::Builder::Context.new(nil)
|
|
79
|
+
# context.assigns[:user_name] = "Alice"
|
|
74
80
|
def initialize(context, mode: :api)
|
|
75
81
|
@context = context
|
|
76
82
|
@assigns = {}
|
|
77
83
|
@mode = mode
|
|
78
84
|
end
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
# that instances of `Context` can respond to methods that are either:
|
|
82
|
-
# 1. Keys in the `@assigns` hash.
|
|
83
|
-
# 2. Methods to which the wrapped `@context` object responds.
|
|
84
|
-
#
|
|
85
|
-
# This ensures that `respond_to?` behaves consistently with how
|
|
86
|
-
# `method_missing` resolves method calls.
|
|
87
|
-
#
|
|
88
|
-
# @param method_name [Symbol] The name of the method being queried.
|
|
89
|
-
# @param include_private [Boolean] Whether to include private methods in
|
|
90
|
-
# the check.
|
|
91
|
-
# @return [Boolean] `true` if the context can handle the method,
|
|
92
|
-
# `false` otherwise.
|
|
93
|
-
# @!visibility private
|
|
94
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
86
|
+
def respond_to_missing?(method_name, include_private = false) # :nodoc:
|
|
95
87
|
@assigns.key?(method_name) ||
|
|
96
88
|
@context.respond_to?(method_name, include_private) || # Check @context directly
|
|
97
89
|
super
|
|
98
90
|
end
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
# The resolution order is:
|
|
102
|
-
# 1. If `method_name` is a key in the `@assigns` hash, its value is returned.
|
|
103
|
-
# 2. If the wrapped `@context` object responds to `method_name`, the call
|
|
104
|
-
# is delegated to `@context`.
|
|
105
|
-
# 3. Otherwise, `super` is called, allowing the standard Ruby method
|
|
106
|
-
# lookup to continue (which will likely result in a `NoMethodError`
|
|
107
|
-
# if the method is truly undefined).
|
|
108
|
-
#
|
|
109
|
-
# This is the core mechanism that allows DSL blocks to seamlessly access
|
|
110
|
-
# data from `assigns` or methods from the user-provided context.
|
|
111
|
-
#
|
|
112
|
-
# @param method_name [Symbol] The name of the invoked method.
|
|
113
|
-
# @param ... [Object] Arguments passed to the method.
|
|
114
|
-
# @return [Object, nil] The value from `@assigns`, the result of the
|
|
115
|
-
# delegated call to `@context`, or raises `NoMethodError` via `super`.
|
|
116
|
-
# @raise [NoMethodError] If the method is not found in `assigns` or
|
|
117
|
-
# on the wrapped context.
|
|
118
|
-
# @!visibility private
|
|
119
|
-
def method_missing(method_name, ...)
|
|
92
|
+
def method_missing(method_name, ...) # :nodoc:
|
|
120
93
|
return @assigns[method_name] if @assigns.key?(method_name)
|
|
121
94
|
# Check @context directly
|
|
122
95
|
return @context.public_send(method_name, ...) if @context.respond_to?(method_name)
|
|
@@ -126,8 +99,10 @@ module Line
|
|
|
126
99
|
|
|
127
100
|
# Checks if the current mode is set to SDK v2 compatibility.
|
|
128
101
|
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
102
|
+
# Returns +true+ if the mode is +:sdkv2+, +false+ otherwise.
|
|
103
|
+
#
|
|
104
|
+
# == Example
|
|
105
|
+
#
|
|
131
106
|
# if context.sdkv2?
|
|
132
107
|
# # Format message for LINE Bot SDK v2
|
|
133
108
|
# else
|