stonepath 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,42 +1,20 @@
1
1
  = stonepath
2
2
 
3
- Wednesday, August 25th UPDATE:
4
- The extraction is underway, and I have to write some generators to have some stuff make sense outside the scope of a real application.
3
+ State-based workflow for rails.
5
4
 
6
- I'm going to have a good article on the stomepath modeling methodology shortly. It is written, it just needs diagrams and an editor's touch.
5
+ UPDATE: The StonePath acl is being pulled out into a separate project called StoneWall.
6
+ Update #2: Rather than reinvent a common wheel, StonePath now relies on the sentient_user gem.
7
7
 
8
+ For more information, check out the stonepath.pdf in this same directory. Stonepath is in real use and under active development, so improved docs will be arriving in this location shortly.
8
9
 
9
- Sunday, August 9th UPDATE:
10
- Sorry for the delay. I just gave this talk again at eRubyCon.
11
10
 
12
- Some of these stateful workflow ideas are being used in a project for the
13
- DC Public School System, but the data store there is unconventional (a
14
- QuickBase database online), so the work being done on StonePath has taken a
15
- back seat. I expect this project to be live again shortly, starting simply
16
- with some wiki entries explaining the same concepts that I outline in the
17
- presentation (including WorkItem, Ownership, Assignment, Task, Permissions, and
18
- organizational modeling with groups and roles).
19
-
20
- ---
21
-
22
- This is a Stateful Workflow gem that is currently being pulled out of a
23
- real-world client project. I'm creating the repository before RailsConf 2009
24
- so interested people that attend my talk can watch and contribute.
25
-
26
- I'm also asking you to keep me honest - this extraction is real, but is likely
27
- to take a back seat to real billable client time. If I know there is interest
28
- (that is, if I see followers and if they nag me), this project is likely to get
29
- time over others.
30
-
31
-
32
-
33
- gem install bokmann-stonepath
11
+ gem install stonepath
34
12
 
35
13
  == LICENSE:
36
14
 
37
15
  (The MIT License)
38
16
 
39
- Copyright (c) 2009 FIXME full name
17
+ Copyright (c) 2009 David Bock
40
18
 
41
19
  Permission is hereby granted, free of charge, to any person obtaining
42
20
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -14,6 +14,9 @@ begin
14
14
  gemspec.description = "Stateful workflow modeling for Rails"
15
15
  gemspec.authors = ["David Bock"]
16
16
  gemspec.add_dependency('activerecord','>= 2.0.0')
17
+ gemspec.add_dependency('aasm','>= 2.1.3')
18
+ gemspec.add_dependency('sentient_user','>= 0.1.0')
19
+
17
20
  end
18
21
 
19
22
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -1,6 +1,4 @@
1
1
  module StonePath
2
2
  class Config
3
- cattr_accessor :acl_failure_mode
4
- cattr_accessor :acl_default_access
5
3
  end
6
4
  end
@@ -5,7 +5,7 @@ module StonePath
5
5
  has_many :logged_events, :as => :auditable, :class_name => "EventRecord"
6
6
 
7
7
  define_method :aasm_event_fired do |event_name, old_state_name, new_state_name|
8
- current_user_id = current_user && current_user.id
8
+ current_user_id = User.current
9
9
  self.logged_events.create(:event_name => event_name.to_s,
10
10
  :old_state_name => old_state_name.to_s,
11
11
  :new_state_name => new_state_name.to_s,
@@ -20,27 +20,7 @@ module StonePath
20
20
  has_many tasks, :as => :workitem
21
21
  end
22
22
 
23
- def stonepath_acl()
24
- require File.expand_path(File.dirname(__FILE__)) + "/acl.rb"
25
- cattr_accessor :acl
26
- self.acl = StonePath::ACL::Controller.new(self)
27
- yield self.acl
28
- end
29
-
30
- def self.define_attribute_methods_with_hook
31
- define_attribute_methods_without_hook
32
- acl.fix_aliases if defined? self.acl
33
- end
34
-
35
- class << self
36
- alias_method "define_attribute_methods_without_hook", "define_attribute_methods"
37
- alias_method "define_attribute_methods", "define_attribute_methods_with_hook"
38
- end
39
23
  end #base.instance_eval
40
-
41
- def allowed?(method)
42
- acl.allowed?(aasm_current_state, current_user, method)
43
- end
44
24
 
45
25
  # modifies to_xml do that it includes all the possible events from this state.
46
26
  # useful when you are using WorkItems as resources with ActiveResource
data/lib/stonepath.rb CHANGED
@@ -51,7 +51,6 @@ require "stonepath/config"
51
51
  # I want to move these into init.rb, but for some reason, the way rails processes the
52
52
  # init.rb chokes on load. I suspect this is an artificial issue because of the way the
53
53
  # embedded test app works.
54
- load File.expand_path( File.dirname(__FILE__)) + "/stonepath/extensions/activerecordbase.rb"
55
54
  load File.expand_path( File.dirname(__FILE__)) + '/stonepath/extensions/action_view.rb'
56
55
 
57
56
 
@@ -1,2 +1,8 @@
1
1
  class ApplicationController < ActionController::Base
2
+ include SentientController
3
+
4
+ def current_user
5
+ nil
6
+ end
7
+
2
8
  end
@@ -1,69 +1,28 @@
1
1
  class Case < ActiveRecord::Base
2
2
  include StonePath
3
3
 
4
- stonepath_workitem
4
+ stonepath_workitem do
5
5
 
6
- aasm_initial_state :pending
7
-
8
- aasm_state :pending
9
- aasm_state :in_process
10
- aasm_state :completed
11
-
12
- aasm_event :complete do
13
- transitions :to => :completed, :from => :in_process
14
- end
15
-
16
- aasm_event :activate do
17
- transitions :to => :in_process, :from => :pending
18
- end
19
-
20
- owned_by :user
21
- tasked_through :assignments
6
+ log_events
22
7
 
23
-
24
-
25
- stonepath_acl do |acl|
26
-
27
- acl.guard_method :save
28
- acl.guard_method :name=
29
- acl.guard_method :name
30
- acl.guard_method :regarding=
31
- acl.guard_method :regarding
8
+ aasm_initial_state :pending
32
9
 
33
- acl.method_group :modifiers, [:save, :name=, :regarding=]
34
- acl.method_group :accessors, [:name, :regarding]
10
+ aasm_state :pending
11
+ aasm_state :in_process
12
+ aasm_state :completed
35
13
 
36
- acl.for_state :in_process do |state|
37
- state.access_for_role :manager do |role|
38
- role.allow_method :name
39
- end
14
+ aasm_event :complete do
15
+ transitions :to => :completed, :from => :in_process
16
+ end
40
17
 
41
- state.access_for_role :peon do |role|
42
- role.deny_method :name=
43
- role.deny_method :regarding=
44
- end
45
-
46
- state.access_for_role :assistant_manager do |role|
47
- role.check_method_access :method_symbol do
48
- return false
49
- end
50
- end
51
- end
52
-
53
- acl.for_state :completed do |state|
54
- state.access_for_role :manager do |role|
55
- role.deny_method_group :modifiers
56
- end
57
-
58
- state.access_for_role :peon do |role|
59
- role.deny_method_group :modifiers
60
- role.deny_method_group :accessors
61
- end
62
-
63
- end
18
+ aasm_event :activate do
19
+ transitions :to => :in_process, :from => :pending
20
+ end
21
+
22
+ owned_by :user
23
+ tasked_through :assignments
64
24
  end
65
25
 
66
-
67
26
  def task_created(task)
68
27
  self.notification_method = "created"
69
28
  self.notified_id = task.id
@@ -0,0 +1,6 @@
1
+ class EventRecord < ActiveRecord::Base
2
+ belongs_to :auditable, :polymorphic => true
3
+
4
+ # the user_id field will point to the id of the current_user set by the controller
5
+ # hack. feel free to add our own finder here using that.
6
+ end
@@ -1,6 +1,7 @@
1
1
  class User < ActiveRecord::Base
2
2
  include StonePath
3
-
3
+ include SentientUser
4
+
4
5
  stonepath_workowner
5
6
  workowner_for :case
6
7
 
@@ -3,7 +3,8 @@ require File.join(File.dirname(__FILE__), 'boot')
3
3
  Rails::Initializer.run do |config|
4
4
 
5
5
  config.gem "aasm"
6
-
6
+ config.gem "sentient_user"
7
+
7
8
  config.cache_classes = false
8
9
  config.whiny_nils = true
9
10
  config.action_controller.session = { :key => "_myapp_session", :secret => "gwirofjweroijger8924rt2zfwehfuiwehb1378rifowenfoqwphf23" }
@@ -4,6 +4,7 @@ class CreateCases < ActiveRecord::Migration
4
4
  create_table :cases do |t|
5
5
  t.string :name
6
6
  t.string :regarding
7
+ t.string :description
7
8
  t.string :aasm_state
8
9
  t.string :notification_method
9
10
  t.integer :notified_id
@@ -0,0 +1,17 @@
1
+ class CreateEventRecords < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :event_records do |t|
4
+
5
+ t.references :auditable, :polymorphic => true
6
+ t.string :event_name
7
+ t.string :old_state_name
8
+ t.string :new_state_name
9
+ t.integer :user_id #This will rely on the acl controller hack
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table :event_records
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class LoggingTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ should "not have any log entries for a new case" do
9
+ c = Case.create
10
+ assert_equal(0, c.logged_events.size)
11
+ end
12
+
13
+ should "log when a transition happens" do
14
+ c = Case.create
15
+ assert_equal(0, c.logged_events.size)
16
+ c.activate!
17
+ assert_equal(1, c.logged_events.size)
18
+ assert_equal("activate", c.logged_events[0].event_name)
19
+ assert_equal("pending", c.logged_events[0].old_state_name)
20
+ assert_equal("in_process", c.logged_events[0].new_state_name)
21
+
22
+ c.complete!
23
+ assert_equal(2, c.logged_events.size)
24
+ assert_equal("complete", c.logged_events[1].event_name)
25
+ assert_equal("in_process", c.logged_events[1].old_state_name)
26
+ assert_equal("completed", c.logged_events[1].new_state_name)
27
+ end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stonepath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bock
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-07 00:00:00 -05:00
12
+ date: 2010-02-08 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,26 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 2.0.0
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: aasm
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.3
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: sentient_user
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.1.0
44
+ version:
25
45
  description: Stateful workflow modeling for Rails
26
46
  email: dbock@codesherpas.com
27
47
  executables: []
@@ -38,15 +58,9 @@ files:
38
58
  - Rakefile
39
59
  - VERSION
40
60
  - lib/stonepath.rb
41
- - lib/stonepath/acl.rb
42
- - lib/stonepath/acl/controller.rb
43
- - lib/stonepath/acl/role.rb
44
- - lib/stonepath/acl/state.rb
45
61
  - lib/stonepath/config.rb
46
- - lib/stonepath/controller_hooks.rb
47
62
  - lib/stonepath/event_logging.rb
48
63
  - lib/stonepath/extensions/action_view.rb
49
- - lib/stonepath/extensions/activerecordbase.rb
50
64
  - lib/stonepath/extensions/rails_generator_commands.rb
51
65
  - lib/stonepath/group.rb
52
66
  - lib/stonepath/role.rb
@@ -95,11 +109,11 @@ files:
95
109
  - script/destroy
96
110
  - script/generate
97
111
  - stonepath.pdf
98
- - test/acl_test.rb
99
112
  - test/app_root/app/controllers/application_controller.rb
100
113
  - test/app_root/app/models/assignment.rb
101
114
  - test/app_root/app/models/case.rb
102
115
  - test/app_root/app/models/custom_assignment.rb
116
+ - test/app_root/app/models/event_record.rb
103
117
  - test/app_root/app/models/user.rb
104
118
  - test/app_root/config/boot.rb
105
119
  - test/app_root/config/database.yml
@@ -113,12 +127,14 @@ files:
113
127
  - test/app_root/db/migrate/01_create_users.rb
114
128
  - test/app_root/db/migrate/02_create_assignments.rb
115
129
  - test/app_root/db/migrate/03_create_cases.rb
130
+ - test/app_root/db/migrate/04_create_event_records.rb
116
131
  - test/app_root/lib/console_with_fixtures.rb
117
132
  - test/app_root/log/.gitignore
118
133
  - test/app_root/script/console
119
134
  - test/custom_task_test.rb
120
135
  - test/fixtures/users.yml
121
136
  - test/group_test.rb
137
+ - test/logging_test.rb
122
138
  - test/role_test.rb
123
139
  - test/stonepath_test.rb
124
140
  - test/task_test.rb
@@ -154,11 +170,11 @@ signing_key:
154
170
  specification_version: 3
155
171
  summary: "Stonepath: stateful workflow modeling for rails"
156
172
  test_files:
157
- - test/acl_test.rb
158
173
  - test/app_root/app/controllers/application_controller.rb
159
174
  - test/app_root/app/models/assignment.rb
160
175
  - test/app_root/app/models/case.rb
161
176
  - test/app_root/app/models/custom_assignment.rb
177
+ - test/app_root/app/models/event_record.rb
162
178
  - test/app_root/app/models/user.rb
163
179
  - test/app_root/config/boot.rb
164
180
  - test/app_root/config/environment.rb
@@ -171,9 +187,11 @@ test_files:
171
187
  - test/app_root/db/migrate/01_create_users.rb
172
188
  - test/app_root/db/migrate/02_create_assignments.rb
173
189
  - test/app_root/db/migrate/03_create_cases.rb
190
+ - test/app_root/db/migrate/04_create_event_records.rb
174
191
  - test/app_root/lib/console_with_fixtures.rb
175
192
  - test/custom_task_test.rb
176
193
  - test/group_test.rb
194
+ - test/logging_test.rb
177
195
  - test/role_test.rb
178
196
  - test/stonepath_test.rb
179
197
  - test/task_test.rb
@@ -1,88 +0,0 @@
1
- module StonePath
2
- module ACL
3
- class Controller
4
-
5
- attr_reader :guarded_class_methods
6
- attr_reader :guarded_instance_methods
7
- attr_reader :states
8
- attr_reader :guarded_class
9
- attr_reader :method_groups
10
-
11
-
12
-
13
- def initialize(guarded_class)
14
- @guarded_class = guarded_class
15
- @guarded_methods = Array.new
16
- @states = Hash.new
17
- @method_groups = Hash.new
18
- @aliases = Array.new
19
- end
20
-
21
- def guard_method(method)
22
- @guarded_methods << method
23
- checked_method = checked_method_name(method)
24
- unchecked_method = unchecked_method_name(method)
25
-
26
- guarded_class.send(:define_method, checked_method) do |*args|
27
- if acl.allowed?(self, current_user, method)
28
- self.send(unchecked_method, *args)
29
- elsif StonePath::Config.acl_failure_mode == :exception
30
- raise "Access Violation"
31
- end
32
- end
33
-
34
- # Save our needed aliases until later
35
- @aliases << [unchecked_method, method]
36
- @aliases << [method, checked_method]
37
- end
38
-
39
- # fix up our saved aliases
40
- def fix_aliases
41
- @aliases.each do |src, target|
42
- guarded_class.send(:alias_method, src, target)
43
- end
44
- end
45
-
46
-
47
- def method_group(name, methods)
48
- @method_groups[name] = methods
49
- end
50
-
51
- def for_state(state_name)
52
- @states[state_name] = State.new(state_name, self) if @states[state_name].nil?
53
- yield @states[state_name]
54
- end
55
-
56
- def allowed?(guarded_object, user, method)
57
- return true if (guarded_object.nil? || user.nil? || method.nil?)
58
- state = guarded_object.aasm_state
59
- roles = user.respond_to?(:roles) ? user.roles : [user.role]
60
- roles.each { |role| break [true] if states[state].roles[role].allowed?(guarded_object, method) }.uniq[0]
61
- end
62
-
63
- private
64
-
65
- # TODO
66
- # these two methods are ugly - we need to dry up the repetition and make them work for ? methods.
67
- def checked_method_name(method)
68
- method_name = method.to_s
69
- if method_name.include?("=")
70
- method_name.gsub!("=","")
71
- return "#{method_name}_with_check="
72
- else
73
- return "#{method_name}_with_check"
74
- end
75
- end
76
-
77
- def unchecked_method_name(method)
78
- method_name = method.to_s
79
- if method_name.include?("=")
80
- method_name.gsub!("=","")
81
- return "#{method_name}_without_check="
82
- else
83
- return "#{method_name}_without_check"
84
- end
85
- end
86
- end
87
- end
88
- end
@@ -1,64 +0,0 @@
1
- module StonePath
2
- module ACL
3
- class Role
4
- attr_reader :name
5
- attr_reader :parent_state
6
- attr_reader :allowed_methods
7
- attr_reader :denied_methods
8
- attr_reader :checked_methods
9
-
10
- def initialize(name, parent_state)
11
- @name = name
12
- @parent_state = parent_state
13
- @allowed_methods = Array.new
14
- @denied_methods = Array.new
15
- @checked_methods = Hash.new
16
- end
17
-
18
- def allowed?(guarded_object, method_name)
19
- #first, we check to see if it is a checked method. If it is, we return whatever the check method says
20
- guard = checked_methods[method_name.to_s]
21
-
22
- return guard.call(guarded_object) if guard
23
-
24
- default_answer = !(Config.acl_default_access == :deny)
25
- checklist = default_answer ? denied_methods : allowed_methods
26
- checklist.include?(method_name.to_s) ? !default_answer : default_answer
27
- end
28
-
29
- def parent_acl
30
- @parent_state.parent_acl
31
- end
32
-
33
- def deny_method(method)
34
- @denied_methods << method
35
- end
36
-
37
- def allow_method(method)
38
- @allowed_methods << method
39
- end
40
-
41
- def check_method_access(method, &check)
42
- @checked_methods[method] = check
43
- end
44
-
45
- def deny_method_group(group)
46
- parent_acl.method_groups[group].each do |method|
47
- deny_method(method)
48
- end
49
- end
50
-
51
- def allow_method_group(method_group)
52
- parent_acl.method_groups[group].each do |method|
53
- allow_method(method)
54
- end
55
- end
56
-
57
- def check_method_group_access(method_group, &check)
58
- parent_acl.method_groups[group].each do |method|
59
- check_method_access(check)
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,20 +0,0 @@
1
- module StonePath
2
- module ACL
3
- class State
4
- attr_reader :name
5
- attr_reader :roles
6
- attr_reader :parent_acl
7
-
8
- def initialize(name, parent_acl)
9
- @name = name
10
- @parent_acl = parent_acl
11
- @roles = Hash.new
12
- end
13
-
14
- def access_for_role(role_name)
15
- @roles[role_name] = Role.new(role_name, self) if @roles[role_name].nil?
16
- yield @roles[role_name]
17
- end
18
- end
19
- end
20
- end
data/lib/stonepath/acl.rb DELETED
@@ -1,3 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__)) + "/acl/controller.rb"
2
- require File.expand_path(File.dirname(__FILE__)) + "/acl/role.rb"
3
- require File.expand_path(File.dirname(__FILE__)) + "/acl/state.rb"
@@ -1,11 +0,0 @@
1
- module StonePath
2
- module ControllerHooks
3
- def self.included(base)
4
- base.instance_eval do
5
- before_filter do |c|
6
- ActiveRecord::Base.current_user = current_user
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,7 +0,0 @@
1
- # for the ACL stuff to work, models need to know who the current user is.
2
- # This adds a current_user to ActiveRecord
3
- unless ActiveRecord::Base.respond_to?(:current_user)
4
- class ActiveRecord::Base
5
- cattr_accessor :current_user
6
- end
7
- end
data/test/acl_test.rb DELETED
@@ -1,17 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
-
4
-
5
- class TestACL < Test::Unit::TestCase
6
-
7
- def setup
8
- end
9
-
10
- context "our sample rails app" do
11
-
12
- should "include the Group module when declared as a stonepath_group" do
13
- assert true
14
- end
15
-
16
- end
17
- end