bento 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 (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