Skip to content
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

Merged
merged 2 commits into from
Dec 1, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/lotus/config/assets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def enabled?
!@path.nil?
end

def middleware
Copy link
Member

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.

[Rack::Static, [{ urls: entries, root: @path }], nil]
end

def to_s
@path.to_s
end
Expand Down
42 changes: 42 additions & 0 deletions lib/lotus/config/sessions.rb
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can safely assume that if @identifier is set, the sessions are enabled.

else
@enabled = false
end
end

def enabled?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def enabled?
  !@identifier.nil?
end

@enabled
end

def middleware
Copy link
Member

Choose a reason for hiding this comment

The 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}"
Copy link
Member

Choose a reason for hiding this comment

The 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
84 changes: 84 additions & 0 deletions lib/lotus/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

else
options = { domain: host, secure: scheme == 'https' }.merge(options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this knowledge should be managed by Config::Sessions initialization process, and not being leaked in this context.

@sessions = Config::Sessions.new(identifier, options)
end
end

# Application load paths
# The application will recursively load all the Ruby files under these paths.
#
Expand Down
2 changes: 2 additions & 0 deletions lib/lotus/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'lotus/routes'
require 'lotus/routing/default'
require 'lotus/action/cookies'
require 'lotus/action/session'

module Lotus
# Load an application
Expand Down Expand Up @@ -41,6 +42,7 @@ def load_frameworks!
controller = Lotus::Controller.duplicate(application_module) do
default_format config.default_format
modules { include Lotus::Action::Cookies } if config.cookies
modules { include Lotus::Action::Session } if config.sessions.enabled?
end

application_module.const_set('Controller', controller)
Expand Down
19 changes: 12 additions & 7 deletions lib/lotus/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use @stack.unshift instead of Middleware#use? We should have only one way to add a middleware.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Middleware#use would append middleware to the stack, @stack.unshift prepends it. As this is lazily loaded default middlewares would end up as the last ones. It was the first middleware in the stack before, and I wanted to keep that order. I think it might be important for perf reasons - Rack::Static is short-circuiting the stack if it finds the file. On the other hand if we have sessions at the top of the stack other middlewares may access the session. If the order is not important I'll be happy with using Middleware#use here.

Copy link
Member

Choose a reason for hiding this comment

The 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.

use assets
use sessions

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 middleware.use in configuration would be before them.
I'll go with #use for now.

@stack.unshift @configuration.assets.middleware if @configuration.assets.enabled?
true
end
end
end
end
52 changes: 52 additions & 0 deletions test/config/sessions_test.rb
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
66 changes: 66 additions & 0 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@configuration.sessions.middleware.must_include 'Rack::Session::Cookie'

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
Expand Down
44 changes: 44 additions & 0 deletions test/fixtures/sessions/application.rb
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