saasy 0.0.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.
Files changed (146) hide show
  1. data/CHANGELOG.md +114 -0
  2. data/Gemfile +26 -0
  3. data/README.md +118 -0
  4. data/Rakefile +38 -0
  5. data/app/controllers/accounts_controller.rb +68 -0
  6. data/app/controllers/billings_controller.rb +25 -0
  7. data/app/controllers/invitations_controller.rb +65 -0
  8. data/app/controllers/memberships_controller.rb +45 -0
  9. data/app/controllers/plans_controller.rb +24 -0
  10. data/app/controllers/profiles_controller.rb +19 -0
  11. data/app/helpers/limits_helper.rb +13 -0
  12. data/app/mailers/billing_mailer.rb +53 -0
  13. data/app/mailers/invitation_mailer.rb +18 -0
  14. data/app/models/invitation.rb +113 -0
  15. data/app/models/limit.rb +49 -0
  16. data/app/models/membership.rb +26 -0
  17. data/app/models/permission.rb +19 -0
  18. data/app/models/signup.rb +163 -0
  19. data/app/views/accounts/_account.html.erb +9 -0
  20. data/app/views/accounts/_blank_slate.html.erb +6 -0
  21. data/app/views/accounts/_projects.html.erb +12 -0
  22. data/app/views/accounts/_subnav.html.erb +10 -0
  23. data/app/views/accounts/edit.html.erb +34 -0
  24. data/app/views/accounts/index.html.erb +9 -0
  25. data/app/views/accounts/new.html.erb +36 -0
  26. data/app/views/billing_mailer/completed_trial.text.erb +13 -0
  27. data/app/views/billing_mailer/expiring_trial.text.erb +15 -0
  28. data/app/views/billing_mailer/new_unactivated.text.erb +1 -0
  29. data/app/views/billing_mailer/problem.html.erb +13 -0
  30. data/app/views/billing_mailer/problem.text.erb +14 -0
  31. data/app/views/billing_mailer/receipt.html.erb +41 -0
  32. data/app/views/billing_mailer/receipt.text.erb +25 -0
  33. data/app/views/billings/_form.html.erb +8 -0
  34. data/app/views/billings/edit.html.erb +13 -0
  35. data/app/views/billings/show.html.erb +29 -0
  36. data/app/views/invitation_mailer/invitation.text.erb +6 -0
  37. data/app/views/invitations/new.html.erb +17 -0
  38. data/app/views/invitations/show.html.erb +22 -0
  39. data/app/views/layouts/saucy.html.erb +36 -0
  40. data/app/views/limits/_meter.html.erb +13 -0
  41. data/app/views/memberships/edit.html.erb +21 -0
  42. data/app/views/memberships/index.html.erb +17 -0
  43. data/app/views/plans/_plan.html.erb +32 -0
  44. data/app/views/plans/_terms.html.erb +15 -0
  45. data/app/views/plans/edit.html.erb +33 -0
  46. data/app/views/plans/index.html.erb +12 -0
  47. data/app/views/profiles/_inputs.html.erb +5 -0
  48. data/app/views/profiles/edit.html.erb +36 -0
  49. data/app/views/projects/_form.html.erb +36 -0
  50. data/app/views/projects/edit.html.erb +22 -0
  51. data/app/views/projects/index.html.erb +28 -0
  52. data/app/views/projects/new.html.erb +13 -0
  53. data/app/views/projects/show.html.erb +0 -0
  54. data/app/views/shared/_project_dropdown.html.erb +55 -0
  55. data/app/views/shared/_saucy_javascript.html.erb +33 -0
  56. data/config/locales/en.yml +37 -0
  57. data/config/routes.rb +19 -0
  58. data/features/run_features.feature +83 -0
  59. data/features/step_definitions/clearance_steps.rb +45 -0
  60. data/features/step_definitions/rails_steps.rb +73 -0
  61. data/features/step_definitions/saucy_steps.rb +8 -0
  62. data/features/support/env.rb +4 -0
  63. data/features/support/file.rb +11 -0
  64. data/lib/generators/saucy/base.rb +18 -0
  65. data/lib/generators/saucy/features/features_generator.rb +91 -0
  66. data/lib/generators/saucy/features/templates/README +3 -0
  67. data/lib/generators/saucy/features/templates/factories.rb +71 -0
  68. data/lib/generators/saucy/features/templates/features/edit_profile.feature +9 -0
  69. data/lib/generators/saucy/features/templates/features/edit_project_permissions.feature +37 -0
  70. data/lib/generators/saucy/features/templates/features/edit_user_permissions.feature +47 -0
  71. data/lib/generators/saucy/features/templates/features/manage_account.feature +35 -0
  72. data/lib/generators/saucy/features/templates/features/manage_billing.feature +93 -0
  73. data/lib/generators/saucy/features/templates/features/manage_plan.feature +143 -0
  74. data/lib/generators/saucy/features/templates/features/manage_projects.feature +139 -0
  75. data/lib/generators/saucy/features/templates/features/manage_users.feature +142 -0
  76. data/lib/generators/saucy/features/templates/features/new_account.feature +33 -0
  77. data/lib/generators/saucy/features/templates/features/project_dropdown.feature +77 -0
  78. data/lib/generators/saucy/features/templates/features/sign_up.feature +32 -0
  79. data/lib/generators/saucy/features/templates/features/sign_up_paid.feature +65 -0
  80. data/lib/generators/saucy/features/templates/features/trial_plans.feature +82 -0
  81. data/lib/generators/saucy/features/templates/step_definitions/account_steps.rb +30 -0
  82. data/lib/generators/saucy/features/templates/step_definitions/braintree_steps.rb +25 -0
  83. data/lib/generators/saucy/features/templates/step_definitions/cron_steps.rb +23 -0
  84. data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +40 -0
  85. data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
  86. data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +51 -0
  87. data/lib/generators/saucy/features/templates/step_definitions/plan_steps.rb +16 -0
  88. data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
  89. data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +37 -0
  90. data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +100 -0
  91. data/lib/generators/saucy/features/templates/support/braintree.rb +5 -0
  92. data/lib/generators/saucy/install/install_generator.rb +40 -0
  93. data/lib/generators/saucy/install/templates/controllers/projects_controller.rb +3 -0
  94. data/lib/generators/saucy/install/templates/create_saucy_tables.rb +115 -0
  95. data/lib/generators/saucy/install/templates/models/account.rb +3 -0
  96. data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
  97. data/lib/generators/saucy/install/templates/models/project.rb +3 -0
  98. data/lib/generators/saucy/specs/specs_generator.rb +20 -0
  99. data/lib/generators/saucy/specs/templates/support/braintree.rb +5 -0
  100. data/lib/generators/saucy/views/views_generator.rb +23 -0
  101. data/lib/saucy.rb +10 -0
  102. data/lib/saucy/account.rb +132 -0
  103. data/lib/saucy/account_authorization.rb +67 -0
  104. data/lib/saucy/configuration.rb +29 -0
  105. data/lib/saucy/engine.rb +35 -0
  106. data/lib/saucy/fake_braintree.rb +134 -0
  107. data/lib/saucy/layouts.rb +36 -0
  108. data/lib/saucy/plan.rb +54 -0
  109. data/lib/saucy/project.rb +125 -0
  110. data/lib/saucy/projects_controller.rb +94 -0
  111. data/lib/saucy/railties/tasks.rake +28 -0
  112. data/lib/saucy/routing_extensions.rb +121 -0
  113. data/lib/saucy/subscription.rb +237 -0
  114. data/lib/saucy/user.rb +30 -0
  115. data/spec/controllers/accounts_controller_spec.rb +228 -0
  116. data/spec/controllers/application_controller_spec.rb +32 -0
  117. data/spec/controllers/invitations_controller_spec.rb +215 -0
  118. data/spec/controllers/memberships_controller_spec.rb +117 -0
  119. data/spec/controllers/plans_controller_spec.rb +13 -0
  120. data/spec/controllers/profiles_controller_spec.rb +48 -0
  121. data/spec/controllers/projects_controller_spec.rb +216 -0
  122. data/spec/environment.rb +95 -0
  123. data/spec/layouts_spec.rb +21 -0
  124. data/spec/mailers/billing_mailer_spec.rb +68 -0
  125. data/spec/mailers/invitiation_mailer_spec.rb +19 -0
  126. data/spec/models/account_spec.rb +218 -0
  127. data/spec/models/invitation_spec.rb +320 -0
  128. data/spec/models/limit_spec.rb +70 -0
  129. data/spec/models/membership_spec.rb +37 -0
  130. data/spec/models/permission_spec.rb +30 -0
  131. data/spec/models/plan_spec.rb +81 -0
  132. data/spec/models/project_spec.rb +223 -0
  133. data/spec/models/signup_spec.rb +177 -0
  134. data/spec/models/subscription_spec.rb +481 -0
  135. data/spec/models/user_spec.rb +72 -0
  136. data/spec/route_extensions_spec.rb +51 -0
  137. data/spec/saucy_spec.rb +62 -0
  138. data/spec/scaffold/config/routes.rb +5 -0
  139. data/spec/spec_helper.rb +39 -0
  140. data/spec/support/authentication_helpers.rb +81 -0
  141. data/spec/support/authorization_helpers.rb +56 -0
  142. data/spec/support/braintree.rb +7 -0
  143. data/spec/support/clearance_matchers.rb +55 -0
  144. data/spec/support/notifications.rb +57 -0
  145. data/spec/views/accounts/_account.html.erb_spec.rb +37 -0
  146. metadata +325 -0
@@ -0,0 +1,45 @@
1
+ class MembershipsController < ApplicationController
2
+ before_filter :authorize_admin
3
+ layout Saucy::Layouts.to_proc
4
+
5
+ def index
6
+ @memberships = current_account.memberships_by_name
7
+ render
8
+ end
9
+
10
+ def edit
11
+ find_membership
12
+ @projects = current_account.projects_by_name
13
+ render
14
+ end
15
+
16
+ def update
17
+ find_membership.update_attributes!(params[:membership])
18
+ flash[:success] = "Permissions updated."
19
+ redirect_to account_memberships_url(current_account)
20
+ end
21
+
22
+ def destroy
23
+ find_membership.destroy
24
+ flash[:success] = "User removed."
25
+ if @membership.user == current_user
26
+ redirect_to edit_profile_url
27
+ else
28
+ redirect_to account_memberships_url(current_account)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def find_membership
35
+ @membership ||= Membership.find(params[:id], :include => :account)
36
+ end
37
+
38
+ def current_account
39
+ if params[:id]
40
+ find_membership.account
41
+ else
42
+ super
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ class PlansController < ApplicationController
2
+ layout Saucy::Layouts.to_proc
3
+
4
+ def index
5
+ @plans = Plan.ordered
6
+ end
7
+
8
+ def edit
9
+ @plans = Plan.ordered
10
+ @account = current_account
11
+ end
12
+
13
+ def update
14
+ @plans = Plan.ordered
15
+ @account = current_account
16
+ Saucy::Configuration.notify("plan_upgraded", :account => @account,
17
+ :request => request)
18
+ if @account.save_customer_and_subscription!(params[:account])
19
+ redirect_to edit_account_path(@account), :notice => t('.update.notice', :default => "Plan changed successfully")
20
+ else
21
+ render :edit
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ class ProfilesController < ApplicationController
2
+ layout Saucy::Layouts.to_proc
3
+
4
+ before_filter :authorize
5
+
6
+ def edit
7
+ @user = current_user
8
+ end
9
+
10
+ def update
11
+ @user = current_user
12
+ if @user.update_attributes(params[:user])
13
+ flash[:notice] = "Your user account has been updated."
14
+ redirect_to edit_profile_url
15
+ else
16
+ render :action => :edit
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module LimitsHelper
2
+ def current_percentage(limit, account)
3
+ number_to_percentage((limit.current_count(account).to_f / limit.value) * 100)
4
+ end
5
+
6
+ def render_limit_meter(limit)
7
+ if FileTest.exist?(File.join(Rails.root, 'app', 'views', 'limits' , "_#{limit.name}_meter.html.erb"))
8
+ render "limits/#{limit.name}_meter", :limit => limit
9
+ else
10
+ render "limits/meter", :limit => limit
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ class BillingMailer < ActionMailer::Base
2
+ def receipt(account, transaction)
3
+ @account = account
4
+ @transaction = transaction
5
+ mail(:to => account.customer.email,
6
+ :subject => I18n.t(:subject,
7
+ :scope => [:saucy, :mailers, :billing_mailer, :receipt],
8
+ :default => "Subscription receipt"),
9
+ :reply_to => Saucy::Configuration.support_email_address,
10
+ :from => Saucy::Configuration.support_email_address)
11
+ end
12
+
13
+ def problem(account, transaction)
14
+ @account = account
15
+ mail(:to => account.customer.email,
16
+ :subject => I18n.t(:subject,
17
+ :scope => [:saucy, :mailers, :billing_mailer, :problem],
18
+ :default => "Problem with subscription billing"),
19
+ :reply_to => Saucy::Configuration.support_email_address,
20
+ :from => Saucy::Configuration.support_email_address)
21
+ end
22
+
23
+ def expiring_trial(account)
24
+ @account = account
25
+ mail(:to => account.admin_emails,
26
+ :subject => I18n.t(:subject,
27
+ :scope => [:billing_mailer, :expiring_trial],
28
+ :default => "Your trial is expiring soon"),
29
+ :reply_to => Saucy::Configuration.support_email_address,
30
+ :from => Saucy::Configuration.manager_email_address)
31
+ end
32
+
33
+ def new_unactivated(account)
34
+ @account = account
35
+ mail(:to => account.admin_emails,
36
+ :subject => I18n.t(:subject,
37
+ :scope => [:billing_mailer, :new_unactivated],
38
+ :default => "A check in from %{app_name}",
39
+ :app_name => t("app_name")),
40
+ :reply_to => Saucy::Configuration.support_email_address,
41
+ :from => Saucy::Configuration.manager_email_address)
42
+ end
43
+
44
+ def completed_trial(account)
45
+ @account = account
46
+ mail(:to => account.admin_emails,
47
+ :subject => I18n.t(:subject,
48
+ :scope => [:billing_mailer, :completed_trial],
49
+ :default => "Your trial has ended"),
50
+ :reply_to => Saucy::Configuration.support_email_address,
51
+ :from => Saucy::Configuration.manager_email_address)
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ class InvitationMailer < ActionMailer::Base
2
+ def invitation(invitation)
3
+ @invitation = invitation
4
+ subject = I18n.t(:subject,
5
+ :scope => [:saucy, :mailers, :invitation_mailer, :invititation],
6
+ :default => "Invitation")
7
+ mail :to => invitation.email,
8
+ :subject => subject,
9
+ :reply_to => invitation.sender_email,
10
+ :from => sender_name_and_support_address
11
+ end
12
+
13
+ private
14
+
15
+ def sender_name_and_support_address
16
+ %{"#{@invitation.sender_name}" <#{Saucy::Configuration.support_email_address}>}
17
+ end
18
+ end
@@ -0,0 +1,113 @@
1
+ class Invitation < ActiveRecord::Base
2
+ belongs_to :account
3
+ belongs_to :sender, :class_name => 'User'
4
+ validates_presence_of :account_id
5
+ validates_presence_of :email
6
+ has_and_belongs_to_many :projects
7
+
8
+ before_create :generate_code
9
+ after_create :deliver_invitation
10
+
11
+ attr_accessor :new_user_name, :new_user_password, :authenticating_user_password, :existing_user
12
+ attr_writer :new_user_email, :authenticating_user_email
13
+ attr_protected :account_id, :used
14
+ attr_reader :user
15
+
16
+ validate :validate_accepting_user, :on => :update
17
+
18
+ def account_name
19
+ account.name
20
+ end
21
+
22
+ def accept(attributes)
23
+ self.attributes = attributes
24
+ self.used = true
25
+ @user = existing_user || authenticating_user || new_user
26
+ if valid?
27
+ transaction do
28
+ save!
29
+ @user.save!
30
+ @user.memberships.create!(:account => account,
31
+ :admin => admin,
32
+ :projects => projects)
33
+ end
34
+ end
35
+ end
36
+
37
+ def new_user_email
38
+ @new_user_email ||= email
39
+ end
40
+
41
+ def authenticating_user_email
42
+ @authenticating_user_email ||= email
43
+ end
44
+
45
+ def to_param
46
+ code
47
+ end
48
+
49
+ def sender_name
50
+ sender.name
51
+ end
52
+
53
+ def sender_email
54
+ sender.email
55
+ end
56
+
57
+ private
58
+
59
+ def deliver_invitation
60
+ InvitationMailer.invitation(self).deliver
61
+ end
62
+
63
+ def existing_user?
64
+ existing_user.present?
65
+ end
66
+
67
+ def authenticating_user
68
+ if authenticating_user?
69
+ User.find_by_email(authenticating_user_email)
70
+ end
71
+ end
72
+
73
+ def authenticating_user?
74
+ authenticating_user_password.present?
75
+ end
76
+
77
+ def new_user
78
+ User.new(
79
+ :email => new_user_email,
80
+ :password => new_user_password,
81
+ :name => new_user_name
82
+ )
83
+ end
84
+
85
+ def validate_accepting_user
86
+ if authenticating_user?
87
+ validate_authenticating_user
88
+ elsif existing_user?
89
+ true
90
+ else
91
+ validate_new_user
92
+ end
93
+ end
94
+
95
+ def validate_new_user
96
+ user.valid?
97
+ user.errors.each do |field, message|
98
+ errors.add("new_user_#{field}", message)
99
+ end
100
+ end
101
+
102
+ def validate_authenticating_user
103
+ if authenticating_user.nil?
104
+ errors.add(:authenticating_user_email, "isn't signed up")
105
+ elsif !authenticating_user.authenticated?(authenticating_user_password)
106
+ errors.add(:authenticating_user_password, "is incorrect")
107
+ end
108
+ end
109
+
110
+ def generate_code
111
+ self.code = SecureRandom.hex(8)
112
+ end
113
+ end
@@ -0,0 +1,49 @@
1
+ class Limit < ActiveRecord::Base
2
+ belongs_to :plan
3
+
4
+ validates_presence_of :name, :value
5
+
6
+ def self.numbered
7
+ where(:value_type => :number)
8
+ end
9
+
10
+ def self.boolean
11
+ where(:value_type => :boolean)
12
+ end
13
+
14
+ def self.named(name)
15
+ where(:name => name).first
16
+ end
17
+
18
+ def self.within?(limit_name, account)
19
+ if account.plan.limit(limit_name)
20
+ account.plan.limit(limit_name).within?(account)
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ def self.can_add_one?(limit_name, account)
27
+ if account.plan.limit(limit_name)
28
+ account.plan.limit(limit_name).can_add_one?(account)
29
+ else
30
+ true
31
+ end
32
+ end
33
+
34
+ def allowed?
35
+ value != 0
36
+ end
37
+
38
+ def within?(account)
39
+ current_count(account) <= value
40
+ end
41
+
42
+ def can_add_one?(account)
43
+ (current_count(account) + 1) <= value
44
+ end
45
+
46
+ def current_count(account)
47
+ account.send(:"#{name}_count")
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ class Membership < ActiveRecord::Base
2
+ belongs_to :user
3
+ belongs_to :account
4
+ has_many :permissions, :dependent => :destroy
5
+ has_many :projects, :through => :permissions
6
+
7
+ validates_presence_of :user_id
8
+ validates_presence_of :account_id
9
+ validates_uniqueness_of :user_id, :scope => :account_id
10
+
11
+ def self.admin
12
+ where(:admin => true)
13
+ end
14
+
15
+ def name
16
+ user.name
17
+ end
18
+
19
+ def email
20
+ user.email
21
+ end
22
+
23
+ def self.by_name
24
+ joins(:user).order('users.name')
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ class Permission < ActiveRecord::Base
2
+ belongs_to :membership
3
+ belongs_to :project
4
+ belongs_to :user
5
+
6
+ before_validation :assign_user_id_from_membership
7
+
8
+ validates_uniqueness_of :membership_id, :scope => :project_id
9
+
10
+ def user=(ignored)
11
+ raise NotImplementedError, "Use Permission#membership= instead"
12
+ end
13
+
14
+ private
15
+
16
+ def assign_user_id_from_membership
17
+ self.user_id = membership.user_id
18
+ end
19
+ end
@@ -0,0 +1,163 @@
1
+ # Responsible for handling the combo User/Account creation. Also deals with
2
+ # Account creation when signing in as an existing User.
3
+ class Signup
4
+ extend ActiveModel::Naming
5
+ include ActiveModel::Conversion
6
+ include ActiveModel::Validations
7
+
8
+ FIELDS = {
9
+ :account => {
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
+ :verification_code => :verification_code,
16
+ :plan => :plan
17
+ },
18
+ :user => {
19
+ :email => :email,
20
+ :password => :password,
21
+ }
22
+ }.freeze
23
+
24
+ attr_accessor :billing_email, :password, :cardholder_name, :email,
25
+ :card_number, :expiration_month, :expiration_year, :plan, :verification_code
26
+
27
+ def initialize(attributes = {})
28
+ if attributes
29
+ attributes.each do |attribute, value|
30
+ send(:"#{attribute}=", value)
31
+ end
32
+ end
33
+ @check_password = true
34
+ end
35
+
36
+ # used by ActiveModel
37
+ def persisted?
38
+ false
39
+ end
40
+
41
+ def save
42
+ delegate_attributes_for(:account)
43
+ populate_additional_account_fields
44
+
45
+ if !existing_user
46
+ delegate_attributes_for(:user)
47
+ populate_additional_user_fields
48
+ end
49
+
50
+ if valid?
51
+ begin
52
+ save!
53
+ true
54
+ rescue ActiveRecord::RecordNotSaved
55
+ delegate_errors_for(:account)
56
+ delegate_errors_for(:user)
57
+ false
58
+ end
59
+ else
60
+ false
61
+ end
62
+ end
63
+
64
+ def account
65
+ @account ||= Account.new
66
+ end
67
+
68
+ def user
69
+ existing_user || new_user
70
+ end
71
+
72
+ def user=(signed_in_user)
73
+ @check_password = false
74
+ @existing_user = signed_in_user
75
+ end
76
+
77
+ def new_user
78
+ @new_user ||= User.new
79
+ end
80
+
81
+ def existing_user
82
+ @existing_user ||= User.find_by_email(email)
83
+ end
84
+
85
+ def membership
86
+ @membership ||= Membership.new(:user => user,
87
+ :account => account,
88
+ :admin => true)
89
+ end
90
+
91
+ def valid?
92
+ errors.clear
93
+ validate
94
+ errors.empty?
95
+ end
96
+
97
+ private
98
+
99
+ def short_name
100
+ if email.present?
101
+ email.split(/@/).first.parameterize
102
+ elsif user && user.email.present?
103
+ existing_user.email.split(/@/).first.parameterize
104
+ end
105
+ end
106
+
107
+ def populate_additional_account_fields
108
+ account.name = "#{short_name}"
109
+ account.keyword = "#{short_name}#{SecureRandom.hex(3)}"
110
+ end
111
+
112
+ def populate_additional_user_fields
113
+ user.name = short_name
114
+ end
115
+
116
+ def delegate_attributes_for(model_name)
117
+ FIELDS[model_name].each do |target, source|
118
+ send(model_name).send(:"#{target}=", send(source))
119
+ end
120
+ end
121
+
122
+ def delegate_errors
123
+ FIELDS.each do |model_name, fields|
124
+ delegate_errors_for(model_name, fields)
125
+ end
126
+ end
127
+
128
+ def delegate_errors_for(model_name)
129
+ fields = FIELDS[model_name]
130
+ send(model_name).errors.each do |field, message|
131
+ errors.add(fields[field], message) if fields[field]
132
+ end
133
+ end
134
+
135
+ def validate
136
+ account.valid?
137
+ delegate_errors_for(:account)
138
+ if existing_user
139
+ validate_existing_user
140
+ else
141
+ validate_new_user
142
+ end
143
+ end
144
+
145
+ def validate_new_user
146
+ new_user.valid?
147
+ delegate_errors_for(:user)
148
+ end
149
+
150
+ def validate_existing_user
151
+ if @check_password && !existing_user.authenticated?(password)
152
+ errors.add(:password, "is incorrect")
153
+ end
154
+ end
155
+
156
+ def save!
157
+ Account.transaction do
158
+ account.save!
159
+ user.save!
160
+ membership.save!
161
+ end
162
+ end
163
+ end