saucy 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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