bento 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -0
- data/lib/bento.rb +6 -0
- data/lib/bento/controllers/account_scopable.rb +22 -0
- data/lib/bento/controllers/helpers.rb +17 -0
- data/lib/bento/models/account.rb +59 -0
- data/lib/bento/rails.rb +4 -0
- data/lib/bento/version.rb +3 -0
- data/lib/generators/active_record/bento_generator.rb +29 -0
- data/lib/generators/active_record/templates/add_migration.rb +9 -0
- data/lib/generators/active_record/templates/create_migration.rb +18 -0
- data/lib/generators/bento/bento_generator.rb +13 -0
- data/lib/generators/bento/orm_helpers.rb +22 -0
- data/lib/generators/bento/views_generator.rb +14 -0
- data/spec/bento/controllers/helpers_spec.rb +4 -0
- data/spec/bento/models/account_spec.rb +125 -0
- data/spec/proof_of_concepts_spec.rb +17 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/controllers/all_projects_controller.rb +6 -0
- data/spec/rails_app/app/controllers/application_controller.rb +4 -0
- data/spec/rails_app/app/controllers/custom_accounts_controller.rb +9 -0
- data/spec/rails_app/app/controllers/home_controller.rb +4 -0
- data/spec/rails_app/app/controllers/projects_controller.rb +6 -0
- data/spec/rails_app/app/models/account.rb +10 -0
- data/spec/rails_app/app/models/project.rb +4 -0
- data/spec/rails_app/app/models/user.rb +8 -0
- data/spec/rails_app/app/views/custom_accounts/new.html.erb +10 -0
- data/spec/rails_app/app/views/home/index.html.erb +1 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +29 -0
- data/spec/rails_app/app/views/projects/_all_projects.html.erb +9 -0
- data/spec/rails_app/app/views/projects/_current_account_projects.html.erb +9 -0
- data/spec/rails_app/app/views/projects/_form.html.erb +5 -0
- data/spec/rails_app/app/views/projects/edit.html.erb +1 -0
- data/spec/rails_app/app/views/projects/index.html.erb +7 -0
- data/spec/rails_app/app/views/projects/new.html.erb +1 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +43 -0
- data/spec/rails_app/config/boot.rb +15 -0
- data/spec/rails_app/config/database.yml +22 -0
- data/spec/rails_app/config/environment.rb +6 -0
- data/spec/rails_app/config/environments/development.rb +28 -0
- data/spec/rails_app/config/environments/production.rb +49 -0
- data/spec/rails_app/config/environments/test.rb +35 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/devise.rb +142 -0
- data/spec/rails_app/config/initializers/inaccessible_attributes.rb +13 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/locales/devise.en.yml +39 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +9 -0
- data/spec/rails_app/db/development.sqlite3 +0 -0
- data/spec/rails_app/db/migrate/20100924132032_devise_create_users.rb +19 -0
- data/spec/rails_app/db/migrate/20101015094514_bento_create_accounts.rb +15 -0
- data/spec/rails_app/db/migrate/20101015094515_bento_add_account_id_to_accounts.rb +9 -0
- data/spec/rails_app/db/migrate/20101015143011_create_projects.rb +13 -0
- data/spec/rails_app/db/production.sqlite3 +0 -0
- data/spec/rails_app/db/schema.rb +44 -0
- data/spec/rails_app/db/test.sqlite3 +0 -0
- data/spec/rails_app/log/development.log +1290 -0
- data/spec/rails_app/log/test.log +30285 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +26 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/tmp/pids/server.pid +1 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/bento_spec_helper.rb +16 -0
- data/spec/support/blueprints.rb +32 -0
- 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,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
|
data/lib/bento/rails.rb
ADDED
@@ -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,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,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
|