self-control 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,10 @@
1
1
  = self-control
2
2
 
3
- Description goes here.
3
+ State machines are awesome but sometimes you need a little more. Like who should do what in order for it to move on? How many steps are left? How can I restfully trigger the next event? Self Control adds route helpers, controller actions and a dsl to turn your existing state machines into full workflows. It is designed to use rails 3 with ActiveModel and should work with any state machine with just a few extra methods.
4
+
5
+ == ToDo
6
+ * Documentation
7
+ * Tests
4
8
 
5
9
  == Note on Patches/Pull Requests
6
10
 
@@ -15,4 +19,4 @@ Description goes here.
15
19
 
16
20
  == Copyright
17
21
 
18
- Copyright (c) 2009 hexorx. See LICENSE for details.
22
+ Copyright (c) 2011 hexorx. See LICENSE for details.
data/Rakefile CHANGED
@@ -5,8 +5,8 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "self-control"
8
- gem.summary = %Q{Sinatra extention to easily add RESTful routes for your models.}
9
- gem.description = %Q{Self-Control provides default REST routes for your models.}
8
+ gem.summary = %Q{Self-Control builds on you existing state machine to create full work flows.}
9
+ gem.description = %Q{State machines are awesome but sometimes you need a little more. Like who should do what in order for it to move on? How many steps are left? How can I restfully trigger the next event? Self Control adds route helpers, controller actions and a dsl to turn your existing state machines into full workflows. It is designed to use rails 3 with ActiveModel and should work with any state machine with just a few extra methods.}
10
10
  gem.email = "hexorx@gmail.com"
11
11
  gem.homepage = "http://github.com/hexorx/self-control"
12
12
  gem.authors = ["hexorx"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
@@ -0,0 +1,50 @@
1
+ require 'active_support'
2
+ require 'self-control/exceptions'
3
+
4
+ module SelfControl
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ attr_reader :selfcontrol_list
9
+ attr_writer :selfcontrol_column
10
+
11
+ def selfcontrol(state=nil, options={}, &block)
12
+ state = (state || options[:when]).to_sym
13
+ @selfcontrol_list ||= {}
14
+ @selfcontrol_list[state] = SelfControl::FlowBuilder.new(state, &block)
15
+ end
16
+
17
+ alias :control_when :selfcontrol
18
+
19
+ def selfcontrol_column
20
+ @selfcontrol_column ||= 'state'
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+ def selfcontrol_list
26
+ self.class.selfcontrol_list
27
+ end
28
+
29
+ def selfcontrol_column
30
+ self.class.selfcontrol_column
31
+ end
32
+
33
+ def current_selfcontrol_state
34
+ (self.respond_to?(selfcontrol_column) ? self.send(selfcontrol_column) : :default).to_sym
35
+ end
36
+
37
+ def selfcontrol(state=current_selfcontrol_state)
38
+ return unless builder = self.selfcontrol_list[state]
39
+ @selfcontrol_flows ||= {}
40
+ @selfcontrol_flows[state] ||= SelfControl::Flow.new(builder,self)
41
+ end
42
+ end
43
+
44
+ autoload :FlowBuilder, 'self-control/flow_builder'
45
+ autoload :StepBuilder, 'self-control/step_builder'
46
+ autoload :Flow, 'self-control/flow'
47
+ autoload :Step, 'self-control/step'
48
+ end
49
+
50
+ require 'self-control/railtie' if defined?(Rails)
@@ -0,0 +1,82 @@
1
+ module SelfControl
2
+ module ControllerAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def steps_list
7
+ @selfcontrol_steps = valid_selfcontrol? ? selfcontrol.steps_for(selfcontrol_actor) : []
8
+ respond_with(@selfcontrol_steps)
9
+ end
10
+
11
+ def start_step
12
+ authorize_selfcontrol_step!
13
+
14
+ if action_methods.include?(params[:id])
15
+ send(params[:id])
16
+ else
17
+ render params[:id]
18
+ end
19
+ end
20
+
21
+ def do_step
22
+ authorize_selfcontrol_step!
23
+ selfcontrol.do!(params[:id],params[:choose],params[:self_control_step])
24
+ respond_with(selfcontrol_resource, :location => selfcontrol_resource_location)
25
+ end
26
+
27
+ protected
28
+
29
+ def default_selfcontrol_actor
30
+ respond_to?(:current_user) ? send(:current_user) : nil
31
+ end
32
+
33
+ def selfcontrol_actor
34
+ @selfcontrol_actor ||= default_selfcontrol_actor
35
+ end
36
+
37
+ def default_selfcontrol_resource
38
+ instance_name = params[:controller].sub("Controller", "").underscore.split('/').last.singularize
39
+ instance_variable_get("@#{instance_name}")
40
+ end
41
+
42
+ def selfcontrol_resource
43
+ @selfcontrol_resource ||= default_selfcontrol_resource
44
+ end
45
+
46
+ def selfcontrol_resource_location
47
+ selfcontrol_resource
48
+ end
49
+
50
+ def selfcontrol
51
+ @selfcontrol ||= valid_selfcontrol? ? selfcontrol_resource.selfcontrol : nil
52
+ end
53
+
54
+ def selfcontrol_step
55
+ @selfcontrol_step ||= selfcontrol.nil? ? nil : selfcontrol[params[:id].to_sym]
56
+ end
57
+
58
+ def valid_selfcontrol?
59
+ !selfcontrol_resource.nil? &&
60
+ selfcontrol_resource.respond_to?(:selfcontrol) &&
61
+ selfcontrol_resource.selfcontrol.is_a?(SelfControl::Flow)
62
+ end
63
+
64
+ def valid_selfcontrol_step?
65
+ !!selfcontrol_step
66
+ end
67
+
68
+ def allow_selfcontrol_step?
69
+ return false unless selfcontrol.is_a?(SelfControl::Flow)
70
+ selfcontrol.allow? selfcontrol_actor, :to => params[:id]
71
+ end
72
+
73
+ def authorize_selfcontrol_step!
74
+ raise SelfControl::NotImplemented.new() unless valid_selfcontrol_step?
75
+ unless allow_selfcontrol_step?
76
+ raise SelfControl::AccessDenied.new(nil, selfcontrol_actor, params[:step], params[:choose])
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ module SelfControl
2
+ class Error < StandardError; end
3
+
4
+ class NotImplemented < Error; end
5
+
6
+ class AccessDenied < Error
7
+ attr_reader :subject, :step, :action
8
+ attr_writer :default_message
9
+
10
+ def initialize(message=nil, subject=nil, step=nil, action=nil)
11
+ @message = message
12
+ @subject = subject
13
+ @step = step
14
+ @action = action
15
+ @default_message = "You are not authorized to access this step."
16
+ end
17
+
18
+ def to_s
19
+ @message || @default_message
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ module SelfControl
2
+ class Flow
3
+ attr_reader :builder, :model
4
+
5
+ delegate :actions, :total, :total_doable, :to => :@builder
6
+
7
+ def initialize(builder, model)
8
+ @builder = builder
9
+ @model = model
10
+ @step_map = {}
11
+ end
12
+
13
+ def [](name)
14
+ return if name.nil?
15
+ name = name.to_sym
16
+ @step_map[name] ||= steps.detect { |step| step.name == name }
17
+ end
18
+
19
+ def steps
20
+ @steps ||= @builder.steps.map { |step| SelfControl::Step.new(step, self) }
21
+ end
22
+
23
+ def steps_for(actor)
24
+ steps.select { |step| step.allow?(actor) }
25
+ end
26
+
27
+ def next_steps_for(actor)
28
+ steps.detect { |step| step.doable? && !step.done? && step.allow?(actor) }
29
+ end
30
+
31
+ def completed_steps
32
+ steps.select(&:done?)
33
+ end
34
+
35
+ def total_completed
36
+ completed_steps.size
37
+ end
38
+
39
+ def progress(options={})
40
+ count = (options[:of] || :all).to_sym == :doable ? total_doable : total
41
+
42
+ case (options[:as] || :percent).to_sym
43
+ when :percent
44
+ count == 0 ? 100 : (total_completed / count.to_f) * 100
45
+ when :decimal
46
+ count == 0 ? 1.0 : (total_completed / count)
47
+ when :array
48
+ [total_completed, count]
49
+ when :string
50
+ [total_completed, count].join(options[:with] || ' / ')
51
+ end
52
+ end
53
+
54
+ def done?
55
+ total_completed == total_doable
56
+ end
57
+
58
+ def allow?(actor=nil,options={})
59
+ return false unless step = self[options[:to]]
60
+ step.allow?(actor)
61
+ end
62
+
63
+ def do!(*args)
64
+ options = args.extract_options!
65
+ return false unless step = self[args[0]]
66
+ step.do!(args[1],options)
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,47 @@
1
+ module SelfControl
2
+ class FlowBuilder
3
+ attr_accessor :state, :steps, :actors, :actions
4
+
5
+ def initialize(state, &flow)
6
+ @state = state
7
+ @steps ||= []
8
+ @actors ||= []
9
+ @actions ||= []
10
+
11
+ instance_eval(&flow)
12
+
13
+ @actors = @actors.flatten.compact.uniq
14
+ @actions = @actions.flatten.compact.uniq
15
+ end
16
+
17
+ def total
18
+ @steps.size
19
+ end
20
+
21
+ def doable_steps
22
+ @steps.select(&:doable?)
23
+ end
24
+
25
+ def total_doable
26
+ doable_steps.size
27
+ end
28
+
29
+ private
30
+
31
+ def the(actor, options)
32
+ step = SelfControl::StepBuilder.new(actor, options)
33
+ @steps.push(step)
34
+ @actors.push(step.actor)
35
+ @actions.concat(step.actions)
36
+ end
37
+
38
+ def someone(options)
39
+ the(nil,options)
40
+ end
41
+
42
+ def they(options)
43
+ the(@actors.last,options)
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,8 @@
1
+ require 'self-control/route_helpers'
2
+ require 'self-control/controller_additions'
3
+ require 'self-control/step_adapter'
4
+
5
+ module SelfControl
6
+ class Railtie < Rails::Railtie
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ def selfcontrol
4
+ get 'steps', :action => 'steps_list', :as => 'self_control_steps_list'
5
+ get 'steps/:id', :action => 'start_step', :as => 'self_control_steps'
6
+ post 'steps/:id(/:choose)', :action => 'do_step', :as => 'self_control_steps'
7
+ end
8
+
9
+ def control_resources(*resources, &block)
10
+ resources(*resources) do
11
+ selfcontrol
12
+ yield if block_given?
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,60 @@
1
+ module SelfControl
2
+ class Step
3
+ delegate :name, :default, :actions, :doable?, :meta, :to => :@builder
4
+
5
+ attr_accessor :model
6
+
7
+ def initialize(builder, flow)
8
+ @builder = builder
9
+ @flow = flow
10
+ @model = flow.model
11
+ end
12
+
13
+ def done?
14
+ doable? && condition?(@builder.if || true) && !condition?(@builder.unless || false)
15
+ end
16
+
17
+ def condition?(attr)
18
+ return attr if attr.is_a?(Boolean)
19
+
20
+ result = if attr.is_a?(Proc)
21
+ attr.call(*[@model,@builder].take(attr.arity >= 0 ? attr.arity : 0))
22
+ elsif @model.respond_to?(attr)
23
+ @model.send(attr)
24
+ else
25
+ false
26
+ end
27
+
28
+ (result == 0 ? false : result.present?)
29
+ end
30
+
31
+ def actor_method
32
+ return if @builder.actor.nil?
33
+ @builder.actor.to_sym
34
+ end
35
+
36
+ def model_name
37
+ ActiveModel::Naming.singular(@model) if defined?(ActiveModel::Naming)
38
+ end
39
+
40
+ def actor
41
+ return if actor_method.nil?
42
+ @model.respond_to?(actor_method) ? @model.send(actor_method) : nil
43
+ end
44
+
45
+ def allow?(person=nil)
46
+ actor ? person == actor : true
47
+ end
48
+
49
+ def do!(*args)
50
+ options = args.extract_options!
51
+ action = options.delete(:choose) || args[0] || default
52
+ return false unless action && actions.include?(action.to_sym) && @model.respond_to?(action)
53
+
54
+ @model.attributes = options[model_name] if options[model_name]
55
+ @model.send(action)
56
+ @model.save
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,25 @@
1
+ module SelfControl
2
+ class Step
3
+ include ActiveModel::Conversion
4
+ include ActiveModel::Validations
5
+ extend ActiveModel::Naming
6
+ extend ActiveModel::Translation
7
+
8
+ def persisted?
9
+ false
10
+ end
11
+
12
+ def as_json(options={})
13
+ {
14
+ :name => name,
15
+ :actor => actor_method,
16
+ :actions => actions,
17
+ :default => default,
18
+ :doable => doable?,
19
+ :done => done?,
20
+ :meta => meta
21
+ }
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module SelfControl
2
+ class StepBuilder
3
+ attr_accessor :actor, :default, :actions, :name, :if, :unless, :meta
4
+
5
+ def initialize(actor, options)
6
+ @actor = actor
7
+ @actions = [*options.delete(:should)].flatten.compact.uniq.map(&:to_sym)
8
+ @default = options.delete(:action) || (@actions.size == 1 ? @actions.first : nil)
9
+ @name = (options.delete(:to) || @actions.join('_or_')).to_sym
10
+
11
+ @if = options.delete(:if)
12
+ @unless = options.delete(:unless)
13
+
14
+ @doable = !!(@if || @unless)
15
+
16
+ @meta = options
17
+ end
18
+
19
+ def doable?
20
+ @doable
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{self-control}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["hexorx"]
12
+ s.date = %q{2011-06-20}
13
+ s.description = %q{State machines are awesome but sometimes you need a little more. Like who should do what in order for it to move on? How many steps are left? How can I restfully trigger the next event? Self Control adds route helpers, controller actions and a dsl to turn your existing state machines into full workflows. It is designed to use rails 3 with ActiveModel and should work with any state machine with just a few extra methods.}
14
+ s.email = %q{hexorx@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/self-control.rb",
26
+ "lib/self-control/controller_additions.rb",
27
+ "lib/self-control/exceptions.rb",
28
+ "lib/self-control/flow.rb",
29
+ "lib/self-control/flow_builder.rb",
30
+ "lib/self-control/railtie.rb",
31
+ "lib/self-control/route_helpers.rb",
32
+ "lib/self-control/step.rb",
33
+ "lib/self-control/step_adapter.rb",
34
+ "lib/self-control/step_builder.rb",
35
+ "self-control.gemspec",
36
+ "spec/self-control_spec.rb",
37
+ "spec/spec_helper.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/hexorx/self-control}
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.6.2}
42
+ s.summary = %q{Self-Control builds on you existing state machine to create full work flows.}
43
+
44
+ if s.respond_to? :specification_version then
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
+ s.add_development_dependency(%q<rspec>, [">= 0"])
49
+ s.add_development_dependency(%q<yard>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<rspec>, [">= 0"])
52
+ s.add_dependency(%q<yard>, [">= 0"])
53
+ end
54
+ else
55
+ s.add_dependency(%q<rspec>, [">= 0"])
56
+ s.add_dependency(%q<yard>, [">= 0"])
57
+ end
58
+ end
59
+
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: self-control
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - hexorx
@@ -9,30 +15,38 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2009-10-13 00:00:00 -06:00
18
+ date: 2011-06-20 00:00:00 -06:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
22
  name: rspec
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
27
  - - ">="
22
28
  - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
23
32
  version: "0"
24
- version:
33
+ type: :development
34
+ version_requirements: *id001
25
35
  - !ruby/object:Gem::Dependency
26
36
  name: yard
27
- type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
30
40
  requirements:
31
41
  - - ">="
32
42
  - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
33
46
  version: "0"
34
- version:
35
- description: Self-Control provides default REST routes for your models.
47
+ type: :development
48
+ version_requirements: *id002
49
+ description: State machines are awesome but sometimes you need a little more. Like who should do what in order for it to move on? How many steps are left? How can I restfully trigger the next event? Self Control adds route helpers, controller actions and a dsl to turn your existing state machines into full workflows. It is designed to use rails 3 with ActiveModel and should work with any state machine with just a few extra methods.
36
50
  email: hexorx@gmail.com
37
51
  executables: []
38
52
 
@@ -43,12 +57,21 @@ extra_rdoc_files:
43
57
  - README.rdoc
44
58
  files:
45
59
  - .document
46
- - .gitignore
47
60
  - LICENSE
48
61
  - README.rdoc
49
62
  - Rakefile
50
63
  - VERSION
51
64
  - lib/self-control.rb
65
+ - lib/self-control/controller_additions.rb
66
+ - lib/self-control/exceptions.rb
67
+ - lib/self-control/flow.rb
68
+ - lib/self-control/flow_builder.rb
69
+ - lib/self-control/railtie.rb
70
+ - lib/self-control/route_helpers.rb
71
+ - lib/self-control/step.rb
72
+ - lib/self-control/step_adapter.rb
73
+ - lib/self-control/step_builder.rb
74
+ - self-control.gemspec
52
75
  - spec/self-control_spec.rb
53
76
  - spec/spec_helper.rb
54
77
  has_rdoc: true
@@ -56,29 +79,34 @@ homepage: http://github.com/hexorx/self-control
56
79
  licenses: []
57
80
 
58
81
  post_install_message:
59
- rdoc_options:
60
- - --charset=UTF-8
82
+ rdoc_options: []
83
+
61
84
  require_paths:
62
85
  - lib
63
86
  required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
64
88
  requirements:
65
89
  - - ">="
66
90
  - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
67
94
  version: "0"
68
- version:
69
95
  required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
70
97
  requirements:
71
98
  - - ">="
72
99
  - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
73
103
  version: "0"
74
- version:
75
104
  requirements: []
76
105
 
77
106
  rubyforge_project:
78
- rubygems_version: 1.3.5
107
+ rubygems_version: 1.6.2
79
108
  signing_key:
80
109
  specification_version: 3
81
- summary: Sinatra extention to easily add RESTful routes for your models.
82
- test_files:
83
- - spec/self-control_spec.rb
84
- - spec/spec_helper.rb
110
+ summary: Self-Control builds on you existing state machine to create full work flows.
111
+ test_files: []
112
+
data/.gitignore DELETED
@@ -1,5 +0,0 @@
1
- *.sw?
2
- .DS_Store
3
- coverage
4
- rdoc
5
- pkg