Skip to content

Commit

Permalink
Added :accessorize option to dynamically generate accessors.
Browse files Browse the repository at this point in the history
  • Loading branch information
nakajima committed Feb 27, 2009
1 parent 6293a16 commit 5c23efe
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 20 deletions.
45 changes: 39 additions & 6 deletions README.md
Expand Up @@ -8,7 +8,43 @@ Try it out here: [flash.patnakajima.net](http://flash.patnakajima.net).

## Usage

You can see the app in the `example/` directory, but it's pretty simple:
Here's how to use it.

### Vanilla Rack apps

You can access flash entries via `env['rack-flash']`. You can treat it either like a regular
flash hash (via the `env['rack-flash'][:notice]` style), or you can pass the `:accessorize`
option when you call `use Rack::Flash`, and you'll be able to use the accessor style, which
generates accessors on the fly (`env['rack-flash'].notice` and the like).

Sample rack app:

get = proc { |env|
[200, {},
env['rack-flash'].notice || 'No flash set. Try going to /set'
]
}

set = proc { |env|
env['rack-flash'].notice = 'Hey, the flash was set! Now refresh and watch it go away.'
[302, {'Location' => '/'},
'You are being redirected.'
]
}

builder = Rack::Builder.new do
use Rack::Session::Cookie
use Rack::Flash, :accessorize => true

map('/set') { run set }
map('/') { run get }
end

Rack::Handler::Mongrel.run builder, :Port => 9292

### Sinatra

If you're using Sinatra, you can use the flash hash just like in Rails:

require 'sinatra/base'
require 'rack-flash'
Expand All @@ -28,8 +64,5 @@ You can see the app in the `example/` directory, but it's pretty simple:
run!
end

Run the app. Nothing on the home page by default. Then go here:

/greeting?q=it-works!

You'll see the flash message. Hit refresh, it'll be gone. As it should be.
If you've got any ideas on how to simplify access to the flash hash for non-Sinatra
apps, let me know. It still feels a bit off to me.
53 changes: 42 additions & 11 deletions lib/rack/flash.rb
@@ -1,3 +1,5 @@
require 'metaid'

module Rack
class Flash
class SessionUnavailable < StandardError; end
Expand All @@ -6,7 +8,11 @@ class SessionUnavailable < StandardError; end
class FlashHash
attr_reader :flagged

def initialize(store)
def initialize(store, opts={})
raise Rack::Flash::SessionUnavailable \
.new('Rack::Flash depends on session middleware.') unless store

@opts = opts
@store = store
@store[:__FLASH__] ||= {}
end
Expand Down Expand Up @@ -49,6 +55,17 @@ def inspect
def to_s
values.inspect
end

# Allow more convenient style for accessing flash entries (This isn't really
# necessary for Sinatra, since it provides the flash[:foo] hash that we're all
# used to. This is for vanilla Rack apps where it can be difficult to define
# such helpers as middleware).
def method_missing(sym, *args)
super unless @opts[:accessorize]
key = sym.to_s =~ /\w=$/ ? sym.to_s[0..-2] : sym
def_accessors(key)
send(sym, *args)
end

private

Expand All @@ -61,6 +78,18 @@ def set(key, val)
raise ArgumentError.new("Flash key must be symbol.") unless key.is_a?(Symbol)
cache[key] = values[key] = val
end

def def_accessors(key)
return if respond_to?(key)

meta_def(key) do
self[key]
end

meta_def("#{key}=") do |val|
self[key] = val
end
end

# Maintain an instance-level cache of retrieved flash entries. These entries
# will have been removed from the session, but are still available through
Expand All @@ -76,19 +105,21 @@ def values
end
end

def initialize(app)
@app = app
@app.class.class_eval do
def flash
raise Rack::Flash::SessionUnavailable \
.new('Rack::Flash depends on session middleware.') unless env['rack.session']
@flash ||= Rack::Flash::FlashHash.new(env['rack.session'])
end
end
def initialize(app, opts={})
@app, @opts = app, opts
end

def call(env)
env['rack-flash'] = Rack::Flash::FlashHash.new(env['rack.session'], @opts)
@app.call(env)
end
end
end

if defined?(Sinatra::Base)
Sinatra::Base.class_eval do
def flash
env['rack-flash']
end
end
end
5 changes: 4 additions & 1 deletion rack-flash.gemspec
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |s|
s.name = %q{rack-flash}
s.version = "0.0.2"
s.version = "0.0.5"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Pat Nakajima"]
Expand All @@ -22,10 +22,13 @@ Gem::Specification.new do |s|

if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rack>, [">= 0"])
s.add_runtime_dependency(%q<metaid>, [">= 0"])
else
s.add_dependency(%q<rack>, [">= 0"])
s.add_dependency(%q<metaid>, [">= 0"])
end
else
s.add_dependency(%q<rack>, [">= 0"])
s.add_dependency(%q<metaid>, [">= 0"])
end
end
2 changes: 1 addition & 1 deletion test/helper.rb
Expand Up @@ -3,7 +3,7 @@
require 'bacon'
require 'sinatra/test'
require 'sinatra/test/bacon'
require File.join(File.dirname(__FILE__), *%w[.. lib rack flash])
require File.join(File.dirname(__FILE__), *%w[.. lib rack-flash])

class String
[:green, :yellow, :red].each { |c| define_method(c) { self } }
Expand Down
34 changes: 33 additions & 1 deletion test/test_flash.rb
Expand Up @@ -57,6 +57,38 @@ def new_flash(entries={})
end
end

describe 'accessorize option' do
def new_flash(entries={})
flash = Rack::Flash::FlashHash.new(@fake_session, :accessorize => true)
entries.each { |key,val| flash[key] = val }
flash
end

it 'allows getters' do
flash = new_flash(:foo => 'bar')
flash.foo.should.equal('bar')
end

it 'allows setters' do
flash = new_flash
flash.fizz = 'buzz'
flash.fizz.should.equal('buzz')
end
end

it 'does not provide getters by default' do
proc {
new_flash(:foo => 'bar').foo
}.should.raise(NoMethodError)
end

it 'does not provide setters by default' do
proc {
flash = new_flash
flash.fizz = 'buzz'
}.should.raise(NoMethodError)
end

describe 'session integration' do
before do
mock_app {
Expand All @@ -80,7 +112,7 @@ def new_flash(entries={})
get '/view'
body.should.be.empty
end
end
end
end

# Testing sessions is a royal pain in the ass.
Expand Down

0 comments on commit 5c23efe

Please sign in to comment.