зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-07 11:25:52 +03:00
Drop .context_handler, .context_to_action! methods
Этот коммит содержится в:
@@ -12,6 +12,9 @@
|
|||||||
- `:telegram_bot` rspec tag is replaced with `telegram_bot: :rails`.
|
- `:telegram_bot` rspec tag is replaced with `telegram_bot: :rails`.
|
||||||
- __Breaking change__. Use bang-methods as actions for commands.
|
- __Breaking change__. Use bang-methods as actions for commands.
|
||||||
This prevents calling context contextual actions and payload specific actions with commands.
|
This prevents calling context contextual actions and payload specific actions with commands.
|
||||||
|
- __Breaking change__. Drop `.context_handler`, `.context_to_action!` methods.
|
||||||
|
Use pass action name directly to `#save_context`.
|
||||||
|
It's the same as `.context_to_action!` is enabled by default.
|
||||||
|
|
||||||
# 0.13.1
|
# 0.13.1
|
||||||
|
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -288,12 +288,12 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|||||||
end
|
end
|
||||||
|
|
||||||
# register context handlers to handle this context
|
# register context handlers to handle this context
|
||||||
context_handler :rename do |*words|
|
def rename(*words)
|
||||||
update_name words[0]
|
update_name words[0]
|
||||||
respond_with :message, text: 'Renamed!'
|
respond_with :message, text: 'Renamed!'
|
||||||
end
|
end
|
||||||
|
|
||||||
# You can do it in other way:
|
# You can use same action name as context name:
|
||||||
def rename!(name = nil, *)
|
def rename!(name = nil, *)
|
||||||
if name
|
if name
|
||||||
update_name name
|
update_name name
|
||||||
@@ -303,13 +303,6 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|||||||
respond_with :message, text: 'What name do you like?'
|
respond_with :message, text: 'What name do you like?'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# This will call #rename! like if it is called with message '/rename %text%'
|
|
||||||
context_handler :rename!
|
|
||||||
|
|
||||||
# If you have a lot of such methods you can call this method
|
|
||||||
# to use context value as action name for all contexts which miss handlers:
|
|
||||||
context_to_action!
|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -206,9 +206,11 @@ module Telegram
|
|||||||
[payload_type, [payload['data']]]
|
[payload_type, [payload['data']]]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Silently ignore unsupported messages.
|
# Silently ignore unsupported messages to not fail when user crafts
|
||||||
# Params are `action, *args`.
|
# an update with usupported command, callback query context, etc.
|
||||||
def action_missing(*)
|
def action_missing(action, *_args)
|
||||||
|
logger.debug { "The action '#{action}' is not defined in #{self.class.name}" } if logger
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
PAYLOAD_TYPES.each do |type|
|
PAYLOAD_TYPES.each do |type|
|
||||||
|
|||||||
@@ -2,51 +2,35 @@ module Telegram
|
|||||||
module Bot
|
module Bot
|
||||||
class UpdatesController
|
class UpdatesController
|
||||||
# Allows to store context in session and treat next message according to this context.
|
# Allows to store context in session and treat next message according to this context.
|
||||||
|
#
|
||||||
|
# It provides `save_context` method to store method name
|
||||||
|
# to be used as action for next update:
|
||||||
|
#
|
||||||
|
# def set_location!(*)
|
||||||
|
# save_context(:set_location_from_message)
|
||||||
|
# respond_with :message, text: 'Where are you?'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def set_location_from_messge(city = nil, *)
|
||||||
|
# # update
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # OR
|
||||||
|
# # This will support both `/set_location city_name`, and `/set_location`
|
||||||
|
# # with subsequent refinement.
|
||||||
|
# def set_location!(city = nil, *)
|
||||||
|
# if city
|
||||||
|
# # update
|
||||||
|
# else
|
||||||
|
# save_context(:set_location!)
|
||||||
|
# respond_with :message, text: 'Where are you?'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
module MessageContext
|
module MessageContext
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
include Session
|
include Session
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def context_handlers
|
|
||||||
@_context_handlers ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers handler for context.
|
|
||||||
#
|
|
||||||
# context_handler :rename do |*|
|
|
||||||
# resource.update!(name: payload['text'])
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # To run other action with all the callbacks:
|
|
||||||
# context_handler :rename do |*words|
|
|
||||||
# process(:rename, *words)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # Or just
|
|
||||||
# context_handler :rename, :your_action_to_call
|
|
||||||
# context_handler :rename # to call :rename
|
|
||||||
#
|
|
||||||
def context_handler(context = nil, action = nil, &block)
|
|
||||||
context &&= context.to_sym
|
|
||||||
if block
|
|
||||||
action = "_context_handler_#{context}"
|
|
||||||
define_method(action, &block)
|
|
||||||
end
|
|
||||||
context_handlers[context] = action || context
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :context_to_action
|
|
||||||
|
|
||||||
# Use it to use context value as action name for all contexts
|
|
||||||
# which miss handlers.
|
|
||||||
# For security reasons it supports only action methods and will
|
|
||||||
# raise AbstractController::ActionNotFound if context is invalid.
|
|
||||||
def context_to_action!
|
|
||||||
@context_to_action = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Action to clear context.
|
# Action to clear context.
|
||||||
def cancel!
|
def cancel!
|
||||||
# Context is already cleared in action_for_message
|
# Context is already cleared in action_for_message
|
||||||
@@ -54,10 +38,6 @@ module Telegram
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Context is read from the session to treat messages
|
|
||||||
# according to previous request.
|
|
||||||
attr_reader :context
|
|
||||||
|
|
||||||
# Controller may have multiple sessions, let it be possible
|
# Controller may have multiple sessions, let it be possible
|
||||||
# to select session for message context.
|
# to select session for message context.
|
||||||
def message_context_session
|
def message_context_session
|
||||||
@@ -68,13 +48,11 @@ module Telegram
|
|||||||
# it has higher priority than contextual action.
|
# it has higher priority than contextual action.
|
||||||
def action_for_message
|
def action_for_message
|
||||||
val = message_context_session.delete(:context)
|
val = message_context_session.delete(:context)
|
||||||
@context = val && val.to_sym
|
context = val && val.to_s
|
||||||
super || context && begin
|
super || context && begin
|
||||||
handler = handler_for_context
|
args = payload['text'].try!(:split) || []
|
||||||
if handler
|
action = action_for_message_context(context)
|
||||||
action_options = {type: :message_context, context: context}
|
[[action, type: :message_context, context: context], args]
|
||||||
[[handler, action_options], payload['text'].try!(:split) || []]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,18 +61,16 @@ module Telegram
|
|||||||
message_context_session[:context] = context
|
message_context_session[:context] = context
|
||||||
end
|
end
|
||||||
|
|
||||||
def handler_for_context
|
# Returns action name for message context. By default it's the same as context name.
|
||||||
self.class.context_handlers[context] || self.class.context_to_action && begin
|
# Raises AbstractController::ActionNotFound if action is not available.
|
||||||
action_name = context.to_s
|
# This differs from other cases where invalid actions are silently ignored,
|
||||||
unless action_method?(action_name)
|
# because message context is controlled by developer, and users are not able
|
||||||
raise AbstractController::ActionNotFound,
|
# to construct update to run any specific context.
|
||||||
"The action '#{action_name}' could not be set from context " \
|
def action_for_message_context(context)
|
||||||
"for #{self.class.name}. " \
|
action = context.to_s
|
||||||
'context_to_action! supports only action methods for security reasons. ' \
|
return action if action_method?(action)
|
||||||
'If you need to call this action use context_handler for it.'
|
raise AbstractController::ActionNotFound,
|
||||||
end
|
"The context action '#{action}' is not found in #{self.class.name}"
|
||||||
action_name
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ RSpec.describe Telegram::Bot::UpdatesController::MessageContext do
|
|||||||
include described_class
|
include described_class
|
||||||
|
|
||||||
attr_accessor :filter_done
|
attr_accessor :filter_done
|
||||||
before_action only: :redirect do
|
before_action only: :context_with_filter do
|
||||||
self.filter_done = true
|
self.filter_done = true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -17,19 +17,16 @@ RSpec.describe Telegram::Bot::UpdatesController::MessageContext do
|
|||||||
[:no_context, *args]
|
[:no_context, *args]
|
||||||
end
|
end
|
||||||
|
|
||||||
context_handler :block do |*args|
|
def handler_method(*args)
|
||||||
[:block_result, *args]
|
[:method_result_1, *args]
|
||||||
end
|
end
|
||||||
|
|
||||||
context_handler :redirect
|
def context_with_filter(*args)
|
||||||
context_handler :other_redirect, :redirect
|
[:method_result_2, *args]
|
||||||
|
|
||||||
def redirect(*args)
|
|
||||||
[:method_result, *args]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def action!(*args)
|
def action!(*args)
|
||||||
[:action_result, *args]
|
[:command_result, *args]
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -52,85 +49,59 @@ RSpec.describe Telegram::Bot::UpdatesController::MessageContext do
|
|||||||
it { should_not change { session[:context] } }
|
it { should_not change { session[:context] } }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when context is handled by block' do
|
context 'when context is handled by handler_method' do
|
||||||
before { session[:context] = :block }
|
before { session[:context] = :handler_method }
|
||||||
its(:call) { should eq [:block_result, *text.split] }
|
its(:call) { should eq [:method_result_1, *text.split] }
|
||||||
it { should_not change(controller, :filter_done) }
|
it { should_not change(controller, :filter_done) }
|
||||||
it { should change { session[:context] }.to nil }
|
it { should change { session[:context] }.to nil }
|
||||||
|
|
||||||
context 'when message has no text' do
|
context 'when message has no text' do
|
||||||
let(:payload) { {'audio' => {'file_id' => 123}} }
|
let(:payload) { {'audio' => {'file_id' => 123}} }
|
||||||
its(:call) { should eq [:block_result] }
|
its(:call) { should eq [:method_result_1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when message has new command' do
|
context 'when message has new command' do
|
||||||
let(:text) { '/action a s d' }
|
let(:text) { '/action a s d' }
|
||||||
its(:call) { should eq [:action_result, 'a', 's', 'd'] }
|
its(:call) { should eq [:command_result, 'a', 's', 'd'] }
|
||||||
it { should change { session[:context] }.to nil }
|
it { should change { session[:context] }.to nil }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when context is handled by short redirect' do
|
context 'when context is handled by short context_with_filter' do
|
||||||
before { session[:context] = :redirect }
|
before { session[:context] = :context_with_filter }
|
||||||
its(:call) { should eq [:method_result, *text.split] }
|
its(:call) { should eq [:method_result_2, *text.split] }
|
||||||
it { should change(controller, :filter_done).to true }
|
it { should change(controller, :filter_done).to true }
|
||||||
it { should change { session[:context] }.to nil }
|
it { should change { session[:context] }.to nil }
|
||||||
it { should change(controller, :callbacks_runs).to 1 }
|
it { should change(controller, :callbacks_runs).to 1 }
|
||||||
|
|
||||||
context 'when message has no text' do
|
context 'when message has no text' do
|
||||||
let(:payload) { {'audio' => {'file_id' => 123}} }
|
let(:payload) { {'audio' => {'file_id' => 123}} }
|
||||||
its(:call) { should eq [:method_result] }
|
its(:call) { should eq [:method_result_2] }
|
||||||
it { should change(controller, :filter_done).to true }
|
it { should change(controller, :filter_done).to true }
|
||||||
it { should change { session[:context] }.to nil }
|
it { should change { session[:context] }.to nil }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when context is handled by custom redirect' do
|
context 'when context is command-action`s name' do
|
||||||
before { session[:context] = :other_redirect }
|
before { session[:context] = :action! }
|
||||||
its(:call) { should eq [:method_result, *text.split] }
|
its(:call) { should eq [:command_result, *text.split] }
|
||||||
it { should change(controller, :filter_done).to true }
|
|
||||||
it { should change { session[:context] }.to nil }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when context is action`s name but not mapped' do
|
|
||||||
before { session[:context] = :action }
|
|
||||||
its(:call) { should eq [:no_context, payload] }
|
|
||||||
it { should_not change(controller, :filter_done) }
|
it { should_not change(controller, :filter_done) }
|
||||||
it { should change { session[:context] }.to nil }
|
it { should change { session[:context] }.to nil }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when context_to_action is true' do
|
context 'when context is not an action`s name' do
|
||||||
before { controller_class.context_to_action! }
|
before { session[:context] = :not_action }
|
||||||
|
it do
|
||||||
context 'when context is not set' do
|
should raise_error(AbstractController::ActionNotFound).
|
||||||
its(:call) { should eq [:no_context, payload] }
|
and change { session[:context] }.to nil
|
||||||
it { should_not change(controller, :filter_done) }
|
|
||||||
it { should_not change { session[:context] } }
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when context is action`s name but not mapped' do
|
context 'when context is invalid name' do
|
||||||
before { session[:context] = :action! }
|
before { session[:context] = :invalid }
|
||||||
its(:call) { should eq [:action_result, *text.split] }
|
it do
|
||||||
it { should_not change(controller, :filter_done) }
|
should raise_error(AbstractController::ActionNotFound).
|
||||||
it { should change { session[:context] }.to nil }
|
and change { session[:context] }.to nil
|
||||||
end
|
|
||||||
|
|
||||||
context 'when context is invalid' do
|
|
||||||
before { session[:context] = :invalid }
|
|
||||||
it 'raises error and clears context' do
|
|
||||||
expect do
|
|
||||||
should raise_error AbstractController::ActionNotFound
|
|
||||||
end.to change { session[:context] }.to nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when context is private method`s name' do
|
|
||||||
before { session[:context] = :not_action }
|
|
||||||
it 'raises error and clears context' do
|
|
||||||
expect do
|
|
||||||
should raise_error AbstractController::ActionNotFound
|
|
||||||
end.to change { session[:context] }.to nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user