stonepath 0.2.0 → 0.3.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/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