зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-05 02:35:50 +03:00
Make #session raise error when store is not configured
Add more explanation on sessions in readme
Этот коммит содержится в:
39
README.md
39
README.md
@@ -199,12 +199,33 @@ end
|
|||||||
|
|
||||||
#### Session
|
#### Session
|
||||||
|
|
||||||
There is support for sessions using `ActiveSupport::Cache` stores.
|
This API is very close to ActiveController's session API, but works different
|
||||||
|
under the hood. Cookies can not be used to store session id or
|
||||||
|
whole session (like CookieStore does). So it uses key-value store and `session_key`
|
||||||
|
method to build identifier from update.
|
||||||
|
|
||||||
|
Store can be one of numerous `ActiveSupport::Cache` stores.
|
||||||
|
While `:file_store` is suitable for development and single-server deployments
|
||||||
|
without heavy load, it doesn't scale well. Key-value databases with persistance
|
||||||
|
like Redis are more appropriate for production use.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# configure store in env files:
|
# In rails app store can be configured in env files:
|
||||||
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}
|
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}
|
||||||
|
|
||||||
|
# In other app it can be done for all controllers with:
|
||||||
|
Telegram::Bot::UpdatesController.session_store = :redis_store, {expires_in: 1.month}
|
||||||
|
# or for specific one:
|
||||||
|
OneOfUpdatesController.session_store = :redis_store, {expires_in: 1.month}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default session id is made from bot's username and `(from || chat)['id']`.
|
||||||
|
It means that session will be the same for updates from user in every chat,
|
||||||
|
and different for every user in the same group chat.
|
||||||
|
To change this behavior you can override `session_key` method, or even
|
||||||
|
define multiple sessions in single controller. For details see `Session` module.
|
||||||
|
|
||||||
|
```ruby
|
||||||
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
||||||
include Telegram::Bot::UpdatesController::Session
|
include Telegram::Bot::UpdatesController::Session
|
||||||
# or just shortcut:
|
# or just shortcut:
|
||||||
@@ -222,12 +243,16 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# By default it uses bot's username and user's id as a session key.
|
|
||||||
# Chat's id is used only when `from` field is empty.
|
# In this case session will persist for user only in specific chat.
|
||||||
# Override `session_key` method to change this behavior.
|
# Same user in other chat will have different session.
|
||||||
def session_key
|
def session_key
|
||||||
# In this case session will persist for user only in specific chat:
|
"#{bot.username}:#{chat['id']}:#{from['id']}" if chat && from
|
||||||
"#{bot.username}:#{chat['id']}:#{from['id']}"
|
end
|
||||||
|
|
||||||
|
# This session will be the same for all updates in chat.
|
||||||
|
def chat_session
|
||||||
|
@_chat_session ||= self.class.build_session(chat && "#{bot.username}:#{chat['id']}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,19 +8,26 @@ module Telegram
|
|||||||
module Session
|
module Session
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
# Builds session with given key and optional store (default to session_store).
|
||||||
|
# This way it's easier to define multiple custom sessions,
|
||||||
|
# ex. one for group chat and one for user.
|
||||||
|
def build_session(key, store = session_store)
|
||||||
|
raise 'session_store is not configured' unless store
|
||||||
|
key ? SessionHash.new(store, key) : NullSessionHash.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def process_action(*)
|
def process_action(*)
|
||||||
super
|
super
|
||||||
ensure
|
ensure
|
||||||
session.commit
|
session.commit if @_session
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def session
|
def session
|
||||||
@_session ||= begin
|
@_session ||= self.class.build_session(session_key)
|
||||||
key = session_key
|
|
||||||
key ? SessionHash.new(self.class.session_store, key) : NullSessionHash.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def session_key
|
def session_key
|
||||||
|
|||||||
@@ -54,4 +54,45 @@ RSpec.describe Telegram::Bot::UpdatesController::Session do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.build_session' do
|
||||||
|
subject { controller_class.build_session(key, *args) }
|
||||||
|
let(:key) {}
|
||||||
|
let(:args) { [] }
|
||||||
|
it { expect { subject }.to raise_error(/session_store is not configured/) }
|
||||||
|
|
||||||
|
shared_examples 'NullSessionHash when key is not present' do |store_proc|
|
||||||
|
it { should be_instance_of(described_class::NullSessionHash) }
|
||||||
|
|
||||||
|
context 'and key is present' do
|
||||||
|
let(:key) { :test_key }
|
||||||
|
it 'is valid SessionHash' do
|
||||||
|
expect(subject).to be_instance_of(described_class::SessionHash)
|
||||||
|
expect(subject.id).to eq key
|
||||||
|
expect(subject.instance_variable_get(:@store)).to be(instance_exec(&store_proc))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when store configured' do
|
||||||
|
before { controller_class.session_store = nil }
|
||||||
|
include_examples 'NullSessionHash when key is not present',
|
||||||
|
-> { controller_class.session_store }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when store is given' do
|
||||||
|
let(:args) { [double(:store)] }
|
||||||
|
include_examples 'NullSessionHash when key is not present', -> { args[0] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.session_store=' do
|
||||||
|
subject { ->(val) { controller_class.session_store = val } }
|
||||||
|
it 'casts to AS::Cache' do
|
||||||
|
expect { subject[:null_store] }.to change(controller_class, :session_store).
|
||||||
|
to(instance_of(ActiveSupport::Cache::NullStore))
|
||||||
|
expect { subject[nil] }.to change(controller_class, :session_store).
|
||||||
|
to(instance_of(ActiveSupport::Cache::MemoryStore))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user