-
-
Notifications
You must be signed in to change notification settings - Fork 542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow session configuation #57
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
require 'lotus/utils/string' | ||
|
||
module Lotus | ||
module Config | ||
# Sessions configuration | ||
# | ||
# @since x.x.x | ||
# @api private | ||
class Sessions | ||
|
||
def initialize(identifier = nil, options = {}) | ||
if identifier | ||
@identifier = identifier | ||
@options = options | ||
@enabled = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can safely assume that if |
||
else | ||
@enabled = false | ||
end | ||
end | ||
|
||
def enabled? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. def enabled?
!@identifier.nil?
end |
||
@enabled | ||
end | ||
|
||
def middleware | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
[resolve_identifier, [@options], nil] | ||
end | ||
|
||
private | ||
|
||
def resolve_identifier | ||
case @identifier | ||
when Symbol | ||
class_name = Utils::String.new(@identifier).classify | ||
"Rack::Session::#{class_name}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For perf reasons, we should extract this into a constant and interpolate it. RACK_NAMESPACE = 'Rack::Session::%{class_name}'.freeze
def resolve_identifier
# ..
RACK_NAMESPACE % { class_name: class_name }
end |
||
else | ||
@identifier | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
require 'lotus/config/assets' | ||
require 'lotus/config/routes' | ||
require 'lotus/config/mapping' | ||
require 'lotus/config/sessions' | ||
|
||
module Lotus | ||
# Configuration for a Lotus application | ||
|
@@ -444,6 +445,89 @@ def cookies(value = nil) | |
end | ||
end | ||
|
||
# Configure sessions | ||
# Enable sessions (disabled by default). | ||
# | ||
# This is part of a DSL, for this reason when this method is called with | ||
# an argument, it will set the corresponding instance variable. When | ||
# called without, it will return the already set value, or the default. | ||
# | ||
# Given Class as identifier it will be used as sessions middleware. | ||
# Given String as identifier it will be resolved as class name and used as | ||
# sessions middleware. | ||
# Given Symbol as identifier it is assumed it's name of the class under | ||
# Rack::Session namespace that will be used as sessions middleware | ||
# (e.g. :cookie for Rack::Session::Cookie). | ||
# | ||
# By default options include domain inferred from host configuration, and | ||
# secure flag inferred from scheme configuration. | ||
# | ||
# @overload sessions(identifier, options) | ||
# Sets the given value. | ||
# @param identifier [Class, String, Symbol] Rack middleware for sessions management | ||
# @param options [Hash] options to pass to sessions middleware | ||
# | ||
# @overload sessions(false) | ||
# Disables sessions | ||
# | ||
# @overload sessions | ||
# Gets the value. | ||
# @return [Lotus::Config::Sessions] sessions configuration | ||
# | ||
# @since x.x.x | ||
# | ||
# @see Lotus::Configuration#host | ||
# @see Lotus::Configuration#scheme | ||
# | ||
# @example Getting the value | ||
# require 'lotus' | ||
# | ||
# module Bookshelf | ||
# class Application < Lotus::Application | ||
# end | ||
# end | ||
# | ||
# Bookshelf::Application.configuration.sessions | ||
# # => #<Lotus::Config::Sessions:0x00000001ca0c28 @enabled=false> | ||
# | ||
# @example Setting the value with symbol | ||
# require 'lotus' | ||
# | ||
# module Bookshelf | ||
# class Application < Lotus::Application | ||
# configure do | ||
# sessions :cookie | ||
# end | ||
# end | ||
# end | ||
# | ||
# Bookshelf::Application.configuration.sessions | ||
# # => #<Lotus::Config::Sessions:0x00000001589458 @enabled=true, @identifier=:cookie, @options={:domain=>"localhost", :secure=>false}> | ||
# | ||
# @example Disabling previusly enabled sessions | ||
# require 'lotus' | ||
# | ||
# module Bookshelf | ||
# class Application < Lotus::Application | ||
# configure do | ||
# sessions :cookie | ||
# sessions false | ||
# end | ||
# end | ||
# end | ||
# | ||
# Bookshelf::Application.configuration.sessions | ||
# # => #<Lotus::Config::Sessions:0x00000002460d78 @enabled=false> | ||
# | ||
def sessions(identifier = nil, options = {}) | ||
if identifier.nil? | ||
@sessions ||= Config::Sessions.new | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
else | ||
options = { domain: host, secure: scheme == 'https' }.merge(options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this knowledge should be managed by |
||
@sessions = Config::Sessions.new(identifier, options) | ||
end | ||
end | ||
|
||
# Application load paths | ||
# The application will recursively load all the Ruby files under these paths. | ||
# | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,13 +16,7 @@ class Middleware | |
# @see Lotus::Configuration | ||
def initialize(configuration) | ||
@stack = [] | ||
|
||
if configuration.assets.enabled? | ||
use Rack::Static, { | ||
urls: configuration.assets.entries, | ||
root: configuration.assets | ||
} | ||
end | ||
@configuration = configuration | ||
end | ||
|
||
# Load the middleware stack | ||
|
@@ -38,6 +32,7 @@ def initialize(configuration) | |
def load!(application, namespace) | ||
@namespace = namespace | ||
@builder = ::Rack::Builder.new | ||
load_default_stack | ||
@stack.each { |m, args, block| @builder.use load_middleware(m), *args, &block } | ||
@builder.run application.routes | ||
|
||
|
@@ -80,5 +75,15 @@ def load_middleware(middleware) | |
middleware | ||
end | ||
end | ||
|
||
# @api private | ||
# @since x.x.x | ||
def load_default_stack | ||
@default_stack_loaded ||= begin | ||
@stack.unshift @configuration.sessions.middleware if @configuration.sessions.enabled? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to think about the order. Let's suppose for a second that we want to keep the current order, it can be achieved by swapping the two lines and use use assets
use sessions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assets and sessions would be in order that way, but all other middlewares added with |
||
@stack.unshift @configuration.assets.middleware if @configuration.assets.enabled? | ||
true | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
require 'test_helper' | ||
|
||
describe Lotus::Config::Sessions do | ||
describe '#enabled?' do | ||
it 'is false unless identifier is provided' do | ||
sessions = Lotus::Config::Sessions.new | ||
sessions.wont_be :enabled? | ||
end | ||
|
||
it 'is true when identifier is provided' do | ||
sessions = Lotus::Config::Sessions.new('Cookie') | ||
sessions.must_be :enabled? | ||
end | ||
end | ||
|
||
describe '#middleware' do | ||
it 'provided with identifier returns passed options' do | ||
options = { domain: 'example.com' } | ||
sessions = Lotus::Config::Sessions.new('Cookie', options) | ||
sessions.middleware[1].must_equal [options] | ||
end | ||
|
||
describe 'provided with class as identifier' do | ||
before do | ||
SessionMiddleware = Class.new | ||
end | ||
|
||
after do | ||
Object.send(:remove_const, :SessionMiddleware) | ||
end | ||
|
||
it 'returns class' do | ||
sessions = Lotus::Config::Sessions.new(SessionMiddleware) | ||
sessions.middleware.first.must_equal SessionMiddleware | ||
end | ||
end | ||
|
||
describe 'provided with string as identifier' do | ||
it 'returns string' do | ||
sessions = Lotus::Config::Sessions.new('SessionMiddleware') | ||
sessions.middleware.first.must_equal 'SessionMiddleware' | ||
end | ||
end | ||
|
||
describe 'provided with symbol as identifier' do | ||
it 'returns symbol as class name under Rack::Session namespace' do | ||
sessions = Lotus::Config::Sessions.new(:some_storage) | ||
sessions.middleware.first.must_equal 'Rack::Session::SomeStorage' | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -340,6 +340,72 @@ def to_pathname | |
|
||
end | ||
|
||
describe '#sessions' do | ||
describe 'when not previously set' do | ||
it 'is not enabled' do | ||
@configuration.sessions.wont_be :enabled? | ||
end | ||
end | ||
|
||
describe 'when set without options' do | ||
before do | ||
@configuration.sessions :cookie | ||
end | ||
|
||
it 'is enabled' do | ||
@configuration.sessions.must_be :enabled? | ||
end | ||
|
||
it 'returns the configured value for middleware' do | ||
@configuration.sessions.middleware.first.must_equal 'Rack::Session::Cookie' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This will make the test independent from the order that middleware are arranged. |
||
end | ||
|
||
it 'returns default values for options' do | ||
default_options = { domain: @configuration.host, secure: @configuration.scheme == 'https' } | ||
@configuration.sessions.middleware.flatten.must_include default_options | ||
end | ||
end | ||
|
||
describe 'when set with options' do | ||
before do | ||
@configuration.sessions :cookies, secure: true, expire_after: 2592000 | ||
end | ||
|
||
it 'merges default option values' do | ||
options = @configuration.sessions.middleware[1][0] | ||
options[:domain].must_equal @configuration.host | ||
options[:expire_after].must_equal 2592000 | ||
options[:secure].must_equal true | ||
end | ||
end | ||
|
||
describe 'when already set' do | ||
before do | ||
@configuration.sessions :cookie | ||
end | ||
|
||
describe 'if set with new configuration' do | ||
before do | ||
@configuration.sessions 'Rack::Session::Redis' | ||
end | ||
|
||
it 'returns it' do | ||
@configuration.sessions.middleware.first.must_equal 'Rack::Session::Redis' | ||
end | ||
end | ||
|
||
describe 'if set with false' do | ||
before do | ||
@configuration.sessions false | ||
end | ||
|
||
it 'is disabled' do | ||
@configuration.sessions.wont_be :enabled? | ||
end | ||
end | ||
end | ||
end | ||
|
||
describe 'assets' do | ||
describe "when not previously set" do | ||
it "is equal to public/ from the root directory" do | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
module SessionsApp | ||
class Application < Lotus::Application | ||
configure do | ||
# Activate sessions | ||
sessions :cookie, secret: '1234567890' | ||
|
||
routes do | ||
post '/set_session' , to: 'sessions#new' | ||
get '/get_session' , to: 'sessions#show' | ||
delete '/clear_session', to: 'sessions#destroy' | ||
end | ||
end | ||
|
||
load! | ||
end | ||
|
||
|
||
module Controllers::Sessions | ||
include SessionsApp::Controller | ||
|
||
action 'New' do | ||
def call(params) | ||
session[:name] = params[:name] | ||
self.body = "Session created for: #{session[:name]}" | ||
end | ||
end | ||
|
||
action 'Show' do | ||
def call(params) | ||
|
||
self.body = session[:name] | ||
end | ||
end | ||
|
||
action 'Destroy' do | ||
def call(params) | ||
name = session[:name] | ||
session.clear | ||
self.body = "Session cleared for: #{name}" | ||
end | ||
end | ||
end | ||
|
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to keep the Rack concern out of this. This object abstracts some file system knowledge, better to keep it simple.
Middleware
should be the right place for this.