saucy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,45 @@
1
+ When /^I bootstrap the application for clearance$/ do
2
+ steps %{
3
+ When I remove the file "public/index.html"
4
+ And I successfully run "rails generate cucumber:install"
5
+ And I successfully run "rails generate clearance"
6
+ And I successfully run "rails generate clearance_features"
7
+ And I configure ActionMailer to use "localhost" as a host
8
+ And I add flash messages to the layout
9
+ And I add session links to the layout
10
+ And I configure "clearance/sessions#new" as the root route
11
+ And I disable Capybara Javascript emulation
12
+ }
13
+ end
14
+
15
+ When /^I add flash messages to the layout$/ do
16
+ flashes = %{
17
+ <% flash.each do |key, value| -%>
18
+ <%= value %>
19
+ <% end -%>
20
+ }
21
+
22
+ replace_in_file "app/views/layouts/application.html.erb",
23
+ /(<body>)/,
24
+ "\\1\n#{flashes}"
25
+ end
26
+
27
+ When /^I add session links to the layout$/ do
28
+ links = %{
29
+ <% if signed_in? -%>
30
+ <%= link_to 'Sign out', sign_out_path, :method => :delete %>
31
+ <% else -%>
32
+ <%= link_to 'Sign in', sign_in_path %>
33
+ <% end -%>
34
+ }
35
+
36
+ replace_in_file "app/views/layouts/application.html.erb",
37
+ /(<body>)/,
38
+ "\\1\n#{links}"
39
+ end
40
+
41
+ When /^I configure "([^"]*)" as the root route$/ do |action|
42
+ replace_in_file "config/routes.rb",
43
+ /(routes\.draw do)/,
44
+ "\\1\nroot :to => '#{action}'"
45
+ end
@@ -0,0 +1,70 @@
1
+ When /^I configure ActionMailer to use "([^"]+)" as a host$/ do |host|
2
+ mailer_config = "config.action_mailer.default_url_options = { :host => '#{host}' }"
3
+ replace_in_file "config/application.rb",
4
+ /(class .* < Rails::Application)/,
5
+ "\\1\n#{mailer_config}"
6
+ end
7
+
8
+ When /^I add the "([^"]*)" gem from this project as a dependency$/ do |gem_name|
9
+ append_to_file('Gemfile', %{\ngem "#{gem_name}", :path => "#{PROJECT_ROOT}"})
10
+ end
11
+
12
+ When /^I disable Capybara Javascript emulation$/ do
13
+ replace_in_file "features/support/env.rb",
14
+ %{require 'cucumber/rails/capybara_javascript_emulation'},
15
+ "# Disabled"
16
+ end
17
+
18
+ When /^I give a more detailed new account message$/ do
19
+ account_message = "Please sign up now."
20
+ replace_in_file 'app/views/accounts/new.html.erb',
21
+ %r{(</h2>)},
22
+ "\\1\n#{account_message}"
23
+
24
+ scenario = <<-HERE
25
+ Feature: The new account page should have a desperate message
26
+ Scenario: New account message
27
+ Given a plan exists with a name of "Free"
28
+ When I go to the sign up page for the "Free" plan
29
+ Then I should see "Please sign up now"
30
+ HERE
31
+ create_file('features/new_account_message.feature', scenario)
32
+ end
33
+
34
+ When /^I add a custom layout to the accounts index$/ do
35
+ in_current_dir do
36
+ FileUtils.cp("app/views/layouts/application.html.erb",
37
+ "app/views/layouts/custom.html.erb")
38
+ end
39
+ replace_in_file 'app/views/layouts/custom.html.erb',
40
+ %r{(<body>)},
41
+ "\\1\nCustom Layout Content"
42
+ layout_config = "config.saucy.layouts.accounts.index = 'custom'"
43
+ replace_in_file "config/application.rb",
44
+ /(class .* < Rails::Application)/,
45
+ "\\1\n#{layout_config}"
46
+
47
+ create_file('features/custom_accounts_index_layout.feature', <<-SCENARIO)
48
+ Feature: The accounts index should have a custom layout
49
+ Scenario: Custom layout
50
+ Given I am signed up and confirmed as "email@person.com/password"
51
+ And the following projects exist:
52
+ | name | account |
53
+ | ClothesPin | name: One |
54
+ | Talkr | name: Two |
55
+ | Fabio | name: One |
56
+ And "email@person.com" is a member of the "ClothesPin" project
57
+ And "email@person.com" is a member of the "Talkr" project
58
+ When I go to the sign in page
59
+ And I sign in as "email@person.com/password"
60
+ And I go to the dashboard page
61
+ Then I should see "Custom Layout Content"
62
+ SCENARIO
63
+ end
64
+
65
+ When /^I copy the specs for this project$/ do
66
+ in_current_dir do
67
+ FileUtils.cp_r(File.join(PROJECT_ROOT, 'spec'), '.')
68
+ end
69
+ end
70
+
@@ -0,0 +1,4 @@
1
+ require 'aruba'
2
+
3
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
4
+
@@ -0,0 +1,11 @@
1
+ module FileHelpers
2
+ def replace_in_file(path, find, replace)
3
+ in_current_dir do
4
+ contents = IO.read(path)
5
+ contents.sub!(find, replace)
6
+ File.open(path, "w") { |file| file.write(contents) }
7
+ end
8
+ end
9
+ end
10
+
11
+ World(FileHelpers)
@@ -0,0 +1,18 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/base'
3
+
4
+ module Saucy
5
+ module Generators
6
+ # Base generator for Saucy generators. Setups up the source root.
7
+ class Base < ::Rails::Generators::Base
8
+ # @return [String] source root for tempates within a saucy generator
9
+ def self.source_root
10
+ @_saucy_source_root ||=
11
+ File.expand_path(File.join(File.dirname(__FILE__),
12
+ generator_name,
13
+ 'templates'))
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,68 @@
1
+ require 'generators/saucy/base'
2
+
3
+ module Saucy
4
+ module Generators
5
+ class FeaturesGenerator < Base
6
+
7
+ desc <<DESC
8
+ Description:
9
+ Copy saucy cucumber features files to your application.
10
+ DESC
11
+
12
+ def copy_feature_files
13
+ directory "features", "features/saucy"
14
+ directory "step_definitions", "features/step_definitions/saucy"
15
+ template "README", "features/saucy/README"
16
+ template "README", "features/step_definitions/saucy/README"
17
+ empty_directory "spec"
18
+ empty_directory "spec/factories"
19
+ template "factories.rb", "spec/factories/saucy_factories.rb"
20
+ end
21
+
22
+ def remove_conflicting_files
23
+ remove_file "features/sign_up.feature"
24
+ remove_file "spec/factories/clearance.rb"
25
+ remove_file "test/factories/clearance.rb"
26
+ end
27
+
28
+ def create_paths
29
+ paths = <<-PATHS
30
+ when /^the memberships page for the "([^"]+)" account$/
31
+ account = Account.find_by_name!($1)
32
+ account_memberships_path(account)
33
+ when /^the projects page for the "([^"]+)" account$/
34
+ account = Account.find_by_name!($1)
35
+ account_projects_path(account)
36
+ when /settings page for the "([^"]+)" account$/i
37
+ account = Account.find_by_name!($1)
38
+ edit_account_path(account)
39
+ when /settings page$/
40
+ edit_profile_path
41
+ when /dashboard page$/
42
+ accounts_path
43
+ when /sign up page for the "([^"]+)" plan$/i
44
+ plan = Plan.find_by_name!($1)
45
+ new_plan_account_path(plan)
46
+ PATHS
47
+
48
+ replace_in_file "features/support/paths.rb",
49
+ "case page_name",
50
+ "case page_name\n#{paths}"
51
+ end
52
+
53
+ private
54
+
55
+ def replace_in_file(relative_path, find, replace)
56
+ path = File.join(destination_root, relative_path)
57
+ contents = IO.read(path)
58
+ unless contents.gsub!(find, replace)
59
+ raise "#{find.inspect} not found in #{relative_path}"
60
+ end
61
+ File.open(path, "w") { |file| file.write(contents) }
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+
68
+
@@ -0,0 +1,55 @@
1
+ Factory.sequence :email do |n|
2
+ "user#{n}@example.com"
3
+ end
4
+
5
+ Factory.sequence :name do |n|
6
+ "name#{n}"
7
+ end
8
+
9
+ Factory.define :user do |user|
10
+ user.name { "test user" }
11
+ user.email { Factory.next :email }
12
+ user.password { "password" }
13
+ user.password_confirmation { "password" }
14
+ end
15
+
16
+ Factory.define :email_confirmed_user, :parent => :user do |user|
17
+ user.email_confirmed { true }
18
+ end
19
+
20
+ Factory.define :account do |f|
21
+ f.name { Factory.next(:name) }
22
+ f.url { Factory.next(:name) }
23
+ end
24
+
25
+ Factory.define :account_membership do |f|
26
+ f.association :user
27
+ f.association :account
28
+ end
29
+
30
+ Factory.define :signup do |f|
31
+ f.account_name { Factory.next(:name) }
32
+ f.url { Factory.next(:name) }
33
+ f.user_name { "test user" }
34
+ f.email { Factory.next :email }
35
+ f.password { "password" }
36
+ f.password_confirmation { "password" }
37
+ end
38
+
39
+ Factory.define :project do |f|
40
+ f.association :account
41
+ end
42
+
43
+ Factory.define :project_membership do |f|
44
+ f.association :user
45
+ f.project {|a| a.association(:project, :account => a.user.account)}
46
+ end
47
+
48
+ Factory.define :invitation do |f|
49
+ f.email { Factory.next(:email) }
50
+ f.association :account
51
+ end
52
+
53
+ Factory.define :plan do |f|
54
+ f.name 'Free'
55
+ end
@@ -0,0 +1,13 @@
1
+ When /^I follow the link sent to "([^"]+)"$/ do |email_address|
2
+ email = ActionMailer::Base.deliveries.detect do |tmail|
3
+ tmail.to.include?(email_address)
4
+ end or raise "No email sent to #{email_address}"
5
+
6
+ unless match = email.body.match(%r{http://\S+})
7
+ raise "No link included in the email:\n#{email.body}"
8
+ end
9
+ url = match[0]
10
+
11
+ visit url
12
+ end
13
+
@@ -0,0 +1 @@
1
+ require 'factory_girl/step_definitions'
@@ -0,0 +1,3 @@
1
+ Then /^the form should have inline error messages$/ do
2
+ page.should have_css(".error")
3
+ end
@@ -0,0 +1,4 @@
1
+ Given /^a project named "([^"]*)" exists under the "([^"]*)" account$/ do |project_name, account_name|
2
+ account = Account.find_by_name!(account_name)
3
+ Factory(:project, :account => account, :name => project_name)
4
+ end
@@ -0,0 +1,27 @@
1
+ Given /^I (?:am signed in|sign in) as an admin of the "([^"]+)" project$/ do |project_name|
2
+ project = Project.find_by_name!(project_name)
3
+ user = Factory(:email_confirmed_user)
4
+ Factory(:account_membership, :user => user, :account => project.account, :admin => true)
5
+ Factory(:project_membership, :user => user, :project => project)
6
+ When %{I sign in as "#{user.email}"}
7
+ end
8
+
9
+ Given /^I am signed in as an admin of the "([^"]*)" account$/ do |account_name|
10
+ account = Account.find_by_name!(account_name)
11
+ user = Factory(:email_confirmed_user)
12
+ Factory(:account_membership, :user => user,
13
+ :account => account,
14
+ :admin => true)
15
+ When %{I sign in as "#{user.email}"}
16
+ end
17
+
18
+ When /^I sign in as "([^"\/]*)"$/ do |email|
19
+ user = User.find_by_email!(email)
20
+ user.update_attributes!(:password => 'test', :password_confirmation => 'test')
21
+ When %{I sign in as "#{email}/test"}
22
+ end
23
+
24
+ Given /^I am signed in$/ do
25
+ user = Factory(:email_confirmed_user)
26
+ When %{I sign in as "#{user.email}"}
27
+ end
@@ -0,0 +1,54 @@
1
+ Given /^"([^"]*)" is a member of the "([^"]*)" project$/ do |email, project_name|
2
+ user = User.find_by_email!(email)
3
+ project = Project.find_by_name!(project_name)
4
+ Factory(:account_membership, :user => user, :account => project.account)
5
+ Factory(:project_membership, :user => user, :project => project)
6
+ end
7
+
8
+ Given /^"([^"]*)" is a member of the "([^"]*)" account/ do |email, account_name|
9
+ user = User.find_by_email!(email)
10
+ account = Account.find_by_name!(account_name)
11
+ Factory(:account_membership, :user => user, :account => account)
12
+ end
13
+
14
+ Given /^"([^"]*)" is an admin of the "([^"]*)" account/ do |email, account_name|
15
+ user = User.find_by_email!(email)
16
+ account = Account.find_by_name!(account_name)
17
+ Factory(:account_membership, :user => user, :account => account, :admin => true)
18
+ end
19
+
20
+ Then /^the user "([^"]*)" should be an admin of "([^"]*)"$/ do |email, account_name|
21
+ user = User.find_by_email!(email)
22
+ account = Account.find_by_name!(account_name)
23
+ user.should be_admin_of(account)
24
+ end
25
+
26
+ Given /^the user "([^"]*)" exists under the "([^"]*)" account$/ do |email, account_name|
27
+ Given %{a user exists with an email of "#{email}"}
28
+ Given %{"#{email}" is a member of the "#{account_name}" account}
29
+ end
30
+
31
+ When /^I fill in the following new user:$/ do |table|
32
+ within "fieldset.new_user" do
33
+ table.transpose.hashes.first.each do |field, value|
34
+ fill_in field, :with => value
35
+ end
36
+ end
37
+ end
38
+
39
+ When /^I fill in the following existing user:$/ do |table|
40
+ within "fieldset.existing_user" do
41
+ table.transpose.hashes.first.each do |field, value|
42
+ fill_in field, :with => value
43
+ end
44
+ end
45
+ end
46
+
47
+ Then /^"([^"]*)" should be a member of the "([^"]*)" account$/ do |email, account_name|
48
+ User.find_by_email!(email).should be_member_of(Account.find_by_name!(account_name))
49
+ end
50
+
51
+ Then /^"([^"]*)" should be an admin member of the "([^"]*)" account$/ do |email, account_name|
52
+ User.find_by_email!(email).should be_admin_of(Account.find_by_name!(account_name))
53
+ end
54
+
@@ -0,0 +1,36 @@
1
+ require 'generators/saucy/base'
2
+ require 'rails/generators/active_record/migration'
3
+
4
+ module Saucy
5
+ module Generators
6
+ class InstallGenerator < Base
7
+ include Rails::Generators::Migration
8
+ extend ActiveRecord::Generators::Migration
9
+
10
+ desc <<DESC
11
+ Description:
12
+ Copy saucy files to your application.
13
+ DESC
14
+
15
+ def generate_migration
16
+ migration_template "create_saucy_tables.rb", "db/migrate/create_saucy_tables.rb"
17
+ end
18
+
19
+ def create_models
20
+ directory "models", "app/models"
21
+ end
22
+
23
+ def update_user_model
24
+ insert_into_file "app/models/user.rb",
25
+ "\ninclude Saucy::User",
26
+ :after => "include Clearance::User"
27
+ end
28
+
29
+ def add_account_authorization
30
+ insert_into_file "app/controllers/application_controller.rb",
31
+ "\ninclude Saucy::AccountAuthorization",
32
+ :after => "include Clearance::Authentication"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,72 @@
1
+ class CreateSaucyTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :account_memberships do |table|
4
+ table.integer :account_id
5
+ table.integer :user_id
6
+ table.boolean :admin
7
+ table.datetime :created_at
8
+ table.datetime :updated_at
9
+ end
10
+
11
+ add_index :account_memberships, [:account_id, :user_id], :unique => true
12
+
13
+ create_table :accounts do |table|
14
+ table.belongs_to :plan
15
+ table.string :name
16
+ table.string :url
17
+ table.datetime :created_at
18
+ table.datetime :updated_at
19
+ end
20
+ add_index :accounts, :plan_id
21
+
22
+ create_table :invitations do |table|
23
+ table.string :email
24
+ table.integer :account_id
25
+ table.boolean :admin
26
+ table.datetime :created_at
27
+ table.datetime :updated_at
28
+ end
29
+
30
+ add_index :invitations, [:account_id]
31
+
32
+ create_table :project_memberships do |table|
33
+ table.integer :user_id
34
+ table.integer :project_id
35
+ table.datetime :created_at
36
+ table.datetime :updated_at
37
+ end
38
+
39
+ add_index :project_memberships, [:user_id, :project_id]
40
+
41
+ create_table :projects do |table|
42
+ table.string :name
43
+ table.integer :account_id
44
+ table.datetime :created_at
45
+ table.datetime :updated_at
46
+ end
47
+
48
+ alter_table :users do |table|
49
+ table.string :name, :default => ""
50
+ end
51
+
52
+ create_table :plans do |t|
53
+ t.string :name
54
+ t.integer :price
55
+
56
+ t.timestamps
57
+ end
58
+
59
+ add_index :plans, :name
60
+ end
61
+
62
+ def self.down
63
+ remove_column :users, :name
64
+ drop_table :plans
65
+ drop_table :projects
66
+ drop_table :project_memberships
67
+ drop_table :invitations
68
+ drop_table :accounts
69
+ drop_table :account_memberships
70
+ end
71
+ end
72
+