action_flow 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +78 -0
- data/Rakefile +45 -0
- data/VERSION.yml +5 -0
- data/examples/sample_migration.rb +9 -0
- data/lib/action_flow.rb +157 -0
- data/lib/flow_context_migration.rb +17 -0
- data/test/action_flow/context_test.rb +81 -0
- data/test/action_flow/helper_test.rb +74 -0
- data/test/action_flow_test.rb +38 -0
- data/test/test_helper.rb +22 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Justin Balthrop
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
= Flow
|
2
|
+
|
3
|
+
Flow is a simple workflow engine mixin for controllers that makes generating simple
|
4
|
+
or complex user flows as easy as it should be, rather than the painful process it usually is with MVC.
|
5
|
+
Also, it makes your controllers incredibly skinny, by moving the flow logic out of the
|
6
|
+
controller into a Flow::Context model.
|
7
|
+
|
8
|
+
== Usage:
|
9
|
+
|
10
|
+
Say you want to create a multi-page flow for new user signup. Assuming you keep the logic
|
11
|
+
for creating users in your User model, where it belongs, this is all the code you would need:
|
12
|
+
|
13
|
+
class NewUserFlowContext < Flow::Context
|
14
|
+
state :start do
|
15
|
+
if params[:already_a_member]
|
16
|
+
transition(:login)
|
17
|
+
else
|
18
|
+
transition(:signup)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
state :login
|
23
|
+
|
24
|
+
state :signup do
|
25
|
+
if User.name_taken?(params[:username])
|
26
|
+
flash[:error] = 'Username already taken. Please choose another.'
|
27
|
+
transition(:signup)
|
28
|
+
else
|
29
|
+
u = User.create(params)
|
30
|
+
data[:user_id] = u.id
|
31
|
+
transition(:confirm)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class NewUserController
|
37
|
+
include Flow
|
38
|
+
flow :new_user
|
39
|
+
end
|
40
|
+
|
41
|
+
Then you just create a template for each state in app/views/new_user. Flow also provides
|
42
|
+
two helper functions to make template creation really easy:
|
43
|
+
|
44
|
+
<%= flow_link_to "I already have an account", :already_a_member => true %>
|
45
|
+
|
46
|
+
<% flow_form_tag do -%>
|
47
|
+
<%= text_field_tag :name %>
|
48
|
+
<%= text_field_tag :email_address %>
|
49
|
+
<%= password_field_tag :password %>
|
50
|
+
<%= submit_tag %>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
These are just like link_to and form_tag, but they submit to the :next action, which is a
|
54
|
+
special action for transitioning between states. They also add the parameters necessary
|
55
|
+
to maintain context.
|
56
|
+
|
57
|
+
== Internals:
|
58
|
+
|
59
|
+
When you call the flow class macro in a controller, it creates an action for each state
|
60
|
+
defined in the specified flow context. It also creates an action called :next
|
61
|
+
for transitioning between states. The template helper functions (flow_link_to, and
|
62
|
+
flow_form_tag) submit a POST to this action. All transition logic is performed within next
|
63
|
+
and then the user is redirected using a GET to the correct state action. This means users
|
64
|
+
can safely use their browser back button to return to previous steps and use the forward
|
65
|
+
button if they change their mind.
|
66
|
+
|
67
|
+
Flow::Context is an ActiveRecord model. This allows flows to be easily persisted between
|
68
|
+
steps.
|
69
|
+
|
70
|
+
== Install:
|
71
|
+
|
72
|
+
sudo gem install flow
|
73
|
+
|
74
|
+
You also need to create a migration to make the flow_contexts table. See examples/sample_migration.rb
|
75
|
+
|
76
|
+
== License:
|
77
|
+
|
78
|
+
Copyright (c) 2009 Justin Balthrop, Geni.com; Published under The MIT License, see License.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = "action_flow"
|
9
|
+
s.summary = %Q{ A state-machine inspired mixin for controllers that makes creating flows and wizards dead simple. }
|
10
|
+
s.email = "code@justinbalthrop.com"
|
11
|
+
s.homepage = "http://github.com/ninjudd/action_flow"
|
12
|
+
s.description = "A state-machine inspired mixin for controllers that makes creating flows and wizards dead simple."
|
13
|
+
s.add_dependency('meta', '>= 0.1.1')
|
14
|
+
s.authors = ["Justin Balthrop"]
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::TestTask.new do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
24
|
+
t.verbose = false
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
rdoc.rdoc_dir = 'rdoc'
|
29
|
+
rdoc.title = 'action_flow'
|
30
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
31
|
+
rdoc.rdoc_files.include('README*')
|
32
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |t|
|
38
|
+
t.libs << 'test'
|
39
|
+
t.test_files = FileList['test/**/*_test.rb']
|
40
|
+
t.verbose = true
|
41
|
+
end
|
42
|
+
rescue LoadError
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/lib/action_flow.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'meta'
|
2
|
+
require 'active_record'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module ActionFlow
|
6
|
+
def flow(name)
|
7
|
+
helper ActionFlow::Helper
|
8
|
+
|
9
|
+
flow = "#{name}_flow_context".camelize.constantize
|
10
|
+
|
11
|
+
define_method(:context) do
|
12
|
+
@context ||= flow.find_or_create(params.delete(:k))
|
13
|
+
end
|
14
|
+
private :context
|
15
|
+
|
16
|
+
flow.states.each do |state|
|
17
|
+
define_method(state) do
|
18
|
+
context.at_state(state)
|
19
|
+
context.data.each do |key, value|
|
20
|
+
instance_variable_set("@#{key}", value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method(:next) do
|
26
|
+
context.at_state(params.delete(:state))
|
27
|
+
context.fire_transition(self)
|
28
|
+
redirect_to(:action => context.state, :k => context.key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Helper
|
33
|
+
def flow_link_to(name, options = {}, html_options = {})
|
34
|
+
options.merge!(flow_options)
|
35
|
+
html_options.merge!(:post => true)
|
36
|
+
link_to(name, options, html_options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def flow_form_tag(options = {}, html_options = {}, *args, &block)
|
40
|
+
options.merge!(flow_options)
|
41
|
+
html_options.merge!(:method => :post)
|
42
|
+
form_tag(options, html_options, *args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def flow_options
|
48
|
+
{:controller => controller.controller_name, :action => :next, :state => controller.context.state, :k => controller.context.key}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Context < ActiveRecord::Base
|
53
|
+
set_table_name 'flow_contexts'
|
54
|
+
|
55
|
+
def before_save
|
56
|
+
self.states = Marshal.dump(states)
|
57
|
+
self.state_data = Marshal.dump(state_data)
|
58
|
+
end
|
59
|
+
|
60
|
+
def after_save
|
61
|
+
self.states = Marshal.load(states)
|
62
|
+
self.state_data = Marshal.load(state_data)
|
63
|
+
end
|
64
|
+
|
65
|
+
def after_find
|
66
|
+
after_save
|
67
|
+
end
|
68
|
+
|
69
|
+
inheritable_class_attr :initial
|
70
|
+
initial :start
|
71
|
+
|
72
|
+
def self.find_or_create(key = nil)
|
73
|
+
context = find_by_key(key) if key
|
74
|
+
context || create(
|
75
|
+
:states => [initial],
|
76
|
+
:state_data => {},
|
77
|
+
:key => generate_key
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.generate_key
|
82
|
+
sha = Digest::SHA1::new
|
83
|
+
now = Time.now
|
84
|
+
sha.update(now.to_s)
|
85
|
+
sha.update(String(now.usec))
|
86
|
+
sha.update(String(rand))
|
87
|
+
sha.update(String($$))
|
88
|
+
sha.update('go with the flow')
|
89
|
+
sha.hexdigest
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.transitions
|
93
|
+
@transitions ||= {}
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.transition(state)
|
97
|
+
ancestors.each do |klass|
|
98
|
+
return if klass == ActionFlow::Context
|
99
|
+
next unless klass.respond_to?(:transitions)
|
100
|
+
transition = klass.transitions[state]
|
101
|
+
return transition if transition
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.states
|
106
|
+
transitions.keys
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.state(state, &block)
|
110
|
+
transitions[state] = block
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader :controller
|
114
|
+
delegate :params, :flash, :to => :controller
|
115
|
+
|
116
|
+
def at_state(state)
|
117
|
+
state = state.to_sym
|
118
|
+
if states.include?(state)
|
119
|
+
@state = state
|
120
|
+
states.slice!(states.index(state) + 1..-1)
|
121
|
+
else
|
122
|
+
raise InvalidState, "state #{state} not valid in this context"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def state
|
127
|
+
@state ||= states.last
|
128
|
+
end
|
129
|
+
|
130
|
+
def data
|
131
|
+
state_data[:state] ||= {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def fire_transition(controller)
|
135
|
+
@controller = controller
|
136
|
+
begin
|
137
|
+
transition = self.class.transition(state)
|
138
|
+
transition.bind(self).call
|
139
|
+
rescue TransitionFired
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def transition(state)
|
144
|
+
@state = state
|
145
|
+
self.states << state
|
146
|
+
self.save
|
147
|
+
raise TransitionFired
|
148
|
+
end
|
149
|
+
|
150
|
+
class InvalidState < StandardError; end
|
151
|
+
class TransitionFired < Exception; end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class ActionController::Base
|
156
|
+
extend ActionFlow
|
157
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class FlowContextMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :flow_contexts do |t|
|
4
|
+
t.timestamps
|
5
|
+
t.string :key
|
6
|
+
t.string :type
|
7
|
+
t.binary :states
|
8
|
+
t.binary :state_data
|
9
|
+
t.text :final_destination
|
10
|
+
end
|
11
|
+
add_index :flow_contexts, :key, :unique => true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :flow_contexts
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class ActionFlowContextTest < Test::Unit::TestCase
|
4
|
+
class ParentContext < ActionFlow::Context
|
5
|
+
state :start do
|
6
|
+
transition(:parent_one)
|
7
|
+
end
|
8
|
+
|
9
|
+
state :parent_one do
|
10
|
+
transition(:parent_two)
|
11
|
+
end
|
12
|
+
|
13
|
+
state :parent_two
|
14
|
+
end
|
15
|
+
|
16
|
+
class ChildContext < ParentContext
|
17
|
+
state :parent_two do
|
18
|
+
transition(:child_one)
|
19
|
+
end
|
20
|
+
|
21
|
+
state :child_one do
|
22
|
+
transition(:child_two)
|
23
|
+
end
|
24
|
+
|
25
|
+
state :child_two
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup
|
29
|
+
FlowContextMigration.down rescue nil
|
30
|
+
FlowContextMigration.up
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_find_or_create
|
34
|
+
count = ActionFlow::Context.count
|
35
|
+
ChildContext.find_or_create
|
36
|
+
assert count != ActionFlow::Context.count
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_state_serializaton
|
40
|
+
ChildContext.find_or_create
|
41
|
+
assert_equal [:start], ChildContext.first.states
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_state_data_serializaton
|
45
|
+
state_data = {:one=>1, :two=>2, (1..10)=>'1 to 10'}
|
46
|
+
ChildContext.find_or_create.update_attributes(:state_data=>state_data)
|
47
|
+
assert_equal state_data, ChildContext.first.state_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_singleton_transition_for_valid_state
|
51
|
+
assert_equal Proc, ChildContext.transition(:parent_one).class
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_singleton_transition_for_invalid_state
|
55
|
+
assert_equal nil, ChildContext.transition(:foo)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_singleton_states
|
59
|
+
assert_equal [:child_one, :child_two, :parent_two], ChildContext.states.sort_by {|ii| ii.to_s}
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_at_state_pops_subsequent_states_off_list
|
63
|
+
ctx = ChildContext.find_or_create
|
64
|
+
ctx.states << :parent_one
|
65
|
+
ctx.at_state(:start)
|
66
|
+
assert_equal [:start], ctx.states
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_state
|
70
|
+
ctx = ChildContext.find_or_create
|
71
|
+
ctx.states << :parent_one
|
72
|
+
assert_equal :parent_one, ctx.state
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_fire_transition
|
76
|
+
ctx = ChildContext.find_or_create
|
77
|
+
ctx.fire_transition(nil)
|
78
|
+
ctx.reload
|
79
|
+
assert_equal [:start, :parent_one], ctx.states
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
require 'action_view/test_case'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module ActionFlow
|
6
|
+
|
7
|
+
class HelperTest < ActionView::TestCase
|
8
|
+
tests ActionFlow::Helper
|
9
|
+
|
10
|
+
class TestController < ActionController::Base
|
11
|
+
attr_accessor :url
|
12
|
+
|
13
|
+
def initialize(url)
|
14
|
+
self.request = ActionController::TestRequest.new
|
15
|
+
self.url = ActionController::UrlRewriter.new(request, url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def context
|
19
|
+
@context ||= OpenStruct.new(:state=>'state', :key=>'key')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@controller = TestController.new({:controller=>'text', :action=>'show'})
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_flow_link_to
|
29
|
+
text = 'text'
|
30
|
+
options = {:option=>'value'}
|
31
|
+
html_options = {:html_option=>'value'}
|
32
|
+
with_route do
|
33
|
+
result = flow_link_to(text, options, html_options)
|
34
|
+
assert_match 'href="/test/next', result, 'href missing'
|
35
|
+
assert_match 'k=key', result, 'flow key missng'
|
36
|
+
assert_match 'state=state', result, 'flow state missing'
|
37
|
+
assert_match 'option=value', result
|
38
|
+
assert_match 'html_option="value"', result
|
39
|
+
assert_match '>text</a>', result, 'text missing'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_flow_form_tag
|
44
|
+
options = {:option=>'value'}
|
45
|
+
html_options = {:html_option=>'value'}
|
46
|
+
with_route do
|
47
|
+
result = flow_form_tag(options, html_options)
|
48
|
+
assert_match 'action="/test/next', result, 'action missing'
|
49
|
+
assert_match 'k=key', result, 'flow key missng'
|
50
|
+
assert_match 'state=state', result, 'flow state missing'
|
51
|
+
assert_match 'option=value', result
|
52
|
+
assert_match 'html_option="value"', result
|
53
|
+
assert_match 'method="post"', result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def with_route
|
60
|
+
with_routing do |set|
|
61
|
+
set.draw do |map|
|
62
|
+
map.connect ':controller/:action/:id'
|
63
|
+
end
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def protect_against_forgery?
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
end # class HelperTest
|
73
|
+
|
74
|
+
end # module ActionFlow
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class DummyFlowContext < ActionFlow::Context
|
4
|
+
state :start do
|
5
|
+
transition :one
|
6
|
+
end
|
7
|
+
|
8
|
+
state :one do
|
9
|
+
transition :two
|
10
|
+
end
|
11
|
+
|
12
|
+
state :two
|
13
|
+
end
|
14
|
+
|
15
|
+
class DummyController < ActionController::Base
|
16
|
+
flow :dummy
|
17
|
+
end
|
18
|
+
|
19
|
+
class ActionFlowTest < Test::Unit::TestCase
|
20
|
+
def test_flow_class_macro_includes_flow_helper
|
21
|
+
assert_equal true, DummyController.master_helper_module.include?(ActionFlow::Helper)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_flow_class_macro_adds_context_method
|
25
|
+
assert_equal true, DummyController.private_instance_methods.include?('context')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_flow_class_macro_adds_state_methods
|
29
|
+
DummyFlowContext.states.each do |state|
|
30
|
+
assert_equal true, DummyController.instance_methods.include?(state.to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_flow_class_macro_adds_next_method
|
35
|
+
assert_equal true, DummyController.instance_methods.include?('next')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'mocha'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
$:.unshift File.dirname(__FILE__), File.dirname(__FILE__) + '/../lib'
|
8
|
+
|
9
|
+
require 'action_flow'
|
10
|
+
require 'flow_context_migration'
|
11
|
+
require 'action_controller/test_process'
|
12
|
+
|
13
|
+
ActiveRecord::Base.establish_connection(
|
14
|
+
:adapter => "postgresql",
|
15
|
+
:host => "localhost",
|
16
|
+
:username => "postgres",
|
17
|
+
:password => "",
|
18
|
+
:database => "test"
|
19
|
+
)
|
20
|
+
|
21
|
+
ActiveRecord::Migration.verbose = false
|
22
|
+
ActiveRecord::Base.connection.client_min_messages = 'panic'
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_flow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Balthrop
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-23 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: meta
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.1
|
24
|
+
version:
|
25
|
+
description: A state-machine inspired mixin for controllers that makes creating flows and wizards dead simple.
|
26
|
+
email: code@justinbalthrop.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- LICENSE
|
37
|
+
- README.rdoc
|
38
|
+
- Rakefile
|
39
|
+
- VERSION.yml
|
40
|
+
- examples/sample_migration.rb
|
41
|
+
- lib/action_flow.rb
|
42
|
+
- lib/flow_context_migration.rb
|
43
|
+
- test/action_flow/context_test.rb
|
44
|
+
- test/action_flow/helper_test.rb
|
45
|
+
- test/action_flow_test.rb
|
46
|
+
- test/test_helper.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/ninjudd/action_flow
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: A state-machine inspired mixin for controllers that makes creating flows and wizards dead simple.
|
75
|
+
test_files:
|
76
|
+
- test/action_flow/context_test.rb
|
77
|
+
- test/action_flow/helper_test.rb
|
78
|
+
- test/action_flow_test.rb
|
79
|
+
- test/test_helper.rb
|
80
|
+
- examples/sample_migration.rb
|