acts_as_tenant 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ spec/*.sqlite3
6
+ spec/*.log
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2@rails31
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in acts_as_tenant.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,117 @@
1
+ Acts As Tenant
2
+ ==============
3
+
4
+ note: acts_as_tenant was introduced in this [blog post](http://www.rollcallapp.com/blog/add).
5
+
6
+ This gem was born out of our own need for a fail-safe and out-of-the-way manner to add multi-tenancy to our Rails app through a shared database strategy, that integrates (near) seamless with Rails.
7
+
8
+ acts_as_tenant adds the ability to scope models to a tenant. Tenants are represented by a tenant model, such as `Account`. acts_as_tenant will help you set the current tenant on each request and ensures all 'tenant models' are always properly scoped to the current tenant: when viewing, searching and creating.
9
+
10
+ In addition, acts_as_tenant:
11
+
12
+ * sets the current tenant using the subdomain or allows you to pass in the current tenant yourself
13
+ * protects against various types of nastiness directed at circumventing the tenant scoping
14
+ * adds a method to validate uniqueness to a tenant, validates_uniqueness_to_tenant
15
+ * sets up a helper method containing the current tenant
16
+
17
+ Installation
18
+ ------------
19
+ acts_as_tenant will only work on Rails 3.1 and up. This is due to changes made to the handling of default_scope, an essential pillar of the gem.
20
+
21
+ To use it, add it to your Gemfile:
22
+
23
+ gem 'acts_as_tenant'
24
+
25
+ Getting started
26
+ ===============
27
+ There are two steps in adding multi-tenancy to your app with acts_as_tenant:
28
+
29
+ 1. setting the current tenant and
30
+ 2. scoping your models.
31
+
32
+ Setting the current tenant
33
+ --------------------------
34
+ There are two ways to set the current tenant: (1) by using the subdomain to lookup the current tenant and (2) by passing in the current tenant yourself.
35
+
36
+ **Use the subdomain to lookup the current tenant**
37
+
38
+ class ApplicationController < ActionController::Base
39
+ set_current_tenant_by_subdomain(:account, :subdomain)
40
+ end
41
+ This tells acts_as_tenant to use the current subdomain to identify the current tenant. In addition, it tells acts_as_tenant that tenants are represented by the Account model and this model has a column named 'subdomain' which can be used to lookup the Account using the actual subdomain. If ommitted, the parameters will default to the values used above.
42
+
43
+ **OR Pass in the current tenant yourself**
44
+
45
+ class ApplicationController < ActionController::Base
46
+ current_account = Account.find_the_current_account
47
+ set_current_tenant_to(current_account)
48
+ end
49
+ This allows you to pass in the current tenant yourself.
50
+
51
+ **note:** If the current tenant is not set by either of these methods, Acts_as_tenant will be unable to apply the proper scope to your models. So make sure you use one of the two methods to tell acts_as_tenant about the current tenant.
52
+
53
+ Scoping your models
54
+ -------------------
55
+ class Addaccounttousers < ActiveRecord::Migration
56
+ def up
57
+ add_column :users, :account_id, :integer
58
+ end
59
+
60
+ class User < ActiveRecord::Base
61
+ acts_as_tenant(:account)
62
+ end
63
+
64
+ acts_as_tenant requires each scoped model to have a column in its schema linking it to a tenant. Adding acts_as_tenant to your model declaration will scope that model to the current tenant **BUT ONLY if a current tenant has been set**.
65
+
66
+ Some examples to illustrate this behavior:
67
+
68
+ # This manually sets the current tenant for testing purposes. In your app this is handled by the gem.
69
+ acts_as_tenant.current_tenant = Account.find(3)
70
+
71
+ # All searches are scoped by the tenant, the following searches will only return objects
72
+ # where account_id == 3
73
+ Project.all => # all projects with account_id => 3
74
+ Project.tasks.all # => all tasks with account_id => 3
75
+
76
+ # New objects are scoped to the current tenant
77
+ @project = Project.new(:name => 'big project') # => <#Project id: nil, name: 'big project', :account_id: 3>
78
+
79
+ # It will not allow the creation of objects outside the current_tenant scope
80
+ @project.account_id = 2
81
+ @project.save # => false
82
+
83
+ # It will not allow association with objects outside the current tenant scope
84
+ # Assuming the Project with ID: 2 does not belong to Account with ID: 3
85
+ @task = Task.new # => <#Task id: nil, name: bil, project_id: nil, :account_id: 3>
86
+
87
+ Acts_as_tenant uses Rails' default_scope method to scope models. Rails 3.1 changed the way default_scope works in a good way. A user defined default_scope should integrate seamlessly with the one added by acts_as_tenant.
88
+
89
+ **Validating attribute uniqueness**
90
+ If you need to validate for uniqueness, chances are that you want to scope this validation to a tenant. You can do so by using:
91
+
92
+ validates_uniqueness_to_tenant :name, :email
93
+
94
+ All options available to Rails' own @validates_uniqueness_of@ are also available to this method.
95
+
96
+ To Do
97
+ -----
98
+ * Change the tests to Test::Unit so I can easily add some controller tests.
99
+
100
+ Bug reports & suggested improvements
101
+ ------------------------------------
102
+ If you have found a bug or want to suggest an improvement, please use our issue tracked at:
103
+
104
+ [github.com/ErwinM/acts_as_tenant/issues](http://github.com/ErwinM/acts_as_tenant/issues)
105
+
106
+ If you want to contribute, fork the project, code your improvements and make a pull request on [Github](http://github.com/ErwinM/acts_as_tenant/). When doing so, please don't forget to add tests. If your contribution is fixing a bug it would be perfect if you could also submit a failing test, illustrating the issue.
107
+
108
+ Author & Credits
109
+ ----------------
110
+ acts_as_tenant is written by Erwin Matthijssen.
111
+ Erwin is currently busy developing [Roll Call](http://www.rollcallapp.com/ "Roll Call App").
112
+
113
+ This gem was inspired by Ryan Sonnek's [Multitenant](https://github.com/wireframe/multitenant) gem and its use of default_scope.
114
+
115
+ License
116
+ -------
117
+ Copyright (c) 2011 Erwin Matthijssen, released under the MIT license
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acts_as_tenant/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acts_as_tenant"
7
+ s.version = ActsAsTenant::VERSION
8
+ s.authors = ["Erwin Matthijssen"]
9
+ s.email = ["erwin.matthijssen@gmail.com"]
10
+ s.homepage = "http://www.rollcallapp.com/blog"
11
+ s.summary = %q{Add multi-tenancy to Rails applications using a shared db strategy}
12
+ s.description = %q{Integrates multi-tenancy into a Rails application in a convenient and out-of-your way manner}
13
+
14
+ s.rubyforge_project = "acts_as_tenant"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,22 @@
1
+ #RAILS_3 = ::ActiveRecord::VERSION::MAJOR >= 3
2
+
3
+ require "active_record"
4
+ require "action_controller"
5
+ require "active_model"
6
+
7
+ #$LOAD_PATH.unshift(File.dirname(__FILE__))
8
+
9
+ require "acts_as_tenant"
10
+ require "acts_as_tenant/version"
11
+ require "acts_as_tenant/controller_extensions.rb"
12
+ require "acts_as_tenant/model_extensions.rb"
13
+
14
+ #$LOAD_PATH.shift
15
+
16
+ if defined?(ActiveRecord::Base)
17
+ ActiveRecord::Base.send(:include, ActsAsTenant::ModelExtensions)
18
+ ActionController::Base.extend ActsAsTenant::ControllerExtensions
19
+ end
20
+
21
+
22
+
@@ -0,0 +1,52 @@
1
+ module ActsAsTenant
2
+ module ControllerExtensions
3
+
4
+ # this method allows setting the current_tenant by reading the subdomain and looking
5
+ # it up in the tenant-model passed to the method (defaults to Account). The method will
6
+ # look for the subdomain in a column referenced by the second argument (defaults to subdomain).
7
+ def set_current_tenant_by_subdomain(tenant = :account, column = :subdomain )
8
+ self.class_eval do
9
+ cattr_accessor :tenant_class, :tenant_column
10
+ attr_accessor :current_tenant
11
+ end
12
+
13
+ self.tenant_class = tenant.to_s.capitalize.constantize
14
+ self.tenant_column = column.to_sym
15
+
16
+ self.class_eval do
17
+ before_filter :find_tenant_by_subdomain
18
+
19
+ helper_method :current_tenant
20
+
21
+ private
22
+ def find_tenant_by_subdomain
23
+ ActsAsTenant.current_tenant = tenant_class.where(tenant_column => request.subdomains.first).first
24
+ @current_tenant_instance = ActsAsTenant.current_tenant
25
+ end
26
+
27
+ # helper method to have the current_tenant available in the controller
28
+ def current_tenant
29
+ ActsAsTenant.current_tenant
30
+ end
31
+ end
32
+ end
33
+
34
+ # this method allows manual setting of the current_tenant by passing in a tenant object
35
+ #
36
+ def set_current_tenant_to(current_tenant_object)
37
+ self.class_eval do
38
+ cattr_accessor :tenant_class
39
+ attr_accessor :current_tenant
40
+ before_filter lambda { @current_tenant_instance = ActsAsTenant.current_tenant = current_tenant_object }
41
+
42
+ helper_method :current_tenant
43
+
44
+ private
45
+ # helper method to have the current_tenant available in the controller
46
+ def current_tenant
47
+ ActsAsTenant.current_tenant
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,89 @@
1
+ # ActsAsTenant
2
+
3
+
4
+ module ActsAsTenant
5
+
6
+ class << self
7
+ cattr_accessor :tenant_class
8
+ attr_accessor :current_tenant
9
+ end
10
+
11
+ module ModelExtensions
12
+ extend ActiveSupport::Concern
13
+
14
+ # Alias the v_uniqueness_of method so we can scope it to the current tenant when relevant
15
+
16
+ module ClassMethods
17
+
18
+ def acts_as_tenant(association = :account)
19
+
20
+ # Method that enables checking if a class is scoped by tenant
21
+ def self.is_scoped_by_tenant?
22
+ true
23
+ end
24
+
25
+ ActsAsTenant.tenant_class ||= association
26
+
27
+ # Setup the association between the class and the tenant class
28
+ belongs_to association
29
+
30
+ # get the tenant model and its foreign key
31
+ reflection = reflect_on_association association
32
+
33
+ # As the "foreign_key" method changed name in 3.1 we check for backward compatibility
34
+ if reflection.respond_to?(:foreign_key)
35
+ fkey = reflection.foreign_key
36
+ else
37
+ fkey = reflection.association_foreign_key
38
+ end
39
+
40
+ # set the current_tenant on newly created objects
41
+ before_validation Proc.new {|m|
42
+ return unless ActsAsTenant.current_tenant
43
+ m.send "#{association}=".to_sym, ActsAsTenant.current_tenant
44
+ }, :on => :create
45
+
46
+ # set the default_scope to scope to current tenant
47
+ default_scope lambda {
48
+ where({fkey => ActsAsTenant.current_tenant.id}) if ActsAsTenant.current_tenant
49
+ }
50
+
51
+ # Rewrite accessors to make tenant foreign_key/association immutable
52
+ define_method "#{fkey}=" do |integer|
53
+ if new_record?
54
+ write_attribute(fkey, integer)
55
+ else
56
+ raise "#{fkey} is immutable!"
57
+ end
58
+ end
59
+
60
+ define_method "#{association}=" do |model|
61
+ if new_record?
62
+ write_attribute(association, model)
63
+ else
64
+ raise "#{association} is immutable!"
65
+ end
66
+ end
67
+
68
+ # add validation of associations against tenant scope
69
+ # we can't do this for polymorphic associations so we
70
+ # exempt them
71
+ reflect_on_all_associations.each do |a|
72
+ unless a == reflection || a.macro == :has_many || a.options[:polymorphic]
73
+ validates_each a.foreign_key.to_sym do |record, attr, value|
74
+ record.errors.add attr, "is invalid" unless a.name.to_s.classify.constantize.where(:id => value).present?
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def validates_uniqueness_to_tenant(fields, args ={})
81
+ raise "ActsAsTenant::validates_uniqueness_to_tenant: no current tenant" unless respond_to?(:is_scoped_by_tenant?)
82
+ tenant_id = lambda { "#{ActsAsTenant.tenant_class.to_s.downcase}_id"}.call
83
+ args[:scope].nil? ? args[:scope] = tenant_id : args[:scope] << tenant_id
84
+ validates_uniqueness_of(fields, args)
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsTenant
2
+ VERSION = "0.2"
3
+ end
@@ -0,0 +1,2 @@
1
+ ActiveRecord::Base.send(:include, ActsAsTenant::ModelExtensions)
2
+ ActionController::Base.extend ActsAsTenant::ControllerExtensions
@@ -0,0 +1,3 @@
1
+ sqlite:
2
+ adapter: sqlite3
3
+ database: spec/actsastenant.sqlite3
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ # Setup the db
4
+ ActiveRecord::Schema.define(:version => 1) do
5
+ create_table :accounts, :force => true do |t|
6
+ t.column :name, :string
7
+ end
8
+
9
+ create_table :projects, :force => true do |t|
10
+ t.column :name, :string
11
+ t.column :account_id, :integer
12
+ end
13
+
14
+ create_table :tasks, :force => true do |t|
15
+ t.column :name, :string
16
+ t.column :account_id, :integer
17
+ t.column :project_id, :integer
18
+ t.column :completed, :boolean
19
+ end
20
+
21
+ create_table :countries, :force => true do |t|
22
+ t.column :name, :string
23
+ end
24
+
25
+ create_table :cities, :force => true do |t|
26
+ t.column :name, :string
27
+ end
28
+
29
+ end
30
+
31
+ # Setup the models
32
+ class Account < ActiveRecord::Base
33
+ has_many :projects
34
+ end
35
+
36
+ class Project < ActiveRecord::Base
37
+ has_many :tasks
38
+ acts_as_tenant :account
39
+
40
+ validates_uniqueness_to_tenant :name
41
+ end
42
+
43
+ class Task < ActiveRecord::Base
44
+ belongs_to :project
45
+ default_scope :conditions => { :completed => nil }, :order => "name"
46
+
47
+ acts_as_tenant :account
48
+ validates_uniqueness_of :name
49
+ end
50
+
51
+
52
+ class City < ActiveRecord::Base
53
+ validates_uniqueness_of :name
54
+ end
55
+
56
+
57
+ # Start testing!
58
+ describe ActsAsTenant do
59
+ after { ActsAsTenant.current_tenant = nil }
60
+
61
+ describe 'Setting the current tenant' do
62
+ before { ActsAsTenant.current_tenant = :foo }
63
+ it { ActsAsTenant.current_tenant == :foo }
64
+ end
65
+
66
+ describe 'is_scoped_as_tenant should return the correct value' do
67
+ it {Project.respond_to?(:is_scoped_by_tenant?).should == true}
68
+ end
69
+
70
+ describe 'Project.all should be scoped to the current tenant if set' do
71
+ before do
72
+ @account1 = Account.create!(:name => 'foo')
73
+ @account2 = Account.create!(:name => 'bar')
74
+
75
+ @project1 = @account1.projects.create!(:name => 'foobar')
76
+ @project2 = @account2.projects.create!(:name => 'baz')
77
+
78
+ ActsAsTenant.current_tenant= @account1
79
+ @projects = Project.all
80
+ end
81
+
82
+ it { @projects.length.should == 1 }
83
+ it { @projects.should == [@project1] }
84
+ end
85
+
86
+ describe 'Associations should be correctly scoped by current tenant' do
87
+ before do
88
+ @account = Account.create!(:name => 'foo')
89
+ @project = @account.projects.create!(:name => 'foobar', :account_id => @account.id )
90
+ # the next line would normally be nearly impossible: a task assigned to a tenant project,
91
+ # but the task has no tenant assigned
92
+ @task1 = Task.create!(:name => 'no_tenant', :project => @project)
93
+
94
+ ActsAsTenant.current_tenant = @account
95
+ @task2 = @project.tasks.create!(:name => 'baz')
96
+ @tasks = @project.tasks
97
+ end
98
+
99
+ it 'should correctly set the tenant on the task created with current_tenant set' do
100
+ @task2.account.should == @account
101
+ end
102
+
103
+ it 'should filter out the non-tenant task from the project' do
104
+ @tasks.length.should == 1
105
+ end
106
+ end
107
+
108
+ describe 'When dealing with a user defined default_scope' do
109
+ before do
110
+ @account = Account.create!(:name => 'foo')
111
+ @project1 = Project.create!(:name => 'inaccessible')
112
+ @task1 = Task.create!(:name => 'no_tenant', :project => @project1)
113
+
114
+ ActsAsTenant.current_tenant = @account
115
+ @project2 = Project.create!(:name => 'accessible')
116
+ @task2 = @project2.tasks.create!(:name => 'bar')
117
+ @task3 = @project2.tasks.create!(:name => 'baz')
118
+ @task4 = @project2.tasks.create!(:name => 'foo')
119
+ @task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
120
+
121
+ @tasks= Task.all
122
+ end
123
+
124
+ it 'should apply both the tenant scope and the user defined default_scope, including :order' do
125
+ @tasks.length.should == 3
126
+ @tasks.should == [@task2, @task3, @task4]
127
+ end
128
+ end
129
+
130
+ describe 'tenant_id should be immutable' do
131
+ before do
132
+ @account = Account.create!(:name => 'foo')
133
+ @project = @account.projects.create!(:name => 'bar')
134
+ end
135
+
136
+ it { lambda {@project.account_id = @account.id + 1}.should raise_error }
137
+ end
138
+
139
+ describe 'Associations can only be made with in-scope objects' do
140
+ before do
141
+ @account = Account.create!(:name => 'foo')
142
+ @project1 = Project.create!(:name => 'inaccessible_project', :account_id => @account.id + 1)
143
+
144
+ ActsAsTenant.current_tenant = @account
145
+ @project2 = Project.create!(:name => 'accessible_project')
146
+ @task = @project2.tasks.create!(:name => 'bar')
147
+ end
148
+
149
+ it { @task.update_attributes(:project_id => @project1.id).should == false }
150
+ end
151
+
152
+ describe 'When using validates_uniqueness_to_tenant in a aat model' do
153
+ before do
154
+ @account = Account.create!(:name => 'foo')
155
+ ActsAsTenant.current_tenant = @account
156
+ @project1 = Project.create!(:name => 'bar')
157
+ end
158
+
159
+ it 'should not be possible to create a duplicate within the same tenant' do
160
+ @project2 = Project.create(:name => 'bar').valid?.should == false
161
+ end
162
+
163
+ it 'should be possible to create a duplicate outside the tenant scope' do
164
+ @account = Account.create!(:name => 'baz')
165
+ ActsAsTenant.current_tenant = @account
166
+ @project2 = Project.create(:name => 'bar').valid?.should == true
167
+ end
168
+ end
169
+
170
+ describe 'When using validates_uniqueness_of in a NON-aat model' do
171
+ before do
172
+ @city1 = City.create!(:name => 'foo')
173
+ end
174
+ it 'should not be possible to create duplicates' do
175
+ @city2 = City.create(:name => 'foo').valid?.should == false
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,41 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'active_record'
5
+ require 'action_controller'
6
+ require 'logger'
7
+ require 'database_cleaner'
8
+
9
+ require 'acts_as_tenant/model_extensions'
10
+ require 'acts_as_tenant/controller_extensions'
11
+
12
+ ActiveRecord::Base.send(:include, ActsAsTenant::ModelExtensions)
13
+ ActionController::Base.extend ActsAsTenant::ControllerExtensions
14
+
15
+ config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
16
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
17
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
18
+
19
+ # Requires supporting files with custom matchers and macros, etc,
20
+ # in ./support/ and its subdirectories.
21
+ #Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
22
+
23
+ #RSpec.configure do |config|
24
+ #end
25
+
26
+ Spec::Runner.configure do |config|
27
+
28
+ config.before(:suite) do
29
+ DatabaseCleaner.strategy = :transaction
30
+ DatabaseCleaner.clean_with(:truncation)
31
+ end
32
+
33
+ config.before(:each) do
34
+ DatabaseCleaner.start
35
+ end
36
+
37
+ config.after(:each) do
38
+ DatabaseCleaner.clean
39
+ end
40
+
41
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_tenant
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Erwin Matthijssen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-03 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Integrates multi-tenancy into a Rails application in a convenient and
15
+ out-of-your way manner
16
+ email:
17
+ - erwin.matthijssen@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - .rvmrc
24
+ - Gemfile
25
+ - MIT-LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - acts_as_tenant.gemspec
29
+ - lib/acts_as_tenant.rb
30
+ - lib/acts_as_tenant/controller_extensions.rb
31
+ - lib/acts_as_tenant/model_extensions.rb
32
+ - lib/acts_as_tenant/version.rb
33
+ - rails/init.rb
34
+ - spec/database.yml
35
+ - spec/model_extensions_spec.rb
36
+ - spec/spec_helper.rb
37
+ homepage: http://www.rollcallapp.com/blog
38
+ licenses: []
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project: acts_as_tenant
57
+ rubygems_version: 1.8.6
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Add multi-tenancy to Rails applications using a shared db strategy
61
+ test_files:
62
+ - spec/database.yml
63
+ - spec/model_extensions_spec.rb
64
+ - spec/spec_helper.rb