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,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
+