saucy 0.8.2 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +19 -0
- data/app/controllers/accounts_controller.rb +8 -3
- data/app/helpers/coupons_helper.rb +7 -0
- data/app/models/coupon.rb +4 -0
- data/app/models/signup.rb +10 -9
- data/app/views/plans/_plan.html.erb +5 -2
- data/lib/generators/saucy/features/templates/factories.rb +4 -0
- data/lib/generators/saucy/features/templates/features/sign_up_coupon.feature +31 -0
- data/lib/generators/saucy/features/templates/step_definitions/account_steps.rb +11 -0
- data/lib/generators/saucy/install/templates/create_saucy_tables.rb +7 -0
- data/lib/saucy.rb +1 -1
- data/lib/saucy/account.rb +7 -1
- data/lib/saucy/coupons.rb +15 -0
- data/lib/saucy/engine.rb +4 -0
- data/spec/controllers/accounts_controller_spec.rb +3 -2
- data/spec/models/account_spec.rb +12 -1
- data/spec/models/coupon_spec.rb +6 -0
- data/spec/models/signup_spec.rb +15 -0
- metadata +31 -26
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
0.8.3
|
2
|
+
|
3
|
+
Added coupon support. In your app, add the following migration:
|
4
|
+
|
5
|
+
class AddCoupons < ActiveRecord::Migration
|
6
|
+
def self.up
|
7
|
+
create_table :coupons do |table|
|
8
|
+
table.string :name, :default => "", :null => false
|
9
|
+
table.integer :free_months, :default => 1, :null => false
|
10
|
+
end
|
11
|
+
add_column :accounts, :coupon_id, :integer
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
remove_column :accounts, :coupon_id
|
16
|
+
drop_table :coupons
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
1
20
|
0.8.2
|
2
21
|
|
3
22
|
Added a uniqueness validation and database constraint for member_id/project_id on permissions. This prevents somebody from being added to the same project twice, which would display it multiple times in the project index and dropdown. When upgrading, make sure this index is in place:
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class AccountsController < ApplicationController
|
2
|
+
include Saucy::Coupons
|
3
|
+
|
2
4
|
before_filter :authorize, :only => [:index, :edit, :update]
|
3
5
|
before_filter :authorize_admin, :except => [:new, :create, :index]
|
4
6
|
before_filter :ensure_active_account, :only => [:edit, :update]
|
@@ -12,10 +14,13 @@ class AccountsController < ApplicationController
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def create
|
15
|
-
@plan
|
17
|
+
@plan = Plan.find(params[:plan_id])
|
16
18
|
@signup = Signup.new(params[:signup])
|
17
|
-
|
18
|
-
@signup.
|
19
|
+
|
20
|
+
@signup.coupon = Coupon.find_by_name(session[:coupon_name])
|
21
|
+
@signup.user = current_user
|
22
|
+
@signup.plan = @plan
|
23
|
+
|
19
24
|
if @signup.save
|
20
25
|
Saucy::Configuration.notify("account_created", :request => request,
|
21
26
|
:account => @signup.account)
|
data/app/models/signup.rb
CHANGED
@@ -7,13 +7,13 @@ class Signup
|
|
7
7
|
|
8
8
|
FIELDS = {
|
9
9
|
:account => {
|
10
|
-
:cardholder_name
|
11
|
-
:billing_email
|
12
|
-
:card_number
|
13
|
-
:expiration_month
|
14
|
-
:expiration_year
|
10
|
+
:cardholder_name => :cardholder_name,
|
11
|
+
:billing_email => :billing_email,
|
12
|
+
:card_number => :card_number,
|
13
|
+
:expiration_month => :expiration_month,
|
14
|
+
:expiration_year => :expiration_year,
|
15
15
|
:verification_code => :verification_code,
|
16
|
-
:plan
|
16
|
+
:plan => :plan
|
17
17
|
},
|
18
18
|
:user => {
|
19
19
|
:email => :email,
|
@@ -21,8 +21,8 @@ class Signup
|
|
21
21
|
}
|
22
22
|
}.freeze
|
23
23
|
|
24
|
-
attr_accessor :billing_email, :password, :cardholder_name, :email,
|
25
|
-
:card_number, :expiration_month, :expiration_year, :plan, :verification_code
|
24
|
+
attr_accessor :billing_email, :password, :cardholder_name, :email,
|
25
|
+
:card_number, :expiration_month, :expiration_year, :plan, :verification_code, :coupon
|
26
26
|
|
27
27
|
def initialize(attributes = {})
|
28
28
|
if attributes
|
@@ -43,7 +43,7 @@ class Signup
|
|
43
43
|
populate_additional_account_fields
|
44
44
|
|
45
45
|
if !existing_user
|
46
|
-
delegate_attributes_for(:user)
|
46
|
+
delegate_attributes_for(:user)
|
47
47
|
populate_additional_user_fields
|
48
48
|
end
|
49
49
|
|
@@ -154,6 +154,7 @@ class Signup
|
|
154
154
|
end
|
155
155
|
|
156
156
|
def save!
|
157
|
+
account.coupon = coupon
|
157
158
|
Account.transaction do
|
158
159
|
account.save!
|
159
160
|
user.save!
|
@@ -11,8 +11,11 @@
|
|
11
11
|
</p>
|
12
12
|
<ul class="features">
|
13
13
|
<% if plan.trial? -%>
|
14
|
-
<li class="trial"
|
15
|
-
|
14
|
+
<li class="trial">
|
15
|
+
<%= t(".trial_will_expire",
|
16
|
+
:default => "Your trial will expire after %{days_until_expiration} days.",
|
17
|
+
:days_until_expiration => days_until_expiration) %>
|
18
|
+
</li>
|
16
19
|
<% else -%>
|
17
20
|
<% plan.limits.numbered.each do |limit| %>
|
18
21
|
<%= content_tag_for :li, limit, :class => limit.name do %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: Sign up with coupon
|
2
|
+
|
3
|
+
In order to try the site with extended time
|
4
|
+
As a user
|
5
|
+
I want to use a coupon provided to me
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following plan exists:
|
9
|
+
| name | price | trial |
|
10
|
+
| Free | 0 | true |
|
11
|
+
And the following coupon exists:
|
12
|
+
| name | free months |
|
13
|
+
| RAMEN | 3 |
|
14
|
+
|
15
|
+
Scenario: Sign up for a trial plan with coupon
|
16
|
+
When I sign up with the "RAMEN" coupon for the "Free" plan
|
17
|
+
Then I should see "Your trial will expire after 90 days"
|
18
|
+
When I fill in "Email" with "email@person.com"
|
19
|
+
And I fill in "Password" with "password"
|
20
|
+
And I press "Sign up"
|
21
|
+
Then I should see "created"
|
22
|
+
|
23
|
+
Scenario: Receive a reminder about an expiring trial plan with coupon
|
24
|
+
Given a "Free" account with "RAMEN" coupon exists with a name of "Test" created 83 days ago
|
25
|
+
And a user exists with an email of "admin@example.com"
|
26
|
+
And "admin@example.com" is an admin of the "Test" account
|
27
|
+
When the daily Saucy jobs are processed
|
28
|
+
And I sign in as "admin@example.com"
|
29
|
+
And I follow the link sent to "admin@example.com" with subject "Your trial is expiring soon"
|
30
|
+
Then I should be on the upgrade plan page for the "Test" account
|
31
|
+
|
@@ -3,6 +3,17 @@ Given /^an? "([^"]+)" account exists with a name of "([^"]*)" created (\d+) days
|
|
3
3
|
Factory(:account, :created_at => days.to_i.days.ago, :plan => plan, :name => account_name)
|
4
4
|
end
|
5
5
|
|
6
|
+
Given /^a "([^"]*)" account with "([^"]*)" coupon exists with a name of "([^"]*)" created (\d+) days ago$/ do |plan_name, coupon_name, account_name, days|
|
7
|
+
plan = Plan.find_by_name!(plan_name)
|
8
|
+
coupon = Coupon.find_by_name!(coupon_name)
|
9
|
+
Factory(:account, :created_at => days.to_i.days.ago, :plan => plan, :coupon => coupon, :name => account_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
Given /^I sign up with the "([^"]*)" coupon for the "([^"]+)" plan$/ do |coupon_name, plan_name|
|
13
|
+
plan = Plan.find_by_name!(plan_name)
|
14
|
+
visit new_plan_account_path(plan, :coupon => coupon_name)
|
15
|
+
end
|
16
|
+
|
6
17
|
Given /^the "([^"]*)" account was created (\d+) days ago$/ do |account_name, days|
|
7
18
|
Account.find_by_name!(account_name).tap do |account|
|
8
19
|
account.created_at = days.to_i.days.ago
|
@@ -12,6 +12,7 @@ class CreateSaucyTables < ActiveRecord::Migration
|
|
12
12
|
|
13
13
|
create_table :accounts do |table|
|
14
14
|
table.belongs_to :plan
|
15
|
+
table.belongs_to :coupon
|
15
16
|
table.string :name
|
16
17
|
table.string :keyword
|
17
18
|
table.datetime :created_at
|
@@ -28,11 +29,17 @@ class CreateSaucyTables < ActiveRecord::Migration
|
|
28
29
|
table.datetime :trial_expires_at
|
29
30
|
end
|
30
31
|
add_index :accounts, :plan_id
|
32
|
+
add_index :accounts, :coupon_id
|
31
33
|
add_index :accounts, :keyword, :unique => true
|
32
34
|
add_index :accounts, :next_billing_date
|
33
35
|
add_index :accounts, :created_at
|
34
36
|
add_index :accounts, :trial_expires_at
|
35
37
|
|
38
|
+
create_table :coupons, :force => true do |t|
|
39
|
+
t.string :name, :default => "", :null => false
|
40
|
+
t.integer :free_months, :default => 1, :null => false
|
41
|
+
end
|
42
|
+
|
36
43
|
create_table :invitations do |table|
|
37
44
|
table.string :email
|
38
45
|
table.integer :account_id
|
data/lib/saucy.rb
CHANGED
data/lib/saucy/account.rb
CHANGED
@@ -16,6 +16,7 @@ module Saucy
|
|
16
16
|
:conditions => { 'memberships.admin' => false }
|
17
17
|
|
18
18
|
belongs_to :plan
|
19
|
+
belongs_to :coupon
|
19
20
|
|
20
21
|
delegate :free?, :billed?, :trial?, :to => :plan
|
21
22
|
|
@@ -69,7 +70,12 @@ module Saucy
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def set_trial_expiration
|
72
|
-
|
73
|
+
number_free_days = if coupon
|
74
|
+
30 * coupon.free_months
|
75
|
+
else
|
76
|
+
30
|
77
|
+
end
|
78
|
+
self.trial_expires_at = number_free_days.days.from_now(created_at || Time.now)
|
73
79
|
end
|
74
80
|
|
75
81
|
def users_count
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Saucy
|
2
|
+
module Coupons
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_filter :set_coupon_cookie
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_coupon_cookie
|
10
|
+
if params[:coupon] && coupon = Coupon.find_by_name(params[:coupon])
|
11
|
+
session[:coupon_name] ||= params[:coupon]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/saucy/engine.rb
CHANGED
@@ -12,7 +12,7 @@ describe AccountsController, "routes" do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
describe AccountsController, "new" do
|
15
|
-
let(:signup) { stub('signup') }
|
15
|
+
let(:signup) { stub('signup', :coupon= => nil) }
|
16
16
|
let(:plan) { Factory(:plan) }
|
17
17
|
|
18
18
|
before do
|
@@ -40,6 +40,7 @@ describe AccountsController, "successful create for a confirmed user" do
|
|
40
40
|
let(:account) { Factory.stub(:account) }
|
41
41
|
let(:signup) { stub('signup', :user => user,
|
42
42
|
:user= => nil,
|
43
|
+
:coupon= => nil,
|
43
44
|
:plan= => plan,
|
44
45
|
:account => account) }
|
45
46
|
let(:signup_attributes) { "attributes" }
|
@@ -76,7 +77,7 @@ describe AccountsController, "successful create for a confirmed user" do
|
|
76
77
|
end
|
77
78
|
|
78
79
|
describe AccountsController, "failed create" do
|
79
|
-
let(:signup) { stub('signup', :user= => nil, :plan= => plan) }
|
80
|
+
let(:signup) { stub('signup', :user= => nil, :coupon= => nil, :plan= => plan) }
|
80
81
|
let(:signup_attributes) { "attributes" }
|
81
82
|
let(:plan) { Factory(:plan) }
|
82
83
|
|
data/spec/models/account_spec.rb
CHANGED
@@ -7,9 +7,10 @@ describe Account do
|
|
7
7
|
it { should have_many(:users).through(:memberships) }
|
8
8
|
it { should have_many(:projects) }
|
9
9
|
it { should belong_to(:plan) }
|
10
|
+
it { should belong_to(:coupon) }
|
10
11
|
|
11
12
|
it { should validate_uniqueness_of(:keyword) }
|
12
|
-
it { should validate_presence_of(
|
13
|
+
it { should validate_presence_of(:name) }
|
13
14
|
it { should validate_presence_of(:keyword) }
|
14
15
|
it { should validate_presence_of(:plan_id) }
|
15
16
|
|
@@ -216,3 +217,13 @@ describe Account do
|
|
216
217
|
end
|
217
218
|
end
|
218
219
|
|
220
|
+
describe Account, "with coupon" do
|
221
|
+
let(:free_months) { 3 }
|
222
|
+
let(:coupon) { Factory(:coupon, :free_months => free_months) }
|
223
|
+
|
224
|
+
subject { Factory(:account, :coupon => coupon) }
|
225
|
+
|
226
|
+
it "sets the trial expires at based on coupon free months" do
|
227
|
+
subject.reload.trial_expires_at.to_s.should == (free_months * 30).days.from_now.to_s
|
228
|
+
end
|
229
|
+
end
|
data/spec/models/signup_spec.rb
CHANGED
@@ -175,3 +175,18 @@ describe Signup, "with an account that doesn't save" do
|
|
175
175
|
subject.save.should_not be
|
176
176
|
end
|
177
177
|
end
|
178
|
+
|
179
|
+
describe Signup, "with coupon" do
|
180
|
+
let(:coupon) { Factory(:coupon) }
|
181
|
+
|
182
|
+
subject { Factory.build(:signup) }
|
183
|
+
|
184
|
+
before do
|
185
|
+
subject.coupon = coupon
|
186
|
+
subject.save
|
187
|
+
end
|
188
|
+
|
189
|
+
it "saves coupon to account" do
|
190
|
+
subject.account.reload.coupon.should == coupon
|
191
|
+
end
|
192
|
+
end
|
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:
|
4
|
+
hash: 57
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 8
|
9
|
-
-
|
10
|
-
version: 0.8.
|
9
|
+
- 3
|
10
|
+
version: 0.8.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- thoughtbot, inc.
|
@@ -18,11 +18,12 @@ autorequire:
|
|
18
18
|
bindir: bin
|
19
19
|
cert_chain: []
|
20
20
|
|
21
|
-
date: 2011-06-
|
21
|
+
date: 2011-06-27 00:00:00 -04:00
|
22
22
|
default_executable:
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
25
|
-
|
25
|
+
name: clearance
|
26
|
+
prerelease: false
|
26
27
|
requirement: &id001 !ruby/object:Gem::Requirement
|
27
28
|
none: false
|
28
29
|
requirements:
|
@@ -34,11 +35,11 @@ dependencies:
|
|
34
35
|
- 11
|
35
36
|
- 0
|
36
37
|
version: 0.11.0
|
38
|
+
type: :runtime
|
37
39
|
version_requirements: *id001
|
38
|
-
name: clearance
|
39
|
-
prerelease: false
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
|
41
|
+
name: formtastic
|
42
|
+
prerelease: false
|
42
43
|
requirement: &id002 !ruby/object:Gem::Requirement
|
43
44
|
none: false
|
44
45
|
requirements:
|
@@ -49,11 +50,11 @@ dependencies:
|
|
49
50
|
- 1
|
50
51
|
- 2
|
51
52
|
version: "1.2"
|
53
|
+
type: :runtime
|
52
54
|
version_requirements: *id002
|
53
|
-
name: formtastic
|
54
|
-
prerelease: false
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
|
56
|
+
name: railties
|
57
|
+
prerelease: false
|
57
58
|
requirement: &id003 !ruby/object:Gem::Requirement
|
58
59
|
none: false
|
59
60
|
requirements:
|
@@ -65,11 +66,11 @@ dependencies:
|
|
65
66
|
- 0
|
66
67
|
- 3
|
67
68
|
version: 3.0.3
|
69
|
+
type: :runtime
|
68
70
|
version_requirements: *id003
|
69
|
-
name: railties
|
70
|
-
prerelease: false
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
|
-
|
72
|
+
name: braintree
|
73
|
+
prerelease: false
|
73
74
|
requirement: &id004 !ruby/object:Gem::Requirement
|
74
75
|
none: false
|
75
76
|
requirements:
|
@@ -81,11 +82,11 @@ dependencies:
|
|
81
82
|
- 6
|
82
83
|
- 2
|
83
84
|
version: 2.6.2
|
85
|
+
type: :runtime
|
84
86
|
version_requirements: *id004
|
85
|
-
name: braintree
|
86
|
-
prerelease: false
|
87
87
|
- !ruby/object:Gem::Dependency
|
88
|
-
|
88
|
+
name: sham_rack
|
89
|
+
prerelease: false
|
89
90
|
requirement: &id005 !ruby/object:Gem::Requirement
|
90
91
|
none: false
|
91
92
|
requirements:
|
@@ -97,11 +98,11 @@ dependencies:
|
|
97
98
|
- 3
|
98
99
|
- 3
|
99
100
|
version: 1.3.3
|
101
|
+
type: :runtime
|
100
102
|
version_requirements: *id005
|
101
|
-
name: sham_rack
|
102
|
-
prerelease: false
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
|
-
|
104
|
+
name: sinatra
|
105
|
+
prerelease: false
|
105
106
|
requirement: &id006 !ruby/object:Gem::Requirement
|
106
107
|
none: false
|
107
108
|
requirements:
|
@@ -113,11 +114,11 @@ dependencies:
|
|
113
114
|
- 1
|
114
115
|
- 2
|
115
116
|
version: 1.1.2
|
117
|
+
type: :runtime
|
116
118
|
version_requirements: *id006
|
117
|
-
name: sinatra
|
118
|
-
prerelease: false
|
119
119
|
- !ruby/object:Gem::Dependency
|
120
|
-
|
120
|
+
name: aruba
|
121
|
+
prerelease: false
|
121
122
|
requirement: &id007 !ruby/object:Gem::Requirement
|
122
123
|
none: false
|
123
124
|
requirements:
|
@@ -129,9 +130,8 @@ dependencies:
|
|
129
130
|
- 2
|
130
131
|
- 6
|
131
132
|
version: 0.2.6
|
133
|
+
type: :development
|
132
134
|
version_requirements: *id007
|
133
|
-
name: aruba
|
134
|
-
prerelease: false
|
135
135
|
description: Clearance-based Rails engine for Software as a Service (Saas) that provides account and project management
|
136
136
|
email: support@thoughtbot.com
|
137
137
|
executables: []
|
@@ -154,9 +154,11 @@ files:
|
|
154
154
|
- app/controllers/memberships_controller.rb
|
155
155
|
- app/controllers/plans_controller.rb
|
156
156
|
- app/controllers/profiles_controller.rb
|
157
|
+
- app/helpers/coupons_helper.rb
|
157
158
|
- app/helpers/limits_helper.rb
|
158
159
|
- app/mailers/billing_mailer.rb
|
159
160
|
- app/mailers/invitation_mailer.rb
|
161
|
+
- app/models/coupon.rb
|
160
162
|
- app/models/invitation.rb
|
161
163
|
- app/models/limit.rb
|
162
164
|
- app/models/membership.rb
|
@@ -225,6 +227,7 @@ files:
|
|
225
227
|
- lib/saucy/account.rb
|
226
228
|
- lib/saucy/account_authorization.rb
|
227
229
|
- lib/saucy/configuration.rb
|
230
|
+
- lib/saucy/coupons.rb
|
228
231
|
- lib/saucy/engine.rb
|
229
232
|
- lib/saucy/fake_braintree.rb
|
230
233
|
- lib/saucy/layouts.rb
|
@@ -253,6 +256,7 @@ files:
|
|
253
256
|
- lib/generators/saucy/features/templates/features/new_account.feature
|
254
257
|
- lib/generators/saucy/features/templates/features/project_dropdown.feature
|
255
258
|
- lib/generators/saucy/features/templates/features/sign_up.feature
|
259
|
+
- lib/generators/saucy/features/templates/features/sign_up_coupon.feature
|
256
260
|
- lib/generators/saucy/features/templates/features/sign_up_paid.feature
|
257
261
|
- lib/generators/saucy/features/templates/features/trial_plans.feature
|
258
262
|
- lib/generators/saucy/features/templates/README
|
@@ -268,6 +272,7 @@ files:
|
|
268
272
|
- spec/mailers/billing_mailer_spec.rb
|
269
273
|
- spec/mailers/invitiation_mailer_spec.rb
|
270
274
|
- spec/models/account_spec.rb
|
275
|
+
- spec/models/coupon_spec.rb
|
271
276
|
- spec/models/invitation_spec.rb
|
272
277
|
- spec/models/limit_spec.rb
|
273
278
|
- spec/models/membership_spec.rb
|
@@ -317,7 +322,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
317
322
|
requirements: []
|
318
323
|
|
319
324
|
rubyforge_project:
|
320
|
-
rubygems_version: 1.6.
|
325
|
+
rubygems_version: 1.6.2
|
321
326
|
signing_key:
|
322
327
|
specification_version: 3
|
323
328
|
summary: Clearance-based Rails engine for SaaS
|