acts_as_tenant 0.2

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.
@@ -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