saucy 0.1.3 → 0.1.4

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 (38) hide show
  1. data/Gemfile +15 -1
  2. data/Gemfile.lock +67 -1
  3. data/Rakefile +26 -0
  4. data/app/controllers/memberships_controller.rb +33 -1
  5. data/app/models/invitation.rb +1 -1
  6. data/app/models/membership.rb +22 -0
  7. data/app/models/permission.rb +17 -0
  8. data/app/models/signup.rb +3 -3
  9. data/app/views/memberships/edit.html.erb +18 -0
  10. data/app/views/memberships/index.html.erb +4 -4
  11. data/config/routes.rb +2 -3
  12. data/features/run_features.feature +4 -3
  13. data/features/step_definitions/rails_steps.rb +3 -0
  14. data/lib/generators/saucy/features/templates/factories.rb +4 -4
  15. data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +6 -3
  16. data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +6 -4
  17. data/lib/generators/saucy/install/templates/create_saucy_tables.rb +8 -6
  18. data/lib/saucy/account.rb +9 -5
  19. data/lib/saucy/project.rb +28 -5
  20. data/lib/saucy/user.rb +5 -12
  21. data/spec/controllers/memberships_controller_spec.rb +87 -5
  22. data/spec/environment.rb +91 -0
  23. data/spec/models/account_spec.rb +17 -6
  24. data/spec/models/membership_spec.rb +37 -0
  25. data/spec/models/permission_spec.rb +19 -0
  26. data/spec/models/project_spec.rb +39 -9
  27. data/spec/models/user_spec.rb +16 -42
  28. data/spec/scaffold/config/routes.rb +5 -0
  29. data/spec/spec_helper.rb +8 -1
  30. data/spec/support/authentication_helpers.rb +16 -6
  31. metadata +75 -74
  32. data/app/controllers/permissions_controller.rb +0 -17
  33. data/app/models/account_membership.rb +0 -8
  34. data/app/models/project_membership.rb +0 -14
  35. data/app/views/permissions/edit.html.erb +0 -15
  36. data/spec/controllers/permissions_controller_spec.rb +0 -69
  37. data/spec/models/account_membership_spec.rb +0 -13
  38. data/spec/models/project_membership_spec.rb +0 -22
data/lib/saucy/account.rb CHANGED
@@ -3,12 +3,12 @@ module Saucy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- has_many :account_memberships, :dependent => :destroy
7
- has_many :users, :through => :account_memberships
6
+ has_many :memberships, :dependent => :destroy
7
+ has_many :users, :through => :memberships
8
8
  has_many :projects, :dependent => :destroy
9
- has_many :admins, :through => :account_memberships,
9
+ has_many :admins, :through => :memberships,
10
10
  :source => :user,
11
- :conditions => { 'account_memberships.admin' => true }
11
+ :conditions => { 'memberships.admin' => true }
12
12
 
13
13
  belongs_to :plan
14
14
 
@@ -31,7 +31,7 @@ module Saucy
31
31
  end
32
32
 
33
33
  def has_member?(user)
34
- account_memberships.exists?(:user_id => user.id)
34
+ memberships.exists?(:user_id => user.id)
35
35
  end
36
36
 
37
37
  def users_by_name
@@ -45,6 +45,10 @@ module Saucy
45
45
  def projects_visible_to(user)
46
46
  projects.visible_to(user)
47
47
  end
48
+
49
+ def memberships_by_name
50
+ memberships.by_name
51
+ end
48
52
  end
49
53
  end
50
54
  end
data/lib/saucy/project.rb CHANGED
@@ -4,12 +4,19 @@ module Saucy
4
4
 
5
5
  included do
6
6
  belongs_to :account
7
- has_many :project_memberships, :dependent => :destroy
8
- has_many :users, :through => :project_memberships
7
+ has_many :permissions, :dependent => :destroy
8
+ has_many :users, :through => :permissions
9
9
 
10
10
  validates_presence_of :account_id
11
11
 
12
12
  after_create :assign_default_memberships
13
+ after_save :update_memberships
14
+
15
+ # We have to define this here instead of mixing it in,
16
+ # because ActiveRecord does the same
17
+ def user_ids=(new_user_ids)
18
+ @new_user_ids = new_user_ids.reject { |user_id| user_id.blank? }
19
+ end
13
20
  end
14
21
 
15
22
  module ClassMethods
@@ -24,14 +31,30 @@ module Saucy
24
31
 
25
32
  module InstanceMethods
26
33
  def has_member?(user)
27
- project_memberships.exists?(:user_id => user.id)
34
+ permissions.
35
+ joins(:membership).
36
+ exists?(:memberships => { :user_id => user.id })
28
37
  end
29
38
 
30
39
  private
31
40
 
32
41
  def assign_default_memberships
33
- account.admins.each do |admin|
34
- self.project_memberships.create(:user => admin)
42
+ account.memberships.where(:admin => true).each do |membership|
43
+ self.permissions.create(:membership => membership)
44
+ end
45
+ end
46
+
47
+ def update_memberships
48
+ if @new_user_ids
49
+ removed_user_ids = self.user_ids - @new_user_ids
50
+ added_user_ids = @new_user_ids - self.user_ids
51
+
52
+ permissions.where(:user_id => removed_user_ids).destroy_all
53
+ added_user_ids.each do |added_user_id|
54
+ membership =
55
+ account.memberships.where(:user_id => added_user_id).first
56
+ permissions.create!(:membership => membership)
57
+ end
35
58
  end
36
59
  end
37
60
  end
data/lib/saucy/user.rb CHANGED
@@ -4,28 +4,21 @@ module Saucy
4
4
 
5
5
  included do
6
6
  attr_accessible :name, :project_ids, :email, :password_confirmation, :password
7
- has_many :project_memberships
8
- has_many :projects, :through => :project_memberships
9
- has_many :account_memberships
10
- has_many :accounts, :through => :account_memberships
7
+ has_many :memberships
8
+ has_many :accounts, :through => :memberships
9
+ has_many :permissions
10
+ has_many :projects, :through => :permissions
11
11
  validates_presence_of :name
12
12
  end
13
13
 
14
14
  module InstanceMethods
15
15
  def admin_of?(account)
16
- account_memberships.exists?(:account_id => account.id, :admin => true)
16
+ memberships.exists?(:account_id => account.id, :admin => true)
17
17
  end
18
18
 
19
19
  def member_of?(account_or_project)
20
20
  account_or_project.has_member?(self)
21
21
  end
22
-
23
- def update_permissions_for(account, project_ids)
24
- project_ids_for_other_accounts = projects.
25
- reject { |project| project.account_id == account.id }.
26
- map { |project| project.id }
27
- self.project_ids = project_ids + project_ids_for_other_accounts
28
- end
29
22
  end
30
23
 
31
24
  module ClassMethods
@@ -3,20 +3,30 @@ require 'spec_helper'
3
3
  describe MembershipsController, "routes" do
4
4
  it { should route(:get, "/accounts/abc/memberships").
5
5
  to(:action => :index, :account_id => 'abc') }
6
+ it { should route(:get, "/memberships/abc/edit").
7
+ to(:action => :edit, :id => 'abc') }
8
+ it { should route(:put, "/memberships/abc").
9
+ to(:action => :update, :id => 'abc') }
10
+ it { should route(:delete, "/memberships/abc").
11
+ to(:action => :destroy, :id => 'abc') }
6
12
  end
7
13
 
8
14
  describe MembershipsController, "permissions", :as => :account_member do
15
+ let(:membership) { Factory(:membership, :account => account) }
9
16
  it { should deny_access.
10
17
  on(:get, :index, :account_id => account.to_param).
11
18
  flash(/admin/) }
19
+ it { should deny_access.
20
+ on(:get, :edit, :id => membership.to_param).
21
+ flash(/admin/) }
12
22
  end
13
23
 
14
24
  describe MembershipsController, "index", :as => :account_admin do
15
- let(:users) { [Factory.stub(:user), Factory.stub(:user)] }
25
+ let(:memberships) { [Factory.stub(:membership), Factory.stub(:membership)] }
16
26
 
17
27
  before do
18
28
  Account.stubs(:find_by_url! => account)
19
- account.stubs(:users_by_name => users)
29
+ account.stubs(:memberships_by_name => memberships)
20
30
  get :index, :account_id => account.to_param
21
31
  end
22
32
 
@@ -25,9 +35,81 @@ describe MembershipsController, "index", :as => :account_admin do
25
35
  should render_template(:index)
26
36
  end
27
37
 
28
- it "assigns users by name" do
29
- account.should have_received(:users_by_name)
30
- should assign_to(:users).with(users)
38
+ it "assigns memberships by name" do
39
+ account.should have_received(:memberships_by_name)
40
+ should assign_to(:memberships).with(memberships)
41
+ end
42
+ end
43
+
44
+ describe MembershipsController, "edit", :as => :account_admin do
45
+ let(:edited_membership) { Factory.stub(:membership, :account => account) }
46
+ let(:projects) { [Factory.stub(:project)] }
47
+
48
+ before do
49
+ Membership.stubs(:find => edited_membership)
50
+ account.stubs(:projects_by_name => projects)
51
+ get :edit, :id => edited_membership.to_param
52
+ end
53
+
54
+ it "renders the edit template" do
55
+ should respond_with(:success)
56
+ should render_template(:edit)
57
+ end
58
+
59
+ it "assigns projects by name" do
60
+ account.should have_received(:projects_by_name)
61
+ should assign_to(:projects).with(projects)
62
+ end
63
+
64
+ it "assigns the membership being edited" do
65
+ Membership.should have_received(:find).with(edited_membership.to_param,
66
+ :include => :account)
67
+ should assign_to(:membership).with(edited_membership)
31
68
  end
32
69
  end
33
70
 
71
+ describe MembershipsController, "update", :as => :account_admin do
72
+ let(:edited_membership) { Factory.stub(:membership, :account => account) }
73
+ let(:attributes) { 'some attributes' }
74
+
75
+ before do
76
+ Membership.stubs(:find => edited_membership)
77
+ edited_membership.stubs(:update_attributes!)
78
+ put :update, :id => edited_membership.to_param,
79
+ :membership => attributes
80
+ end
81
+
82
+ it "redirects to the account memberships index" do
83
+ should redirect_to(account_memberships_url(account))
84
+ end
85
+
86
+ it "update the membership" do
87
+ edited_membership.should have_received(:update_attributes!).with(attributes)
88
+ end
89
+
90
+ it "sets a flash message" do
91
+ should set_the_flash.to(/update/i)
92
+ end
93
+ end
94
+
95
+ describe MembershipsController, "destroy", :as => :account_admin do
96
+ let(:removed_membership) { Factory.stub(:membership, :account => account) }
97
+
98
+ before do
99
+ Membership.stubs(:find => removed_membership)
100
+ removed_membership.stubs(:destroy)
101
+ delete :destroy, :id => removed_membership.to_param
102
+ end
103
+
104
+ it "redirects to the account memberships index" do
105
+ should redirect_to(account_memberships_url(account))
106
+ end
107
+
108
+ it "removes the membership" do
109
+ removed_membership.should have_received(:destroy)
110
+ end
111
+
112
+ it "sets a flash message" do
113
+ should set_the_flash.to(/remove/i)
114
+ end
115
+ end
@@ -0,0 +1,91 @@
1
+ PROJECT_ROOT = File.expand_path("../..", __FILE__)
2
+ $LOAD_PATH << File.join(PROJECT_ROOT, "lib")
3
+
4
+ require 'rails/all'
5
+ require 'saucy'
6
+ require 'clearance'
7
+ require 'factory_girl'
8
+ require 'bourne'
9
+
10
+ class ApplicationController < ActionController::Base
11
+ include Clearance::Authentication
12
+ include Saucy::AccountAuthorization
13
+ end
14
+
15
+ class ProjectsController < ApplicationController
16
+ include Saucy::ProjectsController
17
+ end
18
+
19
+ class User < ActiveRecord::Base
20
+ include Clearance::User
21
+ include Saucy::User
22
+ end
23
+
24
+ module Testapp
25
+ class Application < Rails::Application
26
+ config.action_mailer.default_url_options = { :host => 'localhost' }
27
+ config.encoding = "utf-8"
28
+ config.paths.config.database = "spec/scaffold/config/database.yml"
29
+ config.paths.app.models << "lib/generators/saucy/install/templates/models"
30
+ config.paths.config.routes << "spec/scaffold/config/routes.rb"
31
+ config.paths.app.views << "spec/scaffold/views"
32
+ config.paths.log = "tmp/log"
33
+ config.cache_classes = true
34
+ config.whiny_nils = true
35
+ config.consider_all_requests_local = true
36
+ config.action_controller.perform_caching = false
37
+ config.action_dispatch.show_exceptions = false
38
+ config.action_controller.allow_forgery_protection = false
39
+ config.action_mailer.delivery_method = :test
40
+ config.active_support.deprecation = :stderr
41
+ end
42
+ end
43
+
44
+ Testapp::Application.initialize!
45
+
46
+ require "lib/generators/saucy/features/templates/factories"
47
+ require "lib/generators/saucy/install/templates/create_saucy_tables"
48
+
49
+ class ClearanceCreateUsers < ActiveRecord::Migration
50
+ def self.up
51
+ create_table(:users) do |t|
52
+ t.string :email
53
+ t.string :encrypted_password, :limit => 128
54
+ t.string :salt, :limit => 128
55
+ t.string :confirmation_token, :limit => 128
56
+ t.string :remember_token, :limit => 128
57
+ t.boolean :email_confirmed, :default => false, :null => false
58
+ t.timestamps
59
+ end
60
+
61
+ add_index :users, :email
62
+ add_index :users, :remember_token
63
+ end
64
+ end
65
+
66
+ class ClearanceMailer
67
+ def self.change_password(user)
68
+ new
69
+ end
70
+
71
+ def self.confirmation(user)
72
+ new
73
+ end
74
+
75
+ def self.deliver_change_password(user)
76
+ end
77
+
78
+ def self.deliver_confirmation(user)
79
+ end
80
+
81
+ def deliver
82
+ end
83
+ end
84
+
85
+ Clearance.configure do |config|
86
+ end
87
+
88
+ FileUtils.rm_f(File.join(PROJECT_ROOT, 'tmp', 'test.sqlite3'))
89
+ ClearanceCreateUsers.suppress_messages { ClearanceCreateUsers.migrate(:up) }
90
+ CreateSaucyTables.suppress_messages { CreateSaucyTables.migrate(:up) }
91
+
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  describe Account do
4
4
  subject { Factory(:account) }
5
5
 
6
- it { should have_many(:account_memberships) }
7
- it { should have_many(:users).through(:account_memberships) }
6
+ it { should have_many(:memberships) }
7
+ it { should have_many(:users).through(:memberships) }
8
8
  it { should have_many(:projects) }
9
9
 
10
10
  it { should validate_uniqueness_of(:name) }
@@ -39,9 +39,9 @@ describe Account do
39
39
  non_admin = Factory(:user)
40
40
  non_member = Factory(:user)
41
41
  admins.each do |admin|
42
- Factory(:account_membership, :user => admin, :account => subject, :admin => true)
42
+ Factory(:membership, :user => admin, :account => subject, :admin => true)
43
43
  end
44
- Factory(:account_membership, :user => non_admin, :account => subject, :admin => false)
44
+ Factory(:membership, :user => non_admin, :account => subject, :admin => false)
45
45
 
46
46
  result = subject.admins
47
47
 
@@ -49,13 +49,24 @@ describe Account do
49
49
  end
50
50
 
51
51
  it "has a member with a membership" do
52
- membership = Factory(:account_membership, :account => subject)
52
+ membership = Factory(:membership, :account => subject)
53
53
  should have_member(membership.user)
54
54
  end
55
55
 
56
56
  it "doesn't have a member without a membership" do
57
- membership = Factory(:account_membership, :account => subject)
57
+ membership = Factory(:membership, :account => subject)
58
58
  should_not have_member(Factory(:user))
59
59
  end
60
+
61
+ it "finds memberships by name" do
62
+ expected = 'expected result'
63
+ memberships = stub('memberships', :by_name => expected)
64
+ account = Factory.stub(:account)
65
+ account.stubs(:memberships => memberships)
66
+
67
+ result = account.memberships_by_name
68
+
69
+ result.should == expected
70
+ end
60
71
  end
61
72
 
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Membership do
4
+ it { should belong_to(:account) }
5
+ it { should belong_to(:user) }
6
+ it { should validate_presence_of(:account_id) }
7
+ it { should validate_presence_of(:user_id) }
8
+ it { should have_many(:permissions) }
9
+ it { should have_many(:projects).through(:permissions) }
10
+
11
+ describe "given an existing account membership" do
12
+ before { Factory(:membership) }
13
+ it { should validate_uniqueness_of(:user_id).scoped_to(:account_id) }
14
+ end
15
+
16
+ it "delegates the user's name" do
17
+ user = Factory(:user)
18
+ membership = Factory(:membership, :user => user)
19
+
20
+ membership.name.should == user.name
21
+ end
22
+
23
+ it "delegates the user's email" do
24
+ user = Factory(:user)
25
+ membership = Factory(:membership, :user => user)
26
+
27
+ membership.email.should == user.email
28
+ end
29
+
30
+ it "returns memberships by name" do
31
+ Factory(:membership, :user => Factory(:user, :name => "def"))
32
+ Factory(:membership, :user => Factory(:user, :name => "abc"))
33
+ Factory(:membership, :user => Factory(:user, :name => "ghi"))
34
+
35
+ Membership.by_name.map(&:name).should == %w(abc def ghi)
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Permission do
4
+ it { should belong_to(:project) }
5
+ it { should belong_to(:membership) }
6
+ it { should belong_to(:user) }
7
+
8
+ it "caches the user from the account membership" do
9
+ membership = Factory(:membership)
10
+ permission = Factory(:permission,
11
+ :membership => membership)
12
+ permission.user_id.should == membership.user_id
13
+ end
14
+
15
+ it "doesn't allow the user to be assigned" do
16
+ expect { subject.user = Factory.build(:user) }.to raise_error
17
+ end
18
+ end
19
+
@@ -3,18 +3,19 @@ require 'spec_helper'
3
3
  describe Project do
4
4
  it { should belong_to(:account) }
5
5
  it { should validate_presence_of(:account_id) }
6
- it { should have_many(:project_memberships) }
7
- it { should have_many(:users) }
6
+ it { should have_many(:permissions) }
7
+ it { should have_many(:users).through(:permissions) }
8
8
 
9
9
  it "finds projects visible to a user" do
10
10
  account = Factory(:account)
11
11
  user = Factory(:user)
12
- Factory(:account_membership, :user => user, :account => account)
12
+ membership = Factory(:membership, :user => user, :account => account)
13
13
  visible_projects = [Factory(:project, :account => account),
14
14
  Factory(:project, :account => account)]
15
15
  invisible_project = Factory(:project, :account => account)
16
16
  visible_projects.each do |visible_project|
17
- Factory(:project_membership, :project => visible_project, :user => user)
17
+ Factory(:permission, :project => visible_project,
18
+ :membership => membership)
18
19
  end
19
20
 
20
21
  Project.visible_to(user).to_a.should =~ visible_projects
@@ -38,12 +39,12 @@ describe Project, "for an account with admin and non-admin users" do
38
39
  subject { Factory(:project, :account => account) }
39
40
 
40
41
  before do
41
- Factory(:account_membership, :account => account, :user => non_admin, :admin => false)
42
- Factory(:account_membership, :account => other_account,
42
+ Factory(:membership, :account => account, :user => non_admin, :admin => false)
43
+ Factory(:membership, :account => other_account,
43
44
  :user => non_member,
44
45
  :admin => true)
45
46
  admins.each do |admin|
46
- Factory(:account_membership, :user => admin, :account => account, :admin => true)
47
+ Factory(:membership, :user => admin, :account => account, :admin => true)
47
48
  end
48
49
  end
49
50
 
@@ -68,8 +69,10 @@ describe Project, "saved" do
68
69
 
69
70
  it "has a member with a membership" do
70
71
  user = Factory(:user)
71
- Factory(:account_membership, :account => subject.account, :user => user)
72
- membership = Factory(:project_membership, :project => subject, :user => user)
72
+ membership = Factory(:membership, :account => subject.account,
73
+ :user => user)
74
+ membership = Factory(:permission, :project => subject,
75
+ :membership => membership)
73
76
  should have_member(user)
74
77
  end
75
78
 
@@ -78,3 +81,30 @@ describe Project, "saved" do
78
81
  should_not have_member(user)
79
82
  end
80
83
  end
84
+
85
+ describe Project, "assigning a new user list" do
86
+ subject { Factory(:project) }
87
+
88
+ let(:account) { subject.account }
89
+ let!(:member) { Factory(:user) }
90
+ let!(:non_member) { Factory(:user) }
91
+
92
+ before do
93
+ member_membership =
94
+ Factory(:membership, :account => account, :user => member)
95
+ non_member_membership =
96
+ Factory(:membership, :account => account, :user => non_member)
97
+ Factory(:permission, :membership => member_membership,
98
+ :project => subject)
99
+
100
+ subject.reload.update_attributes!(:user_ids => [non_member.id, ""])
101
+ end
102
+
103
+ it "adds an added user" do
104
+ member.should_not be_member_of(subject)
105
+ end
106
+
107
+ it "removes a removed user" do
108
+ non_member.should be_member_of(subject)
109
+ end
110
+ end