saucy 0.2.18 → 0.2.20

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.
@@ -22,17 +22,17 @@ class InvitationsController < ApplicationController
22
22
  end
23
23
 
24
24
  def show
25
- @invitation = Invitation.find(params[:id])
26
- render
25
+ with_invitation { render }
27
26
  end
28
27
 
29
28
  def update
30
- @invitation = Invitation.find(params[:id])
31
- if @invitation.accept(params[:invitation])
32
- sign_in @invitation.user
33
- redirect_to root_url
34
- else
35
- render :action => 'show'
29
+ with_invitation do
30
+ if @invitation.accept(params[:invitation])
31
+ sign_in @invitation.user
32
+ redirect_to root_url
33
+ else
34
+ render :action => 'show'
35
+ end
36
36
  end
37
37
  end
38
38
 
@@ -41,4 +41,15 @@ class InvitationsController < ApplicationController
41
41
  def assign_projects
42
42
  @projects = current_account.projects_by_name
43
43
  end
44
+
45
+ def with_invitation
46
+ @invitation = Invitation.find_by_code!(params[:id])
47
+ if @invitation.used?
48
+ flash[:error] = t("invitations.show.used",
49
+ :default => "That invitation has already been used.")
50
+ redirect_to root_url
51
+ else
52
+ yield
53
+ end
54
+ end
44
55
  end
@@ -4,13 +4,14 @@ class Invitation < ActiveRecord::Base
4
4
  validates_presence_of :email
5
5
  has_and_belongs_to_many :projects
6
6
 
7
+ before_create :generate_code
7
8
  after_create :deliver_invitation
8
9
 
9
10
  attr_accessor :new_user_name, :new_user_password,
10
11
  :new_user_password_confirmation, :existing_user_password
11
12
  attr_writer :new_user_email, :existing_user_email
12
13
  attr_reader :user
13
- attr_protected :account_id
14
+ attr_protected :account_id, :used
14
15
 
15
16
  validate :validate_accepting_user, :on => :update
16
17
 
@@ -20,12 +21,16 @@ class Invitation < ActiveRecord::Base
20
21
 
21
22
  def accept(attributes)
22
23
  self.attributes = attributes
24
+ self.used = true
23
25
  @user = existing_user || new_user
24
26
  if valid?
25
- @user.save!
26
- @user.memberships.create!(:account => account,
27
- :admin => admin,
28
- :projects => projects)
27
+ transaction do
28
+ save!
29
+ @user.save!
30
+ @user.memberships.create!(:account => account,
31
+ :admin => admin,
32
+ :projects => projects)
33
+ end
29
34
  end
30
35
  end
31
36
 
@@ -37,6 +42,10 @@ class Invitation < ActiveRecord::Base
37
42
  @existing_user_email ||= email
38
43
  end
39
44
 
45
+ def to_param
46
+ code
47
+ end
48
+
40
49
  private
41
50
 
42
51
  def deliver_invitation
@@ -84,4 +93,8 @@ class Invitation < ActiveRecord::Base
84
93
  errors.add(:existing_user_password, "is incorrect")
85
94
  end
86
95
  end
96
+
97
+ def generate_code
98
+ self.code = SecureRandom.hex(8)
99
+ end
87
100
  end
@@ -29,6 +29,8 @@ class CreateSaucyTables < ActiveRecord::Migration
29
29
  table.string :email
30
30
  table.integer :account_id
31
31
  table.boolean :admin
32
+ table.string :code
33
+ table.boolean :used
32
34
  table.datetime :created_at
33
35
  table.datetime :updated_at
34
36
  end
@@ -101,11 +101,11 @@ describe InvitationsController, "invalid create", :as => :account_admin do
101
101
  end
102
102
 
103
103
  describe InvitationsController, "show" do
104
- let(:invitation) { Factory.stub(:invitation) }
104
+ let(:invitation) { Factory.stub(:invitation, :code => 'abc') }
105
105
  let(:account) { invitation.account }
106
106
 
107
107
  before do
108
- Invitation.stubs(:find => invitation)
108
+ Invitation.stubs(:find_by_code! => invitation)
109
109
  get :show, :id => invitation.to_param, :account_id => account.to_param
110
110
  end
111
111
 
@@ -115,19 +115,37 @@ describe InvitationsController, "show" do
115
115
  end
116
116
 
117
117
  it "assigns the invitation" do
118
- Invitation.should have_received(:find).with(invitation.to_param)
118
+ Invitation.should have_received(:find_by_code!).with(invitation.to_param)
119
119
  should assign_to(:invitation).with(invitation)
120
120
  end
121
121
  end
122
122
 
123
+ describe InvitationsController, "show for a used invitation" do
124
+ let(:invitation) { Factory.stub(:invitation, :code => 'abc', :used => true) }
125
+ let(:account) { invitation.account }
126
+
127
+ before do
128
+ Invitation.stubs(:find_by_code! => invitation)
129
+ get :show, :id => invitation.to_param, :account_id => account.to_param
130
+ end
131
+
132
+ it "redirects to the root url" do
133
+ should redirect_to("/")
134
+ end
135
+
136
+ it "sets a flash message" do
137
+ should set_the_flash.to(/used/i)
138
+ end
139
+ end
140
+
123
141
  describe InvitationsController, "valid update" do
124
- let(:invitation) { Factory.stub(:invitation) }
142
+ let(:invitation) { Factory.stub(:invitation, :code => 'abc') }
125
143
  let(:account) { invitation.account }
126
144
  let(:attributes) { 'attributes' }
127
145
  let(:user) { Factory.stub(:user) }
128
146
 
129
147
  before do
130
- Invitation.stubs(:find => invitation)
148
+ Invitation.stubs(:find_by_code! => invitation)
131
149
  invitation.stubs(:accept => true)
132
150
  invitation.stubs(:user => user)
133
151
  put :update, :id => invitation.to_param,
@@ -140,7 +158,7 @@ describe InvitationsController, "valid update" do
140
158
  end
141
159
 
142
160
  it "accepts the invitation" do
143
- Invitation.should have_received(:find).with(invitation.to_param)
161
+ Invitation.should have_received(:find_by_code!).with(invitation.to_param)
144
162
  invitation.should have_received(:accept).with(attributes)
145
163
  end
146
164
 
@@ -150,11 +168,11 @@ describe InvitationsController, "valid update" do
150
168
  end
151
169
 
152
170
  describe InvitationsController, "invalid update" do
153
- let(:invitation) { Factory.stub(:invitation) }
171
+ let(:invitation) { Factory.stub(:invitation, :code => 'abc') }
154
172
  let(:account) { invitation.account }
155
173
 
156
174
  before do
157
- Invitation.stubs(:find => invitation)
175
+ Invitation.stubs(:find_by_code! => invitation)
158
176
  invitation.stubs(:accept => false)
159
177
  put :update, :id => invitation.to_param,
160
178
  :account_id => account.to_param,
@@ -174,3 +192,22 @@ describe InvitationsController, "invalid update" do
174
192
  should assign_to(:invitation).with(invitation)
175
193
  end
176
194
  end
195
+
196
+ describe InvitationsController, "update for a used invitation" do
197
+ let(:invitation) { Factory.stub(:invitation, :code => 'abc', :used => true) }
198
+ let(:account) { invitation.account }
199
+
200
+ before do
201
+ Invitation.stubs(:find_by_code! => invitation)
202
+ put :update, :id => invitation.to_param, :account_id => account.to_param
203
+ end
204
+
205
+ it "redirects to the root url" do
206
+ should redirect_to("/")
207
+ end
208
+
209
+ it "sets a flash message" do
210
+ should set_the_flash.to(/used/i)
211
+ end
212
+ end
213
+
@@ -7,6 +7,7 @@ describe Invitation do
7
7
  it { should have_and_belong_to_many(:projects) }
8
8
 
9
9
  it { should_not allow_mass_assignment_of(:account_id) }
10
+ it { should_not allow_mass_assignment_of(:used) }
10
11
 
11
12
  %w(new_user_name new_user_email new_user_password
12
13
  new_user_password_confirmation existing_user_password).each do |attribute|
@@ -19,9 +20,15 @@ end
19
20
 
20
21
  describe Invitation, "saved" do
21
22
  let(:mail) { stub('invitation', :deliver => true) }
22
- before { InvitationMailer.stubs(:invitation => mail) }
23
23
  subject { Factory(:invitation) }
24
24
  let(:email) { subject.email }
25
+ let(:code) { 'abchex123' }
26
+
27
+ before do
28
+ SecureRandom.stubs(:hex => code)
29
+ InvitationMailer.stubs(:invitation => mail)
30
+ subject
31
+ end
25
32
 
26
33
  it "sends an invitation email" do
27
34
  InvitationMailer.should have_received(:invitation).with(subject)
@@ -39,6 +46,15 @@ describe Invitation, "saved" do
39
46
  it "defauls existing user email to invited email" do
40
47
  subject.existing_user_email.should == subject.email
41
48
  end
49
+
50
+ it "generates a code" do
51
+ SecureRandom.should have_received(:hex).with(8)
52
+ subject.code.should == code
53
+ end
54
+
55
+ it "uses the code in the url" do
56
+ subject.to_param.should == code
57
+ end
42
58
  end
43
59
 
44
60
  describe Invitation, "valid accept for a new user" do
@@ -75,6 +91,10 @@ describe Invitation, "valid accept for a new user" do
75
91
  user.should be_member_of(project)
76
92
  end
77
93
  end
94
+
95
+ it "marks the invitation as used" do
96
+ subject.reload.should be_used
97
+ end
78
98
  end
79
99
 
80
100
  describe Invitation, "invalid accept for a new user" do
@@ -94,6 +114,10 @@ describe Invitation, "invalid accept for a new user" do
94
114
  it "adds error messages" do
95
115
  subject.errors[:new_user_password].should be_present
96
116
  end
117
+
118
+ it "doesn't mark the invitation as used" do
119
+ subject.reload.should_not be_used
120
+ end
97
121
  end
98
122
 
99
123
  describe Invitation, "valid accept for an existing user" do
@@ -115,6 +139,10 @@ describe Invitation, "valid accept for an existing user" do
115
139
  it "adds the user to the account" do
116
140
  account.users.should include(user)
117
141
  end
142
+
143
+ it "marks the invitation as used" do
144
+ subject.reload.should be_used
145
+ end
118
146
  end
119
147
 
120
148
  describe Invitation, "accepting with an invalid password" do
@@ -136,6 +164,108 @@ describe Invitation, "accepting with an invalid password" do
136
164
  end
137
165
  end
138
166
 
167
+ describe Invitation, "saved" do
168
+ let(:mail) { stub('invitation', :deliver => true) }
169
+ subject { Factory(:invitation) }
170
+ let(:email) { subject.email }
171
+ let(:code) { 'abchex123' }
172
+
173
+ before do
174
+ SecureRandom.stubs(:hex => code)
175
+ InvitationMailer.stubs(:invitation => mail)
176
+ subject
177
+ end
178
+
179
+ it "sends an invitation email" do
180
+ InvitationMailer.should have_received(:invitation).with(subject)
181
+ mail.should have_received(:deliver)
182
+ end
183
+
184
+ it "delegates account name" do
185
+ subject.account_name.should == subject.account.name
186
+ end
187
+
188
+ it "defauls new user email to invited email" do
189
+ subject.new_user_email.should == subject.email
190
+ end
191
+
192
+ it "defauls existing user email to invited email" do
193
+ subject.existing_user_email.should == subject.email
194
+ end
195
+
196
+ it "generates a code" do
197
+ SecureRandom.should have_received(:hex).with(8)
198
+ subject.code.should == code
199
+ end
200
+
201
+ it "uses the code in the url" do
202
+ subject.to_param.should == code
203
+ end
204
+ end
205
+
206
+ describe Invitation, "valid accept for a new user" do
207
+ let(:account) { Factory(:account) }
208
+ let(:projects) { [Factory(:project, :account => account)] }
209
+ let(:password) { 'secret' }
210
+ let(:name) { 'Rocket' }
211
+ subject { Factory(:invitation, :account => account, :projects => projects) }
212
+
213
+ let!(:result) do
214
+ subject.accept(:new_user_password => password,
215
+ :new_user_password_confirmation => password,
216
+ :new_user_name => name)
217
+ end
218
+
219
+ let(:user) { subject.user }
220
+
221
+ it "returns true" do
222
+ result.should be_true
223
+ end
224
+
225
+ it "creates a saved, confirmed user" do
226
+ user.should_not be_nil
227
+ user.should be_persisted
228
+ user.name.should == name
229
+ end
230
+
231
+ it "adds the user to the account" do
232
+ account.users.should include(user)
233
+ end
234
+
235
+ it "adds the user to each of the invitation's projects" do
236
+ projects.each do |project|
237
+ user.should be_member_of(project)
238
+ end
239
+ end
240
+
241
+ it "marks the invitation as used" do
242
+ subject.reload.should be_used
243
+ end
244
+ end
245
+
246
+ describe Invitation, "invalid accept for a new user" do
247
+ subject { Factory(:invitation) }
248
+ let!(:result) { subject.accept({}) }
249
+ let(:user) { subject.user }
250
+ let(:account) { subject.account }
251
+
252
+ it "returns false" do
253
+ result.should be_false
254
+ end
255
+
256
+ it "doesn't create a user" do
257
+ user.should be_new_record
258
+ end
259
+
260
+ it "adds error messages" do
261
+ subject.errors[:new_user_password].should be_present
262
+ end
263
+
264
+ it "doesn't mark the invitation as used" do
265
+ subject.reload.should_not be_used
266
+ end
267
+ end
268
+
139
269
  describe Invitation, "accepting with an unknown email" do
140
270
  subject { Factory(:invitation, :email => 'unknown') }
141
271
  let(:account) { subject.account }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saucy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 51
4
+ hash: 63
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 18
10
- version: 0.2.18
9
+ - 20
10
+ version: 0.2.20
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot, inc.
@@ -17,13 +17,14 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-02-09 00:00:00 -05:00
20
+ date: 2011-02-11 00:00:00 -05:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
24
- name: formtastic
24
+ type: :runtime
25
25
  prerelease: false
26
- requirement: &id001 !ruby/object:Gem::Requirement
26
+ name: formtastic
27
+ version_requirements: &id001 !ruby/object:Gem::Requirement
27
28
  none: false
28
29
  requirements:
29
30
  - - ">="
@@ -33,12 +34,12 @@ dependencies:
33
34
  - 1
34
35
  - 2
35
36
  version: "1.2"
36
- type: :runtime
37
- version_requirements: *id001
37
+ requirement: *id001
38
38
  - !ruby/object:Gem::Dependency
39
- name: railties
39
+ type: :runtime
40
40
  prerelease: false
41
- requirement: &id002 !ruby/object:Gem::Requirement
41
+ name: railties
42
+ version_requirements: &id002 !ruby/object:Gem::Requirement
42
43
  none: false
43
44
  requirements:
44
45
  - - ">="
@@ -49,12 +50,12 @@ dependencies:
49
50
  - 0
50
51
  - 3
51
52
  version: 3.0.3
52
- type: :runtime
53
- version_requirements: *id002
53
+ requirement: *id002
54
54
  - !ruby/object:Gem::Dependency
55
- name: braintree
55
+ type: :runtime
56
56
  prerelease: false
57
- requirement: &id003 !ruby/object:Gem::Requirement
57
+ name: braintree
58
+ version_requirements: &id003 !ruby/object:Gem::Requirement
58
59
  none: false
59
60
  requirements:
60
61
  - - ">="
@@ -65,12 +66,12 @@ dependencies:
65
66
  - 6
66
67
  - 2
67
68
  version: 2.6.2
68
- type: :runtime
69
- version_requirements: *id003
69
+ requirement: *id003
70
70
  - !ruby/object:Gem::Dependency
71
- name: sham_rack
71
+ type: :runtime
72
72
  prerelease: false
73
- requirement: &id004 !ruby/object:Gem::Requirement
73
+ name: sham_rack
74
+ version_requirements: &id004 !ruby/object:Gem::Requirement
74
75
  none: false
75
76
  requirements:
76
77
  - - "="
@@ -81,12 +82,12 @@ dependencies:
81
82
  - 3
82
83
  - 3
83
84
  version: 1.3.3
84
- type: :runtime
85
- version_requirements: *id004
85
+ requirement: *id004
86
86
  - !ruby/object:Gem::Dependency
87
- name: sinatra
87
+ type: :runtime
88
88
  prerelease: false
89
- requirement: &id005 !ruby/object:Gem::Requirement
89
+ name: sinatra
90
+ version_requirements: &id005 !ruby/object:Gem::Requirement
90
91
  none: false
91
92
  requirements:
92
93
  - - "="
@@ -97,12 +98,12 @@ dependencies:
97
98
  - 1
98
99
  - 2
99
100
  version: 1.1.2
100
- type: :runtime
101
- version_requirements: *id005
101
+ requirement: *id005
102
102
  - !ruby/object:Gem::Dependency
103
- name: aruba
103
+ type: :development
104
104
  prerelease: false
105
- requirement: &id006 !ruby/object:Gem::Requirement
105
+ name: aruba
106
+ version_requirements: &id006 !ruby/object:Gem::Requirement
106
107
  none: false
107
108
  requirements:
108
109
  - - "="
@@ -113,8 +114,7 @@ dependencies:
113
114
  - 2
114
115
  - 6
115
116
  version: 0.2.6
116
- type: :development
117
- version_requirements: *id006
117
+ requirement: *id006
118
118
  description: Clearance-based Rails engine for Software as a Service (Saas) that provides account and project management
119
119
  email: support@thoughtbot.com
120
120
  executables: []