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.
Files changed (86) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +106 -0
  3. data/README +9 -0
  4. data/app/controllers/accounts_controller.rb +57 -0
  5. data/app/controllers/invitations_controller.rb +36 -0
  6. data/app/controllers/memberships_controller.rb +9 -0
  7. data/app/controllers/permissions_controller.rb +17 -0
  8. data/app/controllers/plans_controller.rb +7 -0
  9. data/app/controllers/profiles_controller.rb +17 -0
  10. data/app/controllers/projects_controller.rb +56 -0
  11. data/app/models/account_membership.rb +8 -0
  12. data/app/models/invitation.rb +86 -0
  13. data/app/models/project_membership.rb +16 -0
  14. data/app/models/signup.rb +134 -0
  15. data/app/views/accounts/_account.html.erb +12 -0
  16. data/app/views/accounts/_blank_slate.html.erb +6 -0
  17. data/app/views/accounts/_projects.html.erb +12 -0
  18. data/app/views/accounts/_tab_bar.html.erb +8 -0
  19. data/app/views/accounts/edit.html.erb +17 -0
  20. data/app/views/accounts/index.html.erb +3 -0
  21. data/app/views/accounts/new.html.erb +26 -0
  22. data/app/views/invitation_mailer/invitation.text.erb +6 -0
  23. data/app/views/invitations/new.html.erb +11 -0
  24. data/app/views/invitations/show.html.erb +20 -0
  25. data/app/views/memberships/index.html.erb +16 -0
  26. data/app/views/permissions/edit.html.erb +15 -0
  27. data/app/views/plans/index.html.erb +3 -0
  28. data/app/views/profiles/_inputs.html.erb +6 -0
  29. data/app/views/profiles/edit.html.erb +35 -0
  30. data/app/views/projects/_form.html.erb +4 -0
  31. data/app/views/projects/edit.html.erb +18 -0
  32. data/app/views/projects/index.html.erb +13 -0
  33. data/app/views/projects/new.html.erb +11 -0
  34. data/config/routes.rb +18 -0
  35. data/features/run_features.feature +73 -0
  36. data/features/step_definitions/clearance_steps.rb +45 -0
  37. data/features/step_definitions/rails_steps.rb +70 -0
  38. data/features/support/env.rb +4 -0
  39. data/features/support/file.rb +11 -0
  40. data/lib/generators/saucy/base.rb +18 -0
  41. data/lib/generators/saucy/features/features_generator.rb +68 -0
  42. data/lib/generators/saucy/features/templates/factories.rb +55 -0
  43. data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +13 -0
  44. data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
  45. data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +3 -0
  46. data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
  47. data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +27 -0
  48. data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +54 -0
  49. data/lib/generators/saucy/install/install_generator.rb +36 -0
  50. data/lib/generators/saucy/install/templates/create_saucy_tables.rb +72 -0
  51. data/lib/generators/saucy/install/templates/models/account.rb +3 -0
  52. data/lib/generators/saucy/install/templates/models/invitation_mailer.rb +9 -0
  53. data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
  54. data/lib/generators/saucy/install/templates/models/project.rb +3 -0
  55. data/lib/generators/saucy/views/views_generator.rb +23 -0
  56. data/lib/saucy.rb +7 -0
  57. data/lib/saucy/account.rb +50 -0
  58. data/lib/saucy/account_authorization.rb +34 -0
  59. data/lib/saucy/configuration.rb +12 -0
  60. data/lib/saucy/engine.rb +9 -0
  61. data/lib/saucy/layouts.rb +36 -0
  62. data/lib/saucy/plan.rb +11 -0
  63. data/lib/saucy/project.rb +39 -0
  64. data/lib/saucy/user.rb +37 -0
  65. data/spec/controllers/accounts_controller_spec.rb +204 -0
  66. data/spec/controllers/application_controller_spec.rb +27 -0
  67. data/spec/controllers/invitations_controller_spec.rb +155 -0
  68. data/spec/controllers/memberships_controller_spec.rb +33 -0
  69. data/spec/controllers/permissions_controller_spec.rb +69 -0
  70. data/spec/controllers/profiles_controller_spec.rb +43 -0
  71. data/spec/controllers/projects_controller_spec.rb +123 -0
  72. data/spec/layouts_spec.rb +21 -0
  73. data/spec/models/account_membership_spec.rb +13 -0
  74. data/spec/models/account_spec.rb +61 -0
  75. data/spec/models/invitation_spec.rb +160 -0
  76. data/spec/models/project_membership_spec.rb +26 -0
  77. data/spec/models/project_spec.rb +80 -0
  78. data/spec/models/signup_spec.rb +175 -0
  79. data/spec/models/user_spec.rb +96 -0
  80. data/spec/saucy_spec.rb +7 -0
  81. data/spec/spec_helper.rb +31 -0
  82. data/spec/support/authentication_helpers.rb +71 -0
  83. data/spec/support/authorization_helpers.rb +56 -0
  84. data/spec/support/clearance_matchers.rb +55 -0
  85. data/spec/views/accounts/_account.html.erb_spec.rb +66 -0
  86. metadata +203 -0
@@ -0,0 +1,134 @@
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
+ :name => :account_name,
11
+ :url => :url,
12
+ },
13
+ :user => {
14
+ :name => :user_name,
15
+ :email => :email,
16
+ :password => :password,
17
+ :password_confirmation => :password_confirmation,
18
+ }
19
+ }.freeze
20
+
21
+ attr_accessor :account_name, :url, :user_name, :email, :password,
22
+ :password_confirmation
23
+
24
+ def initialize(attributes = {})
25
+ attributes.each do |attribute, value|
26
+ send(:"#{attribute}=", value)
27
+ end
28
+ @check_password = true
29
+ end
30
+
31
+ # used by ActiveModel
32
+ def persisted?
33
+ false
34
+ end
35
+
36
+ def save
37
+ delegate_attributes_for(:account)
38
+ delegate_attributes_for(:user) unless existing_user
39
+
40
+ if valid?
41
+ save!
42
+ true
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def account
49
+ @account ||= Account.new
50
+ end
51
+
52
+ def user
53
+ existing_user || new_user
54
+ end
55
+
56
+ def user=(signed_in_user)
57
+ @check_password = false
58
+ @existing_user = signed_in_user
59
+ end
60
+
61
+ def new_user
62
+ @new_user ||= User.new
63
+ end
64
+
65
+ def existing_user
66
+ @existing_user ||= User.find_by_email(email)
67
+ end
68
+
69
+ def account_membership
70
+ @account_membership ||= AccountMembership.new(:user => user,
71
+ :account => account,
72
+ :admin => true)
73
+ end
74
+
75
+ def valid?
76
+ errors.clear
77
+ validate
78
+ errors.empty?
79
+ end
80
+
81
+ def email_confirmed?
82
+ user.email_confirmed?
83
+ end
84
+
85
+ private
86
+
87
+ def delegate_attributes_for(model_name)
88
+ FIELDS[model_name].each do |target, source|
89
+ send(model_name).send(:"#{target}=", send(source))
90
+ end
91
+ end
92
+
93
+ def delegate_errors
94
+ FIELDS.each do |model_name, fields|
95
+ delegate_errors_for(model_name, fields)
96
+ end
97
+ end
98
+
99
+ def delegate_errors_for(model_name)
100
+ fields = FIELDS[model_name]
101
+ send(model_name).errors.each do |field, message|
102
+ errors.add(fields[field], message)
103
+ end
104
+ end
105
+
106
+ def validate
107
+ account.valid?
108
+ delegate_errors_for(:account)
109
+ if existing_user
110
+ validate_existing_user
111
+ else
112
+ validate_new_user
113
+ end
114
+ end
115
+
116
+ def validate_new_user
117
+ new_user.valid?
118
+ delegate_errors_for(:user)
119
+ end
120
+
121
+ def validate_existing_user
122
+ if @check_password && !existing_user.authenticated?(password)
123
+ errors.add(:password, "is incorrect")
124
+ end
125
+ end
126
+
127
+ def save!
128
+ Account.transaction do
129
+ account.save!
130
+ user.save!
131
+ account_membership.save!
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,12 @@
1
+ <%= content_tag_for :div, account do -%>
2
+ <div class="account-header">
3
+ <h2>Account: <strong><%= account.name %></strong></h2>
4
+ <% if current_user.admin_of?(account) -%>
5
+ <%= link_to 'Settings', edit_account_path(account), :class => 'project-settings' %>
6
+ <% end -%>
7
+ </div>
8
+
9
+ <%= render 'accounts/projects',
10
+ :projects => account.projects_visible_to(current_user),
11
+ :account => account%>
12
+ <% end -%>
@@ -0,0 +1,6 @@
1
+ <div id="blank_slate_accounts">
2
+ <h6>You don't have any projects yet</h6>
3
+ <%= link_to 'Create a project',
4
+ new_account_project_path(account),
5
+ :class => 'blank_slate_action' %>
6
+ </div>
@@ -0,0 +1,12 @@
1
+ <% if projects.empty? -%>
2
+ <%= render 'accounts/blank_slate', :account => account %>
3
+ <% else -%>
4
+ <ul class="projects">
5
+ <% projects.each do |project| -%>
6
+ <%= content_tag_for :li, project do -%>
7
+ <%= link_to h(project.name), project_path(project) %>
8
+ <% end -%>
9
+ <% end -%>
10
+ </ul>
11
+ <% end -%>
12
+
@@ -0,0 +1,8 @@
1
+ <div class="tabs">
2
+ <ul>
3
+ <li><%= link_to "Account Info", edit_account_url(current_account) %></li>
4
+ <li><%= link_to "Projects", account_projects_url(current_account) %></li>
5
+ <li><%= link_to "Users", account_memberships_url(current_account) %></li>
6
+ <li><a href="#">Billing</a></li>
7
+ </ul>
8
+ </div>
@@ -0,0 +1,17 @@
1
+ <h2>Settings for <span><%= current_account.name %></span></h2>
2
+
3
+ <%= render :partial => 'tab_bar' %>
4
+
5
+ <%= content_tag_for :div, @account do -%>
6
+ <h3><%= @account.name %></h3>
7
+ <%= semantic_form_for @account do |form| %>
8
+ <%= form.inputs do %>
9
+ <%= form.input :name, :label => "Account name" %>
10
+ <%= form.input :url %>
11
+ <% end %>
12
+
13
+ <%= form.buttons do %>
14
+ <%= form.commit_button "Update" %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end -%>
@@ -0,0 +1,3 @@
1
+ <h2>Accounts</h2>
2
+ <%= render :partial => 'account', :collection => @accounts %>
3
+
@@ -0,0 +1,26 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <%= semantic_form_for @signup, :url => plan_accounts_path(@plan) do |form| %>
4
+ <%= form.error_messages %>
5
+
6
+ <h5 class="legend">Basic Information</h5>
7
+ <%= form.inputs do %>
8
+ <%= form.input :account_name, :label => 'Company Name' %>
9
+ <%= form.input :url, :label => 'Account URL', :hint => "http://copycopter.com/accounturl" %>
10
+ <% end %>
11
+
12
+ <% unless signed_in? -%>
13
+ <h5 class="legend">User Information</h5>
14
+ <%= form.inputs do %>
15
+ <%= form.input :user_name, :label => "Your name" %>
16
+ <%= form.input :email %>
17
+ <%= form.input :password %>
18
+ <%= form.input :password_confirmation, :label => "Confirm password", :required => true %>
19
+ <% end -%>
20
+ <% end -%>
21
+
22
+ <%= form.buttons do %>
23
+ <%= form.commit_button "Sign up" %>
24
+ <% end %>
25
+ <% end %>
26
+
@@ -0,0 +1,6 @@
1
+ You have been invited to join the <%= @invitation.account_name %> account.
2
+
3
+ Go to the following URL to accept this invitation:
4
+
5
+ <%= invitation_url(@invitation) %>
6
+
@@ -0,0 +1,11 @@
1
+ <%= semantic_form_for [current_account, @invitation] do |form| -%>
2
+ <%= form.inputs do -%>
3
+ <%= form.input :email %>
4
+ <%= form.input :admin, :label => "Grant administrator privileges" %>
5
+ <% end -%>
6
+
7
+ <%= form.buttons do -%>
8
+ <%= form.commit_button "Invite User" %>
9
+ <% end -%>
10
+ <% end -%>
11
+
@@ -0,0 +1,20 @@
1
+ <h2>Accept invitation to <%= @invitation.account_name %></h2>
2
+
3
+ <%= semantic_form_for @invitation do |form| %>
4
+ <%= form.inputs 'I am a new user', :class => 'new_user' do -%>
5
+ <%= form.input :new_user_email, :label => 'Email' %>
6
+ <%= form.input :new_user_name, :label => 'Name' %>
7
+ <%= form.input :new_user_password, :label => 'Password' %>
8
+ <%= form.input :new_user_password_confirmation, :label => 'Confirm password' %>
9
+ <% end -%>
10
+
11
+ <%= form.inputs 'I am an existing user', :class => 'existing_user' do -%>
12
+ <%= form.input :existing_user_email, :label => 'Email' %>
13
+ <%= form.input :existing_user_password, :label => 'Password' %>
14
+ <% end -%>
15
+
16
+ <%= form.buttons do %>
17
+ <%= form.commit_button "Accept Invitation" %>
18
+ <% end %>
19
+ <% end %>
20
+
@@ -0,0 +1,16 @@
1
+ <h2>Users for <span><%= current_account.name %></span></h2>
2
+
3
+ <%= render :partial => 'accounts/tab_bar' %>
4
+
5
+ <ul class="users">
6
+ <% @users.each do |user| -%>
7
+ <%= content_tag_for :li, user do -%>
8
+ <%= link_to edit_account_user_permissions_path(current_account, user) do %>
9
+ <%= user.name %> (<%= user.email %>)
10
+ <% end -%>
11
+ <% end -%>
12
+ <% end -%>
13
+ </ul>
14
+
15
+ <%= link_to 'Invite user', new_account_invitation_path(current_account), :class => "button"%>
16
+
@@ -0,0 +1,15 @@
1
+ <h2>Permissions for <span><%= @user.name %></span> on <%= current_account.name %></h2>
2
+
3
+ <%= render :partial => 'accounts/tab_bar' %>
4
+
5
+ <%= semantic_form_for @user,
6
+ :as => :permissions,
7
+ :url => account_user_permissions_path(current_account, @user) do |form| -%>
8
+ <%= form.inputs do -%>
9
+ <%= form.input :projects, :as => :check_boxes, :collection => @projects %>
10
+ <% end -%>
11
+ <%= form.buttons do -%>
12
+ <%= form.commit_button "Update" %>
13
+ <% end -%>
14
+ <% end -%>
15
+
@@ -0,0 +1,3 @@
1
+ <% @plans.each do |plan| %>
2
+ <%= link_to plan.name, new_plan_account_path(plan) %>
3
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%= form.inputs do %>
2
+ <%= form.input :email %>
3
+ <%= form.input :password %>
4
+ <%= form.input :password_confirmation, :label => "Confirm password", :required => true %>
5
+ <% end %>
6
+
@@ -0,0 +1,35 @@
1
+ <h2>User Details</h2>
2
+
3
+ <div id="user_accounts">
4
+ <h3>Accounts</h3>
5
+ <ul>
6
+ <% @user.accounts.each do |account| -%>
7
+ <li>
8
+ <%= link_to edit_account_path(account) do -%>
9
+ <h4><%= account.name %></h4>
10
+ <p class="edit-account">edit account</p>
11
+ <% end -%>
12
+ </li>
13
+ <% end -%>
14
+ </ul>
15
+ <%= link_to 'Add new account', plans_path, :class => 'new-account button' %>
16
+ </div>
17
+
18
+
19
+ <div id="login_info"><h3>Login Information</h3>
20
+ <%= semantic_form_for @user, :url => profile_path do |form| %>
21
+ <%= form.inputs do %>
22
+ <%= form.input :name %>
23
+ <%= form.input :email %>
24
+ <% end %>
25
+
26
+ <%= form.inputs do %>
27
+ <%= form.input :password, :label => "New password", :required => true %>
28
+ <%= form.input :password_confirmation, :label => "Confirm password", :required => true %>
29
+ <% end %>
30
+
31
+ <%= form.buttons do %>
32
+ <%= form.commit_button "Update" %>
33
+ <% end %>
34
+ <% end %>
35
+ </div>
@@ -0,0 +1,4 @@
1
+ <%= form.inputs do %>
2
+ <%= form.input :name %>
3
+ <%= form.input :users, :as => :check_boxes, :collection => users %>
4
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <h2>Edit project: <%=h @project.name %></h2>
2
+ <div class="actions">
3
+ <%= link_to project_path(@project), :method => 'delete', :confirm => 'Are you sure you want to delete this project and all associated content?' do %>
4
+ <span class="delete_item">Remove this project</span>
5
+ <% end -%>
6
+ </div>
7
+
8
+ <%= render :partial => 'accounts/tab_bar' %>
9
+
10
+ <%= semantic_form_for @project do |form| %>
11
+ <%= render 'form', :form => form, :users => current_account.users %>
12
+
13
+ <%= form.buttons do %>
14
+ <%= form.commit_button %>
15
+ <%= link_to 'Cancel', account_projects_path(current_account) %>
16
+ <% end %>
17
+ <% end -%>
18
+
@@ -0,0 +1,13 @@
1
+ <h2>Projects for <%= current_account.name %></h2>
2
+
3
+ <%= render :partial => 'accounts/tab_bar' %>
4
+
5
+ <ul class="projects">
6
+ <% @projects.each do |project| -%>
7
+ <%= content_tag_for :li, project do -%>
8
+ <%= link_to h(project.name), edit_project_path(project) %>
9
+ <% end -%>
10
+ <% end -%>
11
+ </ul>
12
+
13
+ <%= link_to 'New Project', new_account_project_path(current_account), :class => "button" %>
@@ -0,0 +1,11 @@
1
+ <h2>Create a new project</h2>
2
+
3
+ <%= render :partial => 'accounts/tab_bar' %>
4
+
5
+ <%= semantic_form_for(@project, :url => account_projects_url(current_account)) do |form| %>
6
+ <%= render 'form', :form => form, :users => current_account.users %>
7
+ <%= form.buttons do -%>
8
+ <%= form.commit_button %>
9
+ <%= link_to 'Cancel', account_projects_path(current_account) %>
10
+ <% end -%>
11
+ <% end -%>
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ Rails.application.routes.draw do
2
+ resources :accounts, :only => [:index, :edit, :update] do
3
+ resources :projects, :only => [:new, :create, :index, :show]
4
+ resources :memberships, :only => [:index]
5
+ resources :invitations, :only => [:new, :create]
6
+ resources :users, :only => [] do
7
+ resource :permissions, :only => [:edit, :update]
8
+ end
9
+ end
10
+
11
+ resources :plans, :only => [:index] do
12
+ resources :accounts, :only => [:new, :create]
13
+ end
14
+
15
+ resources :invitations, :only => [:show, :update]
16
+ resources :projects, :only => [:edit, :update, :destroy]
17
+ resource :profile, :only => [:edit, :update]
18
+ end
@@ -0,0 +1,73 @@
1
+ @puts @announce @disable-bundler
2
+ Feature: generate a saucy application and run rake
3
+
4
+ Background:
5
+ When I successfully run "rails new testapp"
6
+ And I cd to "testapp"
7
+ And I append to "Gemfile" with:
8
+ """
9
+ gem "clearance", "0.9.0.rc9"
10
+ gem "cucumber-rails"
11
+ gem "capybara"
12
+ gem "factory_girl_rails"
13
+ gem "dynamic_form"
14
+ gem "database_cleaner"
15
+ gem "formtastic"
16
+ gem "rspec-rails"
17
+ gem "bourne"
18
+ gem "shoulda"
19
+ """
20
+ When I add the "saucy" gem from this project as a dependency
21
+ And I successfully run "bundle install"
22
+ And I bootstrap the application for clearance
23
+
24
+ Scenario: generate a saucy application and run rake
25
+ When I successfully run "rails generate saucy:install"
26
+ And I successfully run "rails generate saucy:features"
27
+ And I successfully run "rake db:migrate"
28
+ And I run "rake"
29
+ Then it should pass with:
30
+ """
31
+ passed
32
+ """
33
+ Then the output should not contain "failed"
34
+ And the output should not contain "Could not find generator"
35
+
36
+ Scenario: A new saucy app with custom views
37
+ When I successfully run "rails generate saucy:install"
38
+ And I successfully run "rails generate saucy:features"
39
+ And I successfully run "rails generate saucy:views"
40
+ And I successfully run "rake db:migrate"
41
+ And I give a more detailed new account message
42
+ And I run "rake"
43
+ Then it should pass with:
44
+ """
45
+ passed
46
+ """
47
+ Then the output should not contain "failed"
48
+ And the output should not contain "Could not find generator"
49
+
50
+ Scenario: A new saucy app with custom layouts
51
+ When I successfully run "rails generate saucy:install"
52
+ And I successfully run "rails generate saucy:features"
53
+ And I successfully run "rake db:migrate"
54
+ And I add a custom layout to the accounts index
55
+ And I run "rake"
56
+ Then it should pass with:
57
+ """
58
+ passed
59
+ """
60
+ Then the output should not contain "failed"
61
+ And the output should not contain "Could not find generator"
62
+
63
+ Scenario: run specs
64
+ When I successfully run "rails generate saucy:install"
65
+ And I successfully run "rails generate saucy:features"
66
+ And I successfully run "rake db:migrate"
67
+ And I copy the specs for this project
68
+ And I run "rake spec"
69
+ Then it should pass with:
70
+ """
71
+ 0 failures
72
+ """
73
+ Then the output should not contain "0 examples"