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

Session strategy #32

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ There are three layers of strategies per feature:

* default
* database, to flip features site-wide for all users
* cookie, to flip features just for you (or someone else)
* cookie/session, uses cookies or session to flip features just for you (or someone else)

There is also a configurable system-wide default - !Rails.env.production?` works nicely.

Expand All @@ -30,10 +30,10 @@ Install

# Gemfile
gem "flip"

# Generate the model and migration
> rails g flip:install

# Run the migration
> rake db:migrate

Expand All @@ -49,7 +49,7 @@ class Feature < ActiveRecord::Base
include Flip::Declarable

# The recommended Flip strategy stack.
strategy Flip::CookieStrategy
strategy Flip::CookieStrategy # alternatively can use strategy Flip::CookieStrategy for session-based
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a copy-paste error in this string.

Perhaps this is better:

# Alternative: Use Flip::SessionStrategy to use Rails' encrypted sessions instead of cookies

strategy Flip::DatabaseStrategy
strategy Flip::DefaultStrategy
default false
Expand Down
1 change: 1 addition & 0 deletions lib/flip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
facade
feature_set
forbidden
session_strategy
}.each { |name| require "flip/#{name}" }

require "flip/engine" if defined?(Rails)
Expand Down
1 change: 1 addition & 0 deletions lib/flip/declarable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def feature(key, options = {})
# Adds a strategy for determining feature status.
def strategy(strategy)
FeatureSet.instance.add_strategy strategy
ActionController::Base.send(:include, "#{strategy}::Loader".constantize) if [Flip::SessionStrategy, Flip::CookieStrategy].include? strategy
end

# The default response, boolean or a Proc to be called.
Expand Down
5 changes: 0 additions & 5 deletions lib/flip/engine.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
module Flip
class Engine < ::Rails::Engine

initializer "flip.blarg" do
ActionController::Base.send(:include, Flip::CookieStrategy::Loader)
end

end
end
64 changes: 64 additions & 0 deletions lib/flip/session_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Uses session to determine feature state.
module Flip
class SessionStrategy < AbstractStrategy

def description
"Uses session cookie to apply only to your session."
end

def knows? definition
session.key? session_name(definition)
end

def on? definition
feature = session[session_name(definition)]
feature_value = feature.is_a?(Hash) ? feature['value'] : feature
feature_value === 'true'
end

def switchable?
true
end

def switch! key, on
session[session_name(key)] = on ? "true" : "false"
end

def delete! key
session.delete session_name(key)
end

def self.session= session
@session = session
end

def session_name(definition)
definition = definition.key unless definition.is_a? Symbol
"flip_#{definition}"
end

private

def session
result = self.class.instance_variable_get(:@session) || {}
end

# Include in ApplicationController to push cookies into CookieStrategy.
# Users before_filter and after_filter rather than around_filter to
# avoid pointlessly adding to stack depth.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: Users before_filter and after_filter should be Uses before_filter and after_filter

module Loader
extend ActiveSupport::Concern
included do
before_filter :flip_session_strategy_before
after_filter :flip_session_strategy_after
end
def flip_session_strategy_before
SessionStrategy.session = session
end
def flip_session_strategy_after
SessionStrategy.session = nil
end
end

end
end
110 changes: 110 additions & 0 deletions spec/session_strategy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
require "spec_helper"

class ControllerWithoutSessionStrategy; end
class ControllerWithSessionStrategy
def self.before_filter(_); end
def self.after_filter(_); end
def session; []; end
include Flip::SessionStrategy::Loader
end

describe Flip::SessionStrategy do

let(:session) do
{ strategy.session_name(:one) => "true",
strategy.session_name(:two) => "false" }
end
let(:strategy) do
Flip::SessionStrategy.new.tap do |s|
s.stub(:session) { session }
end
end

its(:description) { should be_present }
it { should be_switchable }
describe "session interrogration" do
context "enabled feature" do
specify "#knows? is true" do
strategy.knows?(:one).should be true
end
specify "#on? is true" do
strategy.on?(:one).should be true
end
end
context "disabled feature" do
specify "#knows? is true" do
strategy.knows?(:two).should be true
end
specify "#on? is false" do
strategy.on?(:two).should be false
end
end
context "feature with no session present" do
specify "#knows? is false" do
strategy.knows?(:three).should be false
end
specify "#on? is false" do
strategy.on?(:three).should be false
end
end
end

describe "session manipulation" do
it "can switch known features on" do
strategy.switch! :one, true
strategy.on?(:one).should be true
end
it "can switch unknown features on" do
strategy.switch! :three, true
strategy.on?(:three).should be true
end
it "can switch features off" do
strategy.switch! :two, false
strategy.on?(:two).should be false
end
it "can delete knowledge of a feature" do
strategy.delete! :one
strategy.on?(:one).should be false
strategy.knows?(:one).should be false
end
end

end

describe Flip::SessionStrategy::Loader do

it "adds filters when included in controller" do
ControllerWithoutSessionStrategy.tap do |klass|
klass.should_receive(:before_filter).with(:flip_session_strategy_before)
klass.should_receive(:after_filter).with(:flip_session_strategy_after)
klass.send :include, Flip::SessionStrategy::Loader
end
end

describe "filter methods" do
let(:strategy) { Flip::SessionStrategy.new }
let(:controller) { ControllerWithSessionStrategy.new }
describe "#flip_session_strategy_before" do
it "passes controller session to SessionStrategy" do
controller.should_receive(:session).and_return(strategy.session_name(:test) => "true")
expect {
controller.flip_session_strategy_before
}.to change {
[ strategy.knows?(:test), strategy.on?(:test) ]
}.from([false, false]).to([true, true])
end
end
describe "#flip_session_strategy_after" do
before do
Flip::SessionStrategy.session = { strategy.session_name(:test) => "true" }
end
it "passes controller session to SessionStrategy" do
expect {
controller.flip_session_strategy_after
}.to change {
[ strategy.knows?(:test), strategy.on?(:test) ]
}.from([true, true]).to([false, false])
end
end
end
end