saucy 0.1.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.
- data/Gemfile +8 -0
- data/Gemfile.lock +106 -0
- data/README +9 -0
- data/app/controllers/accounts_controller.rb +57 -0
- data/app/controllers/invitations_controller.rb +36 -0
- data/app/controllers/memberships_controller.rb +9 -0
- data/app/controllers/permissions_controller.rb +17 -0
- data/app/controllers/plans_controller.rb +7 -0
- data/app/controllers/profiles_controller.rb +17 -0
- data/app/controllers/projects_controller.rb +56 -0
- data/app/models/account_membership.rb +8 -0
- data/app/models/invitation.rb +86 -0
- data/app/models/project_membership.rb +16 -0
- data/app/models/signup.rb +134 -0
- data/app/views/accounts/_account.html.erb +12 -0
- data/app/views/accounts/_blank_slate.html.erb +6 -0
- data/app/views/accounts/_projects.html.erb +12 -0
- data/app/views/accounts/_tab_bar.html.erb +8 -0
- data/app/views/accounts/edit.html.erb +17 -0
- data/app/views/accounts/index.html.erb +3 -0
- data/app/views/accounts/new.html.erb +26 -0
- data/app/views/invitation_mailer/invitation.text.erb +6 -0
- data/app/views/invitations/new.html.erb +11 -0
- data/app/views/invitations/show.html.erb +20 -0
- data/app/views/memberships/index.html.erb +16 -0
- data/app/views/permissions/edit.html.erb +15 -0
- data/app/views/plans/index.html.erb +3 -0
- data/app/views/profiles/_inputs.html.erb +6 -0
- data/app/views/profiles/edit.html.erb +35 -0
- data/app/views/projects/_form.html.erb +4 -0
- data/app/views/projects/edit.html.erb +18 -0
- data/app/views/projects/index.html.erb +13 -0
- data/app/views/projects/new.html.erb +11 -0
- data/config/routes.rb +18 -0
- data/features/run_features.feature +73 -0
- data/features/step_definitions/clearance_steps.rb +45 -0
- data/features/step_definitions/rails_steps.rb +70 -0
- data/features/support/env.rb +4 -0
- data/features/support/file.rb +11 -0
- data/lib/generators/saucy/base.rb +18 -0
- data/lib/generators/saucy/features/features_generator.rb +68 -0
- data/lib/generators/saucy/features/templates/factories.rb +55 -0
- data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +13 -0
- data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
- data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +3 -0
- data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
- data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +27 -0
- data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +54 -0
- data/lib/generators/saucy/install/install_generator.rb +36 -0
- data/lib/generators/saucy/install/templates/create_saucy_tables.rb +72 -0
- data/lib/generators/saucy/install/templates/models/account.rb +3 -0
- data/lib/generators/saucy/install/templates/models/invitation_mailer.rb +9 -0
- data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
- data/lib/generators/saucy/install/templates/models/project.rb +3 -0
- data/lib/generators/saucy/views/views_generator.rb +23 -0
- data/lib/saucy.rb +7 -0
- data/lib/saucy/account.rb +50 -0
- data/lib/saucy/account_authorization.rb +34 -0
- data/lib/saucy/configuration.rb +12 -0
- data/lib/saucy/engine.rb +9 -0
- data/lib/saucy/layouts.rb +36 -0
- data/lib/saucy/plan.rb +11 -0
- data/lib/saucy/project.rb +39 -0
- data/lib/saucy/user.rb +37 -0
- data/spec/controllers/accounts_controller_spec.rb +204 -0
- data/spec/controllers/application_controller_spec.rb +27 -0
- data/spec/controllers/invitations_controller_spec.rb +155 -0
- data/spec/controllers/memberships_controller_spec.rb +33 -0
- data/spec/controllers/permissions_controller_spec.rb +69 -0
- data/spec/controllers/profiles_controller_spec.rb +43 -0
- data/spec/controllers/projects_controller_spec.rb +123 -0
- data/spec/layouts_spec.rb +21 -0
- data/spec/models/account_membership_spec.rb +13 -0
- data/spec/models/account_spec.rb +61 -0
- data/spec/models/invitation_spec.rb +160 -0
- data/spec/models/project_membership_spec.rb +26 -0
- data/spec/models/project_spec.rb +80 -0
- data/spec/models/signup_spec.rb +175 -0
- data/spec/models/user_spec.rb +96 -0
- data/spec/saucy_spec.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/authentication_helpers.rb +71 -0
- data/spec/support/authorization_helpers.rb +56 -0
- data/spec/support/clearance_matchers.rb +55 -0
- data/spec/views/accounts/_account.html.erb_spec.rb +66 -0
- metadata +203 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'generators/saucy/base'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
|
4
|
+
module Saucy
|
5
|
+
module Generators
|
6
|
+
class ViewsGenerator < Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
extend ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
desc <<-DESC
|
11
|
+
Description:
|
12
|
+
Copy saucy views to your application.
|
13
|
+
DESC
|
14
|
+
|
15
|
+
|
16
|
+
def copy_views
|
17
|
+
views_path = File.join(File.dirname(File.expand_path(__FILE__)), '../../../../app/views')
|
18
|
+
directory views_path, 'app/views'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/lib/saucy.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Saucy
|
2
|
+
module Account
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :account_memberships, :dependent => :destroy
|
7
|
+
has_many :users, :through => :account_memberships
|
8
|
+
has_many :projects, :dependent => :destroy
|
9
|
+
has_many :admins, :through => :account_memberships,
|
10
|
+
:source => :user,
|
11
|
+
:conditions => { 'account_memberships.admin' => true }
|
12
|
+
|
13
|
+
belongs_to :plan
|
14
|
+
|
15
|
+
validates_uniqueness_of :name, :url
|
16
|
+
validates_presence_of :name, :url
|
17
|
+
|
18
|
+
attr_accessible :name, :url
|
19
|
+
|
20
|
+
validates_format_of :url,
|
21
|
+
:with => %r{^[a-z0-9]+$},
|
22
|
+
:message => "must be only lower case letters."
|
23
|
+
validates_exclusion_of :url,
|
24
|
+
:in => %w[app admin blog dev ftp mail pop pop3 imap smtp staging stats status www],
|
25
|
+
:message => 'is reserved.'
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
def to_param
|
30
|
+
url
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_member?(user)
|
34
|
+
account_memberships.exists?(:user_id => user.id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def users_by_name
|
38
|
+
users.by_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def projects_by_name
|
42
|
+
projects.by_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def projects_visible_to(user)
|
46
|
+
projects.visible_to(user)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Saucy
|
2
|
+
module AccountAuthorization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :current_account
|
7
|
+
include InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
protected
|
12
|
+
|
13
|
+
def current_account
|
14
|
+
::Account.find_by_url!(params[:account_id])
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_project
|
18
|
+
::Project.find(params[:project_id])
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorize_admin
|
22
|
+
unless current_user.admin_of?(current_account)
|
23
|
+
deny_access("You must be an admin to access that page.")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorize_member
|
28
|
+
unless current_user.member_of?(current_project)
|
29
|
+
deny_access("You do not have permission for this project.")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/saucy/engine.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Saucy
|
2
|
+
class Layouts
|
3
|
+
def initialize
|
4
|
+
@controllers = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(controller_name, *args, &block)
|
8
|
+
@controllers[controller_name.to_s] ||= Controller.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.to_proc
|
12
|
+
lambda do |controller|
|
13
|
+
controller_name = controller.controller_name
|
14
|
+
action_name = controller.action_name
|
15
|
+
Rails.application.config.saucy.layouts.send(controller_name).send(action_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
class Controller
|
22
|
+
def initialize
|
23
|
+
@actions = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method_name, *args, &block)
|
27
|
+
action_name = method_name.to_s
|
28
|
+
if action_name.sub!(/=$/, '')
|
29
|
+
@actions[action_name] = args.first
|
30
|
+
else
|
31
|
+
@actions[action_name] ||= "application"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/saucy/plan.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Saucy
|
2
|
+
module Project
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
belongs_to :account
|
7
|
+
has_many :project_memberships, :dependent => :destroy
|
8
|
+
has_many :users, :through => :project_memberships
|
9
|
+
|
10
|
+
validates_presence_of :account_id
|
11
|
+
|
12
|
+
after_create :assign_default_memberships
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def visible_to(user)
|
17
|
+
where(['projects.id IN(?)', user.project_ids])
|
18
|
+
end
|
19
|
+
|
20
|
+
def by_name
|
21
|
+
order("projects.name")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module InstanceMethods
|
26
|
+
def has_member?(user)
|
27
|
+
project_memberships.exists?(:user_id => user.id)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def assign_default_memberships
|
33
|
+
account.admins.each do |admin|
|
34
|
+
self.project_memberships.create(:user => admin)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/saucy/user.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Saucy
|
2
|
+
module User
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
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
|
11
|
+
validates_presence_of :name
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
def admin_of?(account)
|
16
|
+
account_memberships.exists?(:account_id => account.id, :admin => true)
|
17
|
+
end
|
18
|
+
|
19
|
+
def member_of?(account_or_project)
|
20
|
+
account_or_project.has_member?(self)
|
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
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def by_name
|
33
|
+
order('users.name')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AccountsController, "routes" do
|
4
|
+
it { should route(:get, "/plans/abc/accounts/new").
|
5
|
+
to(:action => :new, :plan_id => :abc) }
|
6
|
+
it { should route(:get, "/accounts").to(:action => :index) }
|
7
|
+
it { should route(:post, "/plans/abc/accounts").
|
8
|
+
to(:action => :create, :plan_id => :abc) }
|
9
|
+
it { should route(:put, "/accounts/1").to(:action => :update, :id => 1) }
|
10
|
+
it { should route(:get, "/accounts/1/edit").to(:action => :edit, :id => 1) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe AccountsController, "new" do
|
14
|
+
let(:signup) { stub('signup') }
|
15
|
+
let(:plan) { Factory(:plan) }
|
16
|
+
|
17
|
+
before do
|
18
|
+
Signup.stubs(:new => signup)
|
19
|
+
get :new, :plan_id => plan.to_param
|
20
|
+
end
|
21
|
+
|
22
|
+
it "renders the new account form" do
|
23
|
+
should respond_with(:success)
|
24
|
+
should render_template(:new)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "assigns a new signup" do
|
28
|
+
Signup.should have_received(:new)
|
29
|
+
should assign_to(:signup).with(signup)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe AccountsController, "successful create for a confirmed user" do
|
34
|
+
let(:user) { Factory.stub(:email_confirmed_user) }
|
35
|
+
let(:signup) { stub('signup', :user => user, :email_confirmed? => true, :user= => nil) }
|
36
|
+
let(:signup_attributes) { "attributes" }
|
37
|
+
let(:plan) { Factory(:plan) }
|
38
|
+
|
39
|
+
before do
|
40
|
+
Signup.stubs(:new => signup)
|
41
|
+
signup.stubs(:save => true)
|
42
|
+
sign_in_as user
|
43
|
+
post :create, :signup => signup_attributes, :plan_id => plan.to_param
|
44
|
+
end
|
45
|
+
|
46
|
+
it "creates an signup" do
|
47
|
+
Signup.should have_received(:new).with(signup_attributes)
|
48
|
+
signup.should have_received(:save)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "redirects to the root" do
|
52
|
+
should redirect_to(root_url)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "sets the current user" do
|
56
|
+
signup.should have_received(:user=).with(user)
|
57
|
+
end
|
58
|
+
|
59
|
+
it { should set_the_flash.to(/created/i) }
|
60
|
+
it { should_not set_the_flash.to(/confirm/i) }
|
61
|
+
it { should be_signed_in.as(user) }
|
62
|
+
end
|
63
|
+
|
64
|
+
describe AccountsController, "successful create for an unconfirmed user" do
|
65
|
+
let(:user) { Factory.stub(:user) }
|
66
|
+
let(:signup) { stub('signup', :user => user, :email_confirmed? => false, :user= => nil) }
|
67
|
+
let(:signup_attributes) { "attributes" }
|
68
|
+
let(:plan) { Factory(:plan) }
|
69
|
+
|
70
|
+
before do
|
71
|
+
Signup.stubs(:new => signup)
|
72
|
+
signup.stubs(:save => true)
|
73
|
+
post :create, :signup => signup_attributes, :plan_id => plan.to_param
|
74
|
+
end
|
75
|
+
|
76
|
+
it "creates an signup" do
|
77
|
+
Signup.should have_received(:new).with(signup_attributes)
|
78
|
+
signup.should have_received(:save)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "redirects to the sign in url" do
|
82
|
+
should redirect_to(sign_in_url)
|
83
|
+
end
|
84
|
+
|
85
|
+
it { should set_the_flash.to(/created/i) }
|
86
|
+
it { should set_the_flash.to(/confirm/i) }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe AccountsController, "failed create" do
|
90
|
+
let(:signup) { stub('signup', :email_confirmed? => false, :user= => nil) }
|
91
|
+
let(:signup_attributes) { "attributes" }
|
92
|
+
let(:plan) { Factory(:plan) }
|
93
|
+
|
94
|
+
before do
|
95
|
+
Signup.stubs(:new => signup)
|
96
|
+
signup.stubs(:save => false)
|
97
|
+
post :create, :signup => signup_attributes, :plan_id => plan.to_param
|
98
|
+
end
|
99
|
+
|
100
|
+
it "creates an signup" do
|
101
|
+
Signup.should have_received(:new).with(signup_attributes)
|
102
|
+
signup.should have_received(:save)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "renders the new signup form" do
|
106
|
+
should respond_with(:success)
|
107
|
+
should render_template(:new)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "assigns a new signup" do
|
111
|
+
Signup.should have_received(:new)
|
112
|
+
should assign_to(:signup).with(signup)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe AccountsController, "index with multiple projects" do
|
117
|
+
let(:user) { Factory.stub(:user) }
|
118
|
+
let(:accounts) { %w(one two) }
|
119
|
+
let(:projects) { %w(one two) }
|
120
|
+
|
121
|
+
before do
|
122
|
+
user.stubs(:accounts => accounts)
|
123
|
+
user.stubs(:projects => projects)
|
124
|
+
sign_in_as user
|
125
|
+
get :index
|
126
|
+
end
|
127
|
+
|
128
|
+
it "renders the dashboard page" do
|
129
|
+
should respond_with(:success)
|
130
|
+
should render_template(:index)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "assigns the user's accounts" do
|
134
|
+
user.should have_received(:accounts)
|
135
|
+
should assign_to(:accounts).with(accounts)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe AccountsController, "index with one project" do
|
140
|
+
let(:user) { Factory.stub(:user) }
|
141
|
+
let(:accounts) { %w(one two) }
|
142
|
+
let(:projects) { %w(one) }
|
143
|
+
|
144
|
+
before do
|
145
|
+
user.stubs(:accounts => accounts)
|
146
|
+
user.stubs(:projects => projects)
|
147
|
+
sign_in_as user
|
148
|
+
get :index
|
149
|
+
end
|
150
|
+
|
151
|
+
it "redirects to the project" do
|
152
|
+
should redirect_to(project_url(projects.first))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe AccountsController, "valid update", :as => :account_admin do
|
157
|
+
before do
|
158
|
+
put :update,
|
159
|
+
:account => Factory.attributes_for(:account),
|
160
|
+
:id => account.to_param
|
161
|
+
end
|
162
|
+
|
163
|
+
it "redirects to settings" do
|
164
|
+
should redirect_to(edit_profile_url)
|
165
|
+
end
|
166
|
+
|
167
|
+
it { should set_the_flash.to(/updated/) }
|
168
|
+
end
|
169
|
+
|
170
|
+
describe AccountsController, "invalid update", :as => :account_admin do
|
171
|
+
before do
|
172
|
+
put :update,
|
173
|
+
:account => {:name => ""},
|
174
|
+
:id => account.to_param
|
175
|
+
end
|
176
|
+
|
177
|
+
it { should respond_with(:success) }
|
178
|
+
it { should render_template(:edit) }
|
179
|
+
end
|
180
|
+
|
181
|
+
describe AccountsController, "edit", :as => :account_admin do
|
182
|
+
before do
|
183
|
+
get :edit, :id => account.to_param
|
184
|
+
end
|
185
|
+
|
186
|
+
it "renders the edit template" do
|
187
|
+
should respond_with(:success)
|
188
|
+
should render_template(:edit)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "assigns the account" do
|
192
|
+
should assign_to(:account).with(account)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe AccountsController, "permissions", :as => :account_member do
|
197
|
+
it { should deny_access.
|
198
|
+
on(:get, :edit, :id => account.to_param).
|
199
|
+
flash(/admin/) }
|
200
|
+
it { should deny_access.
|
201
|
+
on(:put, :update, :id => account.to_param).
|
202
|
+
flash(/admin/) }
|
203
|
+
end
|
204
|
+
|