acts-as-approvable 0.6.1

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.
Files changed (61) hide show
  1. data/.gitignore +6 -0
  2. data/Appraisals +18 -0
  3. data/CHANGELOG +10 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +50 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +108 -0
  8. data/Rakefile +81 -0
  9. data/VERSION +1 -0
  10. data/acts-as-approvable.gemspec +31 -0
  11. data/gemfiles/rails2.gemfile +7 -0
  12. data/gemfiles/rails2.gemfile.lock +50 -0
  13. data/gemfiles/rails30.gemfile +8 -0
  14. data/gemfiles/rails30.gemfile.lock +90 -0
  15. data/gemfiles/rails31.gemfile +8 -0
  16. data/gemfiles/rails31.gemfile.lock +101 -0
  17. data/gemfiles/rails32.gemfile +8 -0
  18. data/gemfiles/rails32.gemfile.lock +99 -0
  19. data/generators/acts_as_approvable/USAGE +3 -0
  20. data/generators/acts_as_approvable/acts_as_approvable_generator.rb +86 -0
  21. data/generators/acts_as_approvable/templates/approvals_controller.rb +97 -0
  22. data/generators/acts_as_approvable/templates/create_approvals.rb +26 -0
  23. data/generators/acts_as_approvable/templates/initializer.rb +3 -0
  24. data/generators/acts_as_approvable/templates/views/erb/_owner_select.html.erb +4 -0
  25. data/generators/acts_as_approvable/templates/views/erb/_table.html.erb +26 -0
  26. data/generators/acts_as_approvable/templates/views/erb/index.html.erb +15 -0
  27. data/generators/acts_as_approvable/templates/views/haml/_owner_select.html.haml +3 -0
  28. data/generators/acts_as_approvable/templates/views/haml/_table.html.haml +19 -0
  29. data/generators/acts_as_approvable/templates/views/haml/index.html.haml +13 -0
  30. data/init.rb +1 -0
  31. data/lib/acts-as-approvable/version.rb +3 -0
  32. data/lib/acts_as_approvable/acts_as_approvable.rb +291 -0
  33. data/lib/acts_as_approvable/approval.rb +179 -0
  34. data/lib/acts_as_approvable/error.rb +31 -0
  35. data/lib/acts_as_approvable/ownership.rb +117 -0
  36. data/lib/acts_as_approvable/railtie.rb +7 -0
  37. data/lib/acts_as_approvable.rb +66 -0
  38. data/lib/generators/acts_as_approvable/USAGE +1 -0
  39. data/lib/generators/acts_as_approvable/acts_as_approvable_generator.rb +73 -0
  40. data/lib/generators/acts_as_approvable/templates/approvals_controller.rb +97 -0
  41. data/lib/generators/acts_as_approvable/templates/create_approvals.rb +26 -0
  42. data/lib/generators/acts_as_approvable.rb +0 -0
  43. data/lib/generators/erb/acts_as_approvable_generator.rb +44 -0
  44. data/lib/generators/erb/templates/_owner_select.html.erb +4 -0
  45. data/lib/generators/erb/templates/_table.html.erb +26 -0
  46. data/lib/generators/erb/templates/index.html.erb +15 -0
  47. data/lib/generators/haml/acts_as_approvable_generator.rb +44 -0
  48. data/lib/generators/haml/templates/_owner_select.html.haml +3 -0
  49. data/lib/generators/haml/templates/_table.html.haml +19 -0
  50. data/lib/generators/haml/templates/index.html.haml +13 -0
  51. data/lib/tasks/acts_as_approvable.rake +4 -0
  52. data/rails/init.rb +1 -0
  53. data/test/acts_as_approvable_model_test.rb +428 -0
  54. data/test/acts_as_approvable_ownership_test.rb +132 -0
  55. data/test/acts_as_approvable_schema_test.rb +13 -0
  56. data/test/acts_as_approvable_test.rb +8 -0
  57. data/test/database.yml +7 -0
  58. data/test/schema.rb +44 -0
  59. data/test/support.rb +19 -0
  60. data/test/test_helper.rb +60 -0
  61. metadata +225 -0
@@ -0,0 +1,99 @@
1
+ PATH
2
+ remote: /Users/jlogsdon/Code/acts_as_approvable/acts_as_approvable
3
+ specs:
4
+ acts-as-approvable (0.6.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ actionpack (3.2.1)
10
+ activemodel (= 3.2.1)
11
+ activesupport (= 3.2.1)
12
+ builder (~> 3.0.0)
13
+ erubis (~> 2.7.0)
14
+ journey (~> 1.0.1)
15
+ rack (~> 1.4.0)
16
+ rack-cache (~> 1.1)
17
+ rack-test (~> 0.6.1)
18
+ sprockets (~> 2.1.2)
19
+ activemodel (3.2.1)
20
+ activesupport (= 3.2.1)
21
+ builder (~> 3.0.0)
22
+ activerecord (3.2.1)
23
+ activemodel (= 3.2.1)
24
+ activesupport (= 3.2.1)
25
+ arel (~> 3.0.0)
26
+ tzinfo (~> 0.3.29)
27
+ activesupport (3.2.1)
28
+ i18n (~> 0.6)
29
+ multi_json (~> 1.0)
30
+ appraisal (0.4.1)
31
+ bundler
32
+ rake
33
+ arel (3.0.0)
34
+ builder (3.0.0)
35
+ coderay (1.0.5)
36
+ erubis (2.7.0)
37
+ hike (1.2.1)
38
+ i18n (0.6.0)
39
+ journey (1.0.1)
40
+ json (1.6.5)
41
+ metaclass (0.0.1)
42
+ method_source (0.7.0)
43
+ mocha (0.10.4)
44
+ metaclass (~> 0.0.1)
45
+ multi_json (1.0.4)
46
+ pry (0.9.8)
47
+ coderay (~> 1.0.5)
48
+ method_source (~> 0.7)
49
+ slop (>= 2.4.3, < 3)
50
+ rack (1.4.1)
51
+ rack-cache (1.1)
52
+ rack (>= 0.4)
53
+ rack-ssl (1.3.2)
54
+ rack
55
+ rack-test (0.6.1)
56
+ rack (>= 1.0)
57
+ railties (3.2.1)
58
+ actionpack (= 3.2.1)
59
+ activesupport (= 3.2.1)
60
+ rack-ssl (~> 1.3.2)
61
+ rake (>= 0.8.7)
62
+ rdoc (~> 3.4)
63
+ thor (~> 0.14.6)
64
+ rake (0.9.2.2)
65
+ rdoc (3.12)
66
+ json (~> 1.4)
67
+ redcarpet (2.1.0)
68
+ shoulda (2.11.3)
69
+ simplecov (0.5.4)
70
+ multi_json (~> 1.0.3)
71
+ simplecov-html (~> 0.5.3)
72
+ simplecov-html (0.5.3)
73
+ slop (2.4.3)
74
+ sprockets (2.1.2)
75
+ hike (~> 1.2)
76
+ rack (~> 1.0)
77
+ tilt (~> 1.1, != 1.3.0)
78
+ sqlite3 (1.3.5)
79
+ thor (0.14.6)
80
+ tilt (1.3.3)
81
+ tzinfo (0.3.31)
82
+ yard (0.7.5)
83
+
84
+ PLATFORMS
85
+ ruby
86
+
87
+ DEPENDENCIES
88
+ activerecord (~> 3.2.0)
89
+ acts-as-approvable!
90
+ appraisal
91
+ mocha
92
+ pry
93
+ railties (~> 3.2.0)
94
+ rake
95
+ redcarpet
96
+ shoulda
97
+ simplecov
98
+ sqlite3
99
+ yard
@@ -0,0 +1,3 @@
1
+ Description:
2
+ Generates (but does not run) a migration to add an approvals table as well as a controller
3
+ and views for handling the approval queue.
@@ -0,0 +1,86 @@
1
+ class ActsAsApprovableGenerator < Rails::Generator::Base
2
+ default_options :base => 'ApplicationController'
3
+
4
+ def manifest
5
+ record do |m|
6
+ m.migration_template 'create_approvals.rb', 'db/migrate', :migration_file_name => 'create_approvals'
7
+
8
+ m.directory 'app/controllers'
9
+ m.template 'approvals_controller.rb', 'app/controllers/approvals_controller.rb'
10
+
11
+ m.directory 'app/views/approvals'
12
+ m.template "views/#{view_language}/index.html.#{view_language}", "app/views/approvals/index.html.#{view_language}"
13
+ m.template "views/#{view_language}/_table.html.#{view_language}", "app/views/approvals/_table.html.#{view_language}"
14
+
15
+ m.directory 'config/initializers'
16
+ m.template 'initializer.rb', 'config/initializers/acts_as_approvable.rb'
17
+
18
+ m.route route
19
+ end
20
+ end
21
+
22
+ protected
23
+ def view_language
24
+ options[:haml] ? 'haml' : 'erb'
25
+ end
26
+
27
+ def owner?
28
+ options[:owner].present?
29
+ end
30
+
31
+ def collection_actions
32
+ actions = [:index, :history]
33
+ actions << :mine if owner?
34
+ actions.map { |a| ":#{a}" }
35
+ end
36
+
37
+ def member_actions
38
+ actions = [:approve, :reject]
39
+ actions << :assign if owner?
40
+ actions.map { |a| ":#{a}" }
41
+ end
42
+
43
+ def route
44
+ route = 'map.resources :approvals, :only => [:index], :collection => ['
45
+ route << collection_actions.reject { |a| a == ':index' }.join(', ')
46
+ route << '], :member => ['
47
+ route << member_actions.join(', ')
48
+ route << ']'
49
+ end
50
+
51
+ def add_options!(opt)
52
+ opt.separator ''
53
+ opt.separator 'Options:'
54
+ opt.on('--base BASE', 'Base class for ApprovableController.') { |v| options[:base] = v }
55
+ opt.on('--haml', 'Generate HAML views instead of ERB.') { |v| options[:haml] = v }
56
+ opt.on('--owner [User]', 'Enable and, optionally, set the model for approval ownerships.') { |v| options[:owner] = v || 'User' }
57
+ end
58
+ end
59
+
60
+ # Gross! But Rails 2 only allows us to define resources with no options.
61
+ module Rails
62
+ module Generator
63
+ module Commands
64
+ class Create
65
+ def route(route)
66
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
67
+
68
+ logger.route route
69
+ unless options[:pretend]
70
+ gsub_file 'config/routes.rb', /#{Regexp.escape(sentinel)}/mi do |match|
71
+ "#{match}\n #{route}\n"
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ class Destroy
78
+ def route(route)
79
+ look_for = "\n #{Regexp.escape(route)}\n"
80
+ logger.route route
81
+ gsub_file 'config/routes.rb', /#{look_for}/mi, ''
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,97 @@
1
+ class ApprovalsController < <%= options[:base] %>
2
+ before_filter :setup_conditions, :only => [<%= collection_actions.join(', ') %>]
3
+ before_filter :setup_partial, :only => [<%= collection_actions.join(', ') %>]
4
+ before_filter :find_approval, :only => [<%= member_actions.join(', ') %>]
5
+ around_filter :json_wrapper, :only => [<%= member_actions.join(', ') %>]
6
+
7
+ def index
8
+ state = params[:state] =~ /^\d+$/ ? params[:state].to_i : Approval.enumerate_state('pending')
9
+ @conditions[:state] = state if state > -1
10
+
11
+ @approvals = Approval.all(:conditions => @conditions, :order => 'created_at ASC')
12
+ end
13
+
14
+ def history
15
+ @conditions[:state] = Approval.enumerate_states('approved', 'rejected')
16
+
17
+ @approvals = Approval.all(:conditions => @conditions, :order => 'created_at DESC')
18
+ render :index
19
+ end
20
+
21
+ <% if owner? %> def mine
22
+ @conditions[:owner_id] = current_user.id
23
+
24
+ @approvals = Approval.all(:conditions => @conditions, :order => 'created_at ASC')
25
+ render :index
26
+ end
27
+
28
+ def assign
29
+ if params[:approval][:owner_id].empty?
30
+ @approval.unassign
31
+ else
32
+ user = <%= options[:owner] %>.find(params[:approval][:owner_id])
33
+ @approval.assign(user)
34
+ end
35
+ end
36
+
37
+ <% end %> def approve
38
+ <% if owner? %> @approval.owner = current_user if respond_to?(:current_user)
39
+ <% end %> @approval.approve!
40
+ end
41
+
42
+ def reject
43
+ <% if owner? %> @approval.owner = current_user if respond_to?(:current_user)
44
+ <% end %> @approval.reject!(params[:reason])
45
+ end
46
+
47
+ private
48
+ def json_wrapper
49
+ json = {:success => false}
50
+
51
+ begin
52
+ json[:success] = yield
53
+ rescue ActsAsApprovable::Error => e
54
+ json[:message] = e.message
55
+ rescue
56
+ json[:message] = 'An unknown error occured'
57
+ end
58
+
59
+ respond_to do |format|
60
+ format.html do
61
+ flash[:error] = json[:message] if json[:message]
62
+ redirect_to :action => :index
63
+ end
64
+ format.json { render :json => json }
65
+ end
66
+ end
67
+
68
+ def setup_conditions
69
+ @conditions ||= {}
70
+
71
+ <% if owner? %> if params[:owner_id]
72
+ @conditions[:owner_id] = params[:owner_id]
73
+ @conditions[:owner_id] = nil if params[:owner_id] == 0
74
+ end
75
+ <% end %> if params[:item_type]
76
+ @conditions[:item_type] = params[:item_type]
77
+ end
78
+ end
79
+
80
+ # Check for the selected models partial, use the generic one if it doesn't exist
81
+ def setup_partial
82
+ @table_partial = @conditions.fetch(:item_type) { 'table' }
83
+
84
+ if @table_partial != 'table'
85
+ partial_path = Rails.root.join('app', 'views', 'approvals', "_#{@table_partial}.html.#{view_language}")
86
+ @table_partial = 'table' unless File.exist?(partial_path)
87
+ end
88
+ end
89
+
90
+ def find_approval
91
+ @approval = Approval.find(params[:id])
92
+ end
93
+
94
+ def view_language
95
+ ActsAsApprovable.view_language
96
+ end
97
+ end
@@ -0,0 +1,26 @@
1
+ class CreateApprovals < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :approvals do |t|
4
+ t.string :item_type, :null => false
5
+ t.integer :item_id, :null => false
6
+ t.string :event, :null => false
7
+ t.integer :state, :null => false, :default => 0
8
+ <% if options[:owner] %> t.integer :owner_id
9
+ <% end %> t.text :object
10
+ t.text :reason
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :approvals, [:state, :event]
16
+ add_index :approvals, [:item_type, :item_id]
17
+ <% if options[:owner] %> add_index :approvals, [:owner_id]
18
+ <% end %> end
19
+
20
+ def self.down
21
+ remove_index :approvals, [:state, :event]
22
+ remove_index :approvals, [:item_type, :item_id]
23
+ <% if options[:owner] %> remove_index :approvals, [:owner_id]
24
+ <% end %> drop_table :approvals
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ ActsAsApprovable.view_language = '<%= view_language %>'
2
+ <% if owner? %>ActsAsApprovable::Ownership.configure<% if options[:owner] != 'User' %>(:owner => <%= options[:owner].constantize %>)<% end %>
3
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%% form_for(approval, :url => assign_approval_path(approval.id), :html => {:class => 'assignment'}) do |f| %>
2
+ <%%= f.select(:owner_id, options_for_select(Approval.options_for_available_owners(true), approval.owner_id)) %>
3
+ <%%= f.submit('Set') %>
4
+ <%% end %>
@@ -0,0 +1,26 @@
1
+ <table>
2
+ <thead>
3
+ <tr>
4
+ <th>Type</th>
5
+ <th>Record</th>
6
+ <% if owner? %> <th>Owner</th>
7
+ <% end %> <th>State</th>
8
+ <th>&nbsp;</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <%% @approvals.each do |approval| %>
13
+ <tr>
14
+ <td><%%= approval.item_type.classify %></td>
15
+ <td><%%= approval.item.try(:to_s) || approval.item.id %></td>
16
+ <% if owner? %> <td><%%= render :partial => 'owner_select', :locals => {:approval => approval} %></td>
17
+ <% end %> <td><%%= approval.state %></td>
18
+ <td class='actions'>
19
+ <%%= link_to('Approve', approve_approval_path(approval)) %>
20
+ <%%= link_to('Reject', reject_approval_path(approval)) %>
21
+ </td>
22
+ </tr>
23
+ <%% end %>
24
+ </tbody>
25
+ </table>
26
+
@@ -0,0 +1,15 @@
1
+ <h1>Approval Queue</h1>
2
+
3
+ <%% form_for(:approval, :url => approvals_path, :html => {:method => :get}) do |f| %>
4
+ <%% unless @conditions[:state].is_a?(Array) # History page shouldn't allow selecting different states %>
5
+ <%%= f.label(:state) %>
6
+ <%%= f.select(:state, options_for_select(Approval.options_for_state, @conditions[:state])) %>
7
+ <%% end %>
8
+ <% if owner? %> <%%= f.label(:owner_id) %>
9
+ <%%= f.select(:owner_id, options_for_select(Approval.options_for_assigned_owners, @conditions[:owner_id]), :prompt => 'All Users') %>
10
+ <% end %> <%%= f.label(:item_type) %>
11
+ <%%= f.select(:item_type, options_for_select(Approval.options_for_type, @conditions[:item_type]), :prompt => 'All Types') %>
12
+ <%%= f.submit('Filter') %>
13
+ <%% end %>
14
+
15
+ <%%= render :partial => @table_partial %>
@@ -0,0 +1,3 @@
1
+ - form_for(approval, :url => assign_approval_path(approval.id), :html => {:class => 'assignment'}) do |f|
2
+ = f.select(:owner_id, options_for_select(Approval.options_for_available_owners(true), approval.owner_id))
3
+ = f.submit('Set')
@@ -0,0 +1,19 @@
1
+ %table
2
+ %thead
3
+ %tr
4
+ %th Type
5
+ %th Record
6
+ <% if owner? %> %th Owner
7
+ <% end %> %th State
8
+ %th
9
+ %tbody
10
+ - @approvals.each do |approval|
11
+ %tr
12
+ %td= approval.item_type.classify
13
+ %td= approval.item.try(:to_s) || approval.item.id
14
+ <% if owner? %> %td= render :partial => 'owner_select', :locals => {:approval => approval}
15
+ <% end %> %td= approval.state
16
+ %td.actions
17
+ = link_to('Approve', approve_approval_path(approval))
18
+ = link_to('Reject', reject_approval_path(approval))
19
+
@@ -0,0 +1,13 @@
1
+ %h1 Approval Queue
2
+
3
+ - form_for(:approval, :url => approvals_path, :html => {:method => :get}) do |f|
4
+ - unless @conditions[:state].is_a?(Array) # History page shouldn't allow selecting different states
5
+ = label_tag('state', 'State')
6
+ = select_tag('state', options_for_select(Approval.options_for_state, @conditions[:state]))
7
+ <% if owner? %> = label_tag('owner_id', 'Owner')
8
+ = select('owner_id', options_for_select(Approval.options_for_assigned_owners(true), @conditions[:owner_id]))
9
+ <% end %> = label_tag('item_type', 'Type')
10
+ = select_tag('item_type', options_for_select(Approval.options_for_type(true), @conditions[:item_type]))
11
+ %button{:type => 'Submit'} Filter
12
+
13
+ = render :partial => @table_partial
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/rails/init'
@@ -0,0 +1,3 @@
1
+ module ActsAsApprovable
2
+ VERSION = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'VERSION'))).chomp
3
+ end