зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-08-28 15:26:18 +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
|
||||
|
||||
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
|
||||
# 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}
|
||||
|
||||
# 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
|
||||
include Telegram::Bot::UpdatesController::Session
|
||||
# or just shortcut:
|
||||
@@ -222,12 +243,16 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
||||
end
|
||||
|
||||
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.
|
||||
# Override `session_key` method to change this behavior.
|
||||
|
||||
# In this case session will persist for user only in specific chat.
|
||||
# Same user in other chat will have different session.
|
||||
def session_key
|
||||
# In this case session will persist for user only in specific chat:
|
||||
"#{bot.username}:#{chat['id']}:#{from['id']}"
|
||||
"#{bot.username}:#{chat['id']}:#{from['id']}" if chat && from
|
||||
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
|
||||
```
|
||||
|
||||
@@ -8,19 +8,26 @@ module Telegram
|
||||
module Session
|
||||
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(*)
|
||||
super
|
||||
ensure
|
||||
session.commit
|
||||
session.commit if @_session
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def session
|
||||
@_session ||= begin
|
||||
key = session_key
|
||||
key ? SessionHash.new(self.class.session_store, key) : NullSessionHash.new
|
||||
end
|
||||
@_session ||= self.class.build_session(session_key)
|
||||
end
|
||||
|
||||
def session_key
|
||||
|
||||
@@ -54,4 +54,45 @@ RSpec.describe Telegram::Bot::UpdatesController::Session do
|
||||
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
|
||||
|
||||
Ссылка в новой задаче
Block a user