bento 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/README.rdoc +62 -0
  2. data/lib/bento.rb +6 -0
  3. data/lib/bento/controllers/account_scopable.rb +22 -0
  4. data/lib/bento/controllers/helpers.rb +17 -0
  5. data/lib/bento/models/account.rb +59 -0
  6. data/lib/bento/rails.rb +4 -0
  7. data/lib/bento/version.rb +3 -0
  8. data/lib/generators/active_record/bento_generator.rb +29 -0
  9. data/lib/generators/active_record/templates/add_migration.rb +9 -0
  10. data/lib/generators/active_record/templates/create_migration.rb +18 -0
  11. data/lib/generators/bento/bento_generator.rb +13 -0
  12. data/lib/generators/bento/orm_helpers.rb +22 -0
  13. data/lib/generators/bento/views_generator.rb +14 -0
  14. data/spec/bento/controllers/helpers_spec.rb +4 -0
  15. data/spec/bento/models/account_spec.rb +125 -0
  16. data/spec/proof_of_concepts_spec.rb +17 -0
  17. data/spec/rails_app/Rakefile +7 -0
  18. data/spec/rails_app/app/controllers/all_projects_controller.rb +6 -0
  19. data/spec/rails_app/app/controllers/application_controller.rb +4 -0
  20. data/spec/rails_app/app/controllers/custom_accounts_controller.rb +9 -0
  21. data/spec/rails_app/app/controllers/home_controller.rb +4 -0
  22. data/spec/rails_app/app/controllers/projects_controller.rb +6 -0
  23. data/spec/rails_app/app/models/account.rb +10 -0
  24. data/spec/rails_app/app/models/project.rb +4 -0
  25. data/spec/rails_app/app/models/user.rb +8 -0
  26. data/spec/rails_app/app/views/custom_accounts/new.html.erb +10 -0
  27. data/spec/rails_app/app/views/home/index.html.erb +1 -0
  28. data/spec/rails_app/app/views/layouts/application.html.erb +29 -0
  29. data/spec/rails_app/app/views/projects/_all_projects.html.erb +9 -0
  30. data/spec/rails_app/app/views/projects/_current_account_projects.html.erb +9 -0
  31. data/spec/rails_app/app/views/projects/_form.html.erb +5 -0
  32. data/spec/rails_app/app/views/projects/edit.html.erb +1 -0
  33. data/spec/rails_app/app/views/projects/index.html.erb +7 -0
  34. data/spec/rails_app/app/views/projects/new.html.erb +1 -0
  35. data/spec/rails_app/config.ru +4 -0
  36. data/spec/rails_app/config/application.rb +43 -0
  37. data/spec/rails_app/config/boot.rb +15 -0
  38. data/spec/rails_app/config/database.yml +22 -0
  39. data/spec/rails_app/config/environment.rb +6 -0
  40. data/spec/rails_app/config/environments/development.rb +28 -0
  41. data/spec/rails_app/config/environments/production.rb +49 -0
  42. data/spec/rails_app/config/environments/test.rb +35 -0
  43. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/rails_app/config/initializers/devise.rb +142 -0
  45. data/spec/rails_app/config/initializers/inaccessible_attributes.rb +13 -0
  46. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  47. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  48. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  49. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  50. data/spec/rails_app/config/locales/devise.en.yml +39 -0
  51. data/spec/rails_app/config/locales/en.yml +5 -0
  52. data/spec/rails_app/config/routes.rb +9 -0
  53. data/spec/rails_app/db/development.sqlite3 +0 -0
  54. data/spec/rails_app/db/migrate/20100924132032_devise_create_users.rb +19 -0
  55. data/spec/rails_app/db/migrate/20101015094514_bento_create_accounts.rb +15 -0
  56. data/spec/rails_app/db/migrate/20101015094515_bento_add_account_id_to_accounts.rb +9 -0
  57. data/spec/rails_app/db/migrate/20101015143011_create_projects.rb +13 -0
  58. data/spec/rails_app/db/production.sqlite3 +0 -0
  59. data/spec/rails_app/db/schema.rb +44 -0
  60. data/spec/rails_app/db/test.sqlite3 +0 -0
  61. data/spec/rails_app/log/development.log +1290 -0
  62. data/spec/rails_app/log/test.log +30285 -0
  63. data/spec/rails_app/public/404.html +26 -0
  64. data/spec/rails_app/public/422.html +26 -0
  65. data/spec/rails_app/public/500.html +26 -0
  66. data/spec/rails_app/public/favicon.ico +0 -0
  67. data/spec/rails_app/script/rails +6 -0
  68. data/spec/rails_app/tmp/pids/server.pid +1 -0
  69. data/spec/spec_helper.rb +21 -0
  70. data/spec/support/bento_spec_helper.rb +16 -0
  71. data/spec/support/blueprints.rb +32 -0
  72. metadata +403 -0
data/README.rdoc ADDED
@@ -0,0 +1,62 @@
1
+ = Bento
2
+ Bento is a Rails account management engine.
3
+
4
+ == Disclaimer
5
+ Don't expect everything to work as described below. The gem is still in early beta.
6
+
7
+ == Requirements
8
+ Currently it has a lot of pre conditions. The goal is to get rid of as many of them as possible but right new Bento expects the following:
9
+ * Your model for managing accounts is named Account (Bento can generate it for you)
10
+ * The account model has a name and a plan column (Bento can generate the migrations too)
11
+ * You have a model named User to keep track of users
12
+ - The users has a first_name, last_name, email and password fields
13
+
14
+ == Installation
15
+ gem install bento
16
+ rails generate bento account
17
+ rake db:migrate
18
+
19
+ == Basic Usage
20
+ Bento sets up the following routes that you can use, complete with views and controllers:
21
+ * [GET] sign_up_accounts - account and user creation in once
22
+ * [GET] new_accounts - for just account creation
23
+ * [GET] accounts - show
24
+ * [GET] account - index
25
+ * [POST] accounts - create
26
+ * [PUT] account - update
27
+ * [DELETE] account - destroy
28
+
29
+ == Views and controllers
30
+ === Access the current account:
31
+ There is a current_account helper in your controllers and views which returns the currently logged in user's account.
32
+
33
+ === Automatic scoping
34
+ If you're using inherited_resources in your controllers you can do the below to scope your resource to the current account.
35
+ class MyController < ApplicationController
36
+ scoped_to_account
37
+ end
38
+
39
+ === Admin managed account
40
+ Bento can give you a complete CRUD for creating accounts. Use
41
+ rails generate bento:views
42
+ If your current_user respond to admin? These actions will only be accessible by users that respond with true.
43
+
44
+ == Configuring controllers
45
+
46
+ You can customize the accounts controller by following these steps:
47
+
48
+ 1) Create your custom accounts controller, AccountsController
49
+ class AccountsController < Bento::AccountsController
50
+ end
51
+
52
+ 2) Tell the router to use this controller:
53
+ resources :accounts
54
+
55
+ 3) And since we changed the controller, it won't use the gem's views so make sure to make some and put in app/views/accounts.
56
+
57
+ == Credits and contributors
58
+
59
+ * The contributors of the awesome Devise gem from which I've borrowed a lot of ideas and patterns.
60
+ * Elabs for sponsoring
61
+ * Jonas Nicklas for helping me boot strap the engine.
62
+ * All the gems that this gem depends on.
data/lib/bento.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Bento
2
+ require "bento/rails"
3
+ require "bento/models/account"
4
+ require "bento/controllers/helpers"
5
+ require "bento/controllers/account_scopable"
6
+ end
@@ -0,0 +1,22 @@
1
+ module Bento
2
+ module Controllers
3
+ module AccountScopable
4
+ def scoped_to_account(*args, &block)
5
+ inherit_resources(*args)
6
+ include AccountScopable::InstanceMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+ def begin_of_association_chain
11
+ current_user.account
12
+ end
13
+
14
+ private :begin_of_association_chain
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class ActionController::Base
21
+ extend Bento::Controllers::AccountScopable
22
+ end
@@ -0,0 +1,17 @@
1
+ module Bento
2
+ module Controllers
3
+ module Helpers
4
+ def self.included(base)
5
+ base.helper_method :current_account
6
+ end
7
+
8
+ def current_account
9
+ current_user.account if current_user
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ class ActionController::Base
16
+ include Bento::Controllers::Helpers
17
+ end
@@ -0,0 +1,59 @@
1
+ module Bento
2
+ module Models
3
+ module Account
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ USER_ACCESSORS = [:first_name, :last_name, :email, :password, :password_confirmation]
10
+
11
+ def bento_account(*options)
12
+ if extend_with_validations?(options)
13
+ validates_presence_of :name
14
+ end
15
+
16
+ if extend_with_user_accessors?(options)
17
+ attr_accessor *USER_ACCESSORS
18
+ end
19
+
20
+ if extend_with_user_association?(options)
21
+ has_many :users
22
+ end
23
+
24
+ if extend_with_trial?(options)
25
+ define_method("trial_days_remaining") do
26
+ created_at.advance(:days => 30).to_date - Date.today
27
+ end
28
+ end
29
+
30
+ if extend_with_user_association?(options) and extend_with_user_accessors?(options)
31
+ before_validation :build_user, :on => :create
32
+
33
+ define_method("build_user") do
34
+ user_attributes = USER_ACCESSORS.inject({}) { |h, key| h.merge(key => send(key)) }
35
+ if user_attributes.values.any?
36
+ @user ||= users.build(user_attributes)
37
+ @user.tap(&:valid?).errors.each { |attribute, message| errors.add(attribute, message) }
38
+ @user
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def method_missing(*args)
47
+ option = args.shift.to_s.sub(/^extend_with_/, '').chop.to_sym
48
+ available = args.flatten
49
+
50
+ (available.is_a?(Array) and available.empty?) or available.include?(:all) or available.include?(option)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ class ActiveRecord::Base
58
+ include Bento::Models::Account
59
+ end
@@ -0,0 +1,4 @@
1
+ module Bento
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Bento
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails/generators/active_record'
2
+ require 'generators/bento/orm_helpers'
3
+
4
+ module ActiveRecord
5
+ module Generators
6
+ class BentoGenerator < ActiveRecord::Generators::Base
7
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
8
+
9
+ include Bento::Generators::OrmHelpers
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ def generate_model
13
+ invoke "active_record:model", [name], :migration => false unless model_exists?
14
+ end
15
+
16
+ def copy_bento_migration
17
+ migration_template "create_migration.rb", "db/migrate/bento_create_#{table_name}"
18
+ migration_template "add_migration.rb", "db/migrate/bento_add_account_id_to_#{table_name}"
19
+ end
20
+
21
+ def inject_bento_content
22
+ inject_into_class model_path, class_name, model_contents + <<-CONTENT
23
+ # Setup accessible (or protected) attributes for your model
24
+ attr_accessible :name, :plan, :first_name, :last_name, :email, :password_confirmation, :password
25
+ CONTENT
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ class BentoAddAccountIdToAccounts < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :users, :account_id, :integer
4
+ end
5
+
6
+ def self.down
7
+ remove_column :users, :column_name
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ class BentoCreateAccounts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :accounts do |t|
4
+ t.string :name
5
+ t.string :plan
6
+ <% for attribute in attributes -%>
7
+ t.<%= attribute.type %> :<%= attribute.name %>
8
+ <% end -%>
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :accounts, :name, :unique => true
13
+ end
14
+
15
+ def self.down
16
+ drop_table :accounts
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Bento
2
+ module Generators
3
+ class BentoGenerator < Rails::Generators::NamedBase
4
+ namespace "bento"
5
+ source_root File.expand_path("../templates", __FILE__)
6
+
7
+ desc "Generates a model with the given NAME (if one does not exist) with bento " <<
8
+ "configuration plus a migration file."
9
+
10
+ hook_for :orm
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Bento
2
+ module Generators
3
+ module OrmHelpers
4
+ def model_contents
5
+ <<-CONTENT
6
+ # Include all bento modules. Others available are:
7
+ # :all, :validations, :user_accessors, :user_association, :user_accessors, :trial
8
+ bento_account
9
+
10
+ CONTENT
11
+ end
12
+
13
+ def model_exists?
14
+ File.exists?(File.join(destination_root, model_path))
15
+ end
16
+
17
+ def model_path
18
+ @model_path ||= File.join("app", "models", "#{file_path}.rb")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module Bento
2
+ module Generators
3
+ class ViewsGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../../../app/views", __FILE__)
5
+ desc "Copies all Bento views to your application."
6
+
7
+ argument :scope, :required => false, :default => nil, :desc => "The scope to copy views to"
8
+
9
+ def copy_views
10
+ directory "bento", "app/views/#{scope}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bento::Controllers::Helpers do
4
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ class Bento::AccountTest < ActiveRecord::Base
4
+ set_table_name("accounts")
5
+ attr_accessible :name, :plan, :first_name, :last_name, :email, :password_confirmation, :password, :created_at
6
+ end
7
+ class NoOptionsTestAccount < Bento::AccountTest; bento_account; end
8
+ class AllTestAccount < Bento::AccountTest; bento_account(:all); end
9
+ class ValidationsTestAccount < Bento::AccountTest; bento_account(:validations); end
10
+ class UserAccessorsTestAccount < Bento::AccountTest; bento_account(:user_accessors); end
11
+ class UserAssociationTestAccount < Bento::AccountTest; bento_account(:user_association); end
12
+ class AllUserTestAccount < Bento::AccountTest; bento_account(:user_accessors, :user_association); end
13
+ class ServeralOptionsAccessorsTestAccount < Bento::AccountTest; bento_account(:user_accessors, :validations); end
14
+ class TrialTestAccount < Bento::AccountTest; bento_account(:trial); end
15
+
16
+ describe Bento::Models::Account do
17
+ let(:account_params) do
18
+ { :name => "Hashrocket", :first_name => "Obie", :last_name => "Fernandez", :email => "obie@hashrocket.com", :password => "test1234" }
19
+ end
20
+
21
+ context "all" do
22
+ subject { AllTestAccount.new }
23
+ it { should be_invalid_without(:name) }
24
+ it { should have_user_accessors }
25
+ it { should respond_to(:users) }
26
+ it { should respond_to(:trial_days_remaining) }
27
+ it { should respond_to(:build_user) }
28
+
29
+ describe ".build_user" do
30
+ context "all user attributes are blank" do
31
+ it "creates the account without the user" do
32
+ account = AllTestAccount.new(:name => "Elabs")
33
+ account.save.should be_true
34
+ User.find_by_account_id(account.id).should be_nil
35
+ end
36
+ end
37
+
38
+ context "at least one user attributes is present" do
39
+ context "successfully creating a user" do
40
+ subject do
41
+ account = AllTestAccount.create!(account_params)
42
+ account.users.first
43
+ end
44
+
45
+ its(:email) { should == "obie@hashrocket.com" }
46
+ its(:first_name) { should == "Obie" }
47
+ its(:last_name) { should == "Fernandez" }
48
+ end
49
+
50
+ context "unsuccessfully creating a user" do
51
+ subject { AllTestAccount.new(account_params.merge(:email => "")).tap(&:valid?) }
52
+ it { subject.errors.should include(:email) }
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#trial_days_remaining" do
58
+ subject { TrialTestAccount.create!(:name => "Account", :created_at => 6.days.ago).trial_days_remaining }
59
+ it("returns the number of days that are left in the trial") { should == 24 }
60
+ end
61
+ end
62
+
63
+ context "no options" do
64
+ subject { NoOptionsTestAccount.new }
65
+ it { should be_invalid_without(:name) }
66
+ it { should have_user_accessors }
67
+ it { should respond_to(:users) }
68
+ it { should respond_to(:trial_days_remaining) }
69
+ it { should respond_to(:build_user) }
70
+ end
71
+
72
+ context "validations" do
73
+ subject { ValidationsTestAccount.new }
74
+ it { should be_invalid_without(:name) }
75
+ it { should_not have_user_accessors }
76
+ it { should_not respond_to(:users) }
77
+ it { should_not respond_to(:trial_days_remaining) }
78
+ it { should_not respond_to(:build_user) }
79
+ end
80
+
81
+ context "user accessors" do
82
+ subject { UserAccessorsTestAccount.new }
83
+ it { should_not be_invalid_without(:name) }
84
+ it { should have_user_accessors }
85
+ it { should_not respond_to(:users) }
86
+ it { should_not respond_to(:trial_days_remaining) }
87
+ it { should_not respond_to(:build_user) }
88
+ end
89
+
90
+ context "several options" do
91
+ subject { ServeralOptionsAccessorsTestAccount.new }
92
+ it { should be_invalid_without(:name) }
93
+ it { should have_user_accessors }
94
+ it { should_not respond_to(:users) }
95
+ it { should_not respond_to(:trial_days_remaining) }
96
+ it { should_not respond_to(:build_user) }
97
+ end
98
+
99
+ context "user association" do
100
+ subject { UserAssociationTestAccount.new }
101
+ it { should_not be_invalid_without(:name) }
102
+ it { should_not have_user_accessors }
103
+ it { should respond_to(:users) }
104
+ it { should_not respond_to(:trial_days_remaining) }
105
+ it { should_not respond_to(:build_user) }
106
+ end
107
+
108
+ context "trial" do
109
+ subject { TrialTestAccount.new }
110
+ it { should_not be_invalid_without(:name) }
111
+ it { should_not have_user_accessors }
112
+ it { should_not respond_to(:users) }
113
+ it { should respond_to(:trial_days_remaining) }
114
+ it { should_not respond_to(:build_user) }
115
+ end
116
+
117
+ context "user accessors and user association" do
118
+ subject { AllUserTestAccount.new }
119
+ it { should_not be_invalid_without(:name) }
120
+ it { should have_user_accessors }
121
+ it { should respond_to(:users) }
122
+ it { should_not respond_to(:trial_days_remaining) }
123
+ it { should respond_to(:build_user) }
124
+ end
125
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsApp do
4
+ it "reach it's own files" do
5
+ visit "/"
6
+ page.body.should include("Welcome to the app that's using Bento")
7
+ end
8
+
9
+ it "loads app" do
10
+ Bento::AccountsController.should == Bento::AccountsController
11
+ end
12
+
13
+ it "mounts the new accounts route" do
14
+ visit "/accounts/new"
15
+ page.status_code.should == 200
16
+ end
17
+ end