acts_as_tenant 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +4 -5
- data/_config.yml +1 -0
- data/acts_as_tenant.gemspec +2 -2
- data/docs/blog_post.md +67 -0
- data/lib/acts_as_tenant.rb +4 -0
- data/lib/acts_as_tenant/controller_extensions.rb +10 -6
- data/lib/acts_as_tenant/version.rb +1 -1
- data/spec/acts_as_tenant/model_extensions_spec.rb +1 -1
- data/spec/acts_as_tenant/tenant_by_filter_spec.rb +3 -3
- data/spec/acts_as_tenant/tenant_by_subdomain_spec.rb +2 -2
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abc77aadd13ef4f32cbbbe8af35e7d48177b6cba
|
4
|
+
data.tar.gz: 0f151185b5ff68073c2bc85655e3972907453482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '087f67e11e1cb133274eaa45ce505c0b535df937e8f49149e898f83eb92e69f321ce0ab58d36a8ea5aa36d38e5b6a5bf637a56c71a872effeda875dc707576f2'
|
7
|
+
data.tar.gz: 60b79f39295a57bee878532cb697ac8b5570afed65bc9ffeb01a0daa80167f2c074788e1a46855ed321d12aae18135f237ef86de360b30c58728e0f1966dcdb1
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Acts As Tenant
|
2
2
|
==============
|
3
3
|
|
4
|
-
[![Build Status](https://travis-ci.org/ErwinM/acts_as_tenant.
|
4
|
+
[![Build Status](https://travis-ci.org/ErwinM/acts_as_tenant.svg)](https://travis-ci.org/ErwinM/acts_as_tenant)
|
5
5
|
|
6
|
-
**Note**: acts_as_tenant was introduced in this [blog post](
|
6
|
+
**Note**: acts_as_tenant was introduced in this [blog post](https://github.com/ErwinM/acts_as_tenant/blob/master/docs/blog_post.md).
|
7
7
|
|
8
8
|
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.
|
9
9
|
|
@@ -58,7 +58,7 @@ Alternatively, you could locate the tenant using the method `set_current_tenant_
|
|
58
58
|
```ruby
|
59
59
|
class ApplicationController < ActionController::Base
|
60
60
|
set_current_tenant_through_filter
|
61
|
-
|
61
|
+
before_action :your_method_that_finds_the_current_tenant
|
62
62
|
|
63
63
|
def your_method_that_finds_the_current_tenant
|
64
64
|
current_account = Account.find_it
|
@@ -67,7 +67,7 @@ class ApplicationController < ActionController::Base
|
|
67
67
|
end
|
68
68
|
```
|
69
69
|
|
70
|
-
Setting the `current_tenant` yourself, requires you to declare `set_current_tenant_through_filter` at the top of your application_controller to tell acts_as_tenant that you are going to use a
|
70
|
+
Setting the `current_tenant` yourself, requires you to declare `set_current_tenant_through_filter` at the top of your application_controller to tell acts_as_tenant that you are going to use a before_action to setup the current tenant. Next you should actually setup that before_action to fetch the current tenant and pass it to `acts_as_tenant` by using `set_current_tenant(current_tenant)` in the before_action.
|
71
71
|
|
72
72
|
|
73
73
|
### Setting the current tenant for a block ###
|
@@ -213,7 +213,6 @@ If you want to contribute, fork the project, code your improvements and make a p
|
|
213
213
|
Author & Credits
|
214
214
|
----------------
|
215
215
|
acts_as_tenant is written by Erwin Matthijssen.
|
216
|
-
Erwin is currently busy developing [Roll Call](http://www.rollcallapp.com/ "Roll Call App").
|
217
216
|
|
218
217
|
This gem was inspired by Ryan Sonnek's [Multitenant](https://github.com/wireframe/multitenant) gem and its use of default_scope.
|
219
218
|
|
data/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-tactile
|
data/acts_as_tenant.gemspec
CHANGED
@@ -19,12 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
s.add_runtime_dependency('request_store', '>= 1.0.5')
|
22
|
-
s.add_dependency('rails','>=
|
22
|
+
s.add_dependency('rails','>= 4.0')
|
23
23
|
#s.add_dependency('request_store', '>= 1.0.5')
|
24
24
|
|
25
25
|
s.add_development_dependency('rspec', '>=3.0')
|
26
26
|
s.add_development_dependency('rspec-rails')
|
27
|
-
s.add_development_dependency('database_cleaner', '~> 1.3
|
27
|
+
s.add_development_dependency('database_cleaner', '~> 1.5.3')
|
28
28
|
s.add_development_dependency('sqlite3')
|
29
29
|
#s.add_development_dependency('mongoid', '~> 4.0')
|
30
30
|
|
data/docs/blog_post.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
<h2> Adding multi-tenancy to your Rails app: acts_as_tenant </h2>
|
2
|
+
Roll Call is implemented as a multi-tenant application: each user gets their own instance of the app, content is strictly scoped to a user’s instance. In Rails, this can be achieved in various ways. Guy Naor did a great job of diving into the pros and cons of each option in his <a href="http://confreaks.net/videos/111-aac2009-writing-multi-tenant-applications-in-rails">2009 Acts As Conference talk</a>. If you are doing multi-tenancy in Rails, you should watch his video.</p>
|
3
|
+
<p>With a multi-db or multi-schema approach, you only deal with the multi-tenancy-aspect in a few specific spots in your app (Jerod Santo recently wrote an excellent post on implementing a <a href="http://blog.jerodsanto.net/2011/07/building-multi-tenant-rails-apps-with-postgresql-schemas/">multi-schema strategy</a>). Compared to the previous two strategies, a <strong>shared database</strong> strategy has the downside that the ‘multi-tenancy’-logic is something you need to actively be aware of and manage in almost every part of your app.</p>
|
4
|
+
<h4>Using a Shared Database strategy is alot of work!</h4>
|
5
|
+
<p>For various other reasons we opted for a <strong>shared database</strong> strategy. However, for us the prospect of dealing with the multi-tenancy-logic throughout our app, was not appealing. Worse, we run the risk of accidently exposing content of one tenant to another one, if we mismanage this logic. While researching this topic I noticed there are no real ready made solutions available that get you on your way, <a href="http://github.com/wireframe/multitenant">Ryan Sonnek</a> wrote his ‘multitenant’ gem and <a href="http://github.com/mconnell/multi_tenant">Mark Connel</a> did the same. Neither of these solution seemed “finished” to us. So, we wrote our own implementation.</p>
|
6
|
+
<h4>First, how does multi-tenancy with a shared database strategy work</h4>
|
7
|
+
<p>A shared database strategy manages the multi-tenancy-logic through Rails associations. A tenant is represented by an object, for example an <code>Account</code>. All other objects are associated with a tenant: <code>belongs_to :account</code>. Each request starts with finding the <code>@current_account</code>. After that, each find is scoped through the tenant object: <code>current_account.projects.all</code>. This has to be remembered everywhere: in model method declarations and in controller actions. Otherwise, you’re exposing content of other tenants.</p>
|
8
|
+
<p>In addition, you have to actively babysit other parts of your app: <code>validates_uniqueness_of</code> requires you to scope it to the current tenant. You also have to protect agaist all sorts of form-injections that could allow one tenant to gain access or temper with the content of another tenant (see <a href="http://www.slideshare.net/tardate/multitenancy-with-rails">Paul Gallaghers</a> presentation for more on these dangers).</p>
|
9
|
+
<h4>Enter acts_as_tenant</h4>
|
10
|
+
<p>I wanted to implement all the concerns above in an easy to manage, out of the way fashion. We should be able to add a single declaration to our model and that should implement:</p>
|
11
|
+
<ol>
|
12
|
+
<li>scoping all searches to the current <code>Account</code></li>
|
13
|
+
<li>scoping the uniqueness validator to the current <code>Account</code></li>
|
14
|
+
<li>protecting against various nastiness trying to circumvent the scoping.</li>
|
15
|
+
</ol>
|
16
|
+
<p>The result is <code>acts_as_tenant</code> (<a href="https://github.com/ErwinM/acts_as_tenant">github</a>), a rails gem that will add multi tenancy using a shared database to your rails app in an out-of-your way fashion.</p>
|
17
|
+
<p>In the <span class="caps">README</span>, you will find more information on using <code>acts_as_tenant</code> in your projects, so we’ll give you a high-level overview here. Let’s suppose that you have an app to which you want to add multi tenancy, tenants are represented by the <code>Account</code> model and <code>Project</code> is one of the models that should be scoped by tenant:</p>
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class Addaccounttoproject < ActiveRecord::Migration
|
21
|
+
def change
|
22
|
+
add_column :projects, :account_id, :integer
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Project < ActiveRecord::Base
|
27
|
+
acts_as_tenant(:account)
|
28
|
+
validates_uniqueness_to_tenant :name
|
29
|
+
end
|
30
|
+
```
|
31
|
+
What does adding these two methods accomplish:
|
32
|
+
<ol>
|
33
|
+
<li>it ensures every search on the project model will be scoped to the current tenant,</li>
|
34
|
+
<li>it adds validation for every association confirming the associated object does indeed belong to the current tenant,</li>
|
35
|
+
<li>it validates the uniqueness of `:name` to the current tenant,</li>
|
36
|
+
<li>it implements a bunch of safeguards preventing all kinds of nastiness from exposing other tenants data (mainly form-injection attacks).</li>
|
37
|
+
</ol>
|
38
|
+
<p>Ofcourse, all the above assumes `acts_as_tenant` actually knows who the current tenant is. Two strategies are implemented to help with this.</p>
|
39
|
+
<p><strong>Using the subdomain to workout the current tenant</strong></p>
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class ApplicationController < ActionController::Base
|
43
|
+
set_current_tenant_by_subdomain(:account, :subdomain)
|
44
|
+
end
|
45
|
+
```
|
46
|
+
<p>Adding the above method to your `application_controller` tells `acts_as_tenant`:</p>
|
47
|
+
<ol>
|
48
|
+
<li>the current tenant should be found based on the subdomain (e.g. account1.myappdomain.com),</li>
|
49
|
+
<li>tenants are represented by the `Account`-model and</li>
|
50
|
+
<li>the `Account` model has a column named `subdomain` that should be used the lookup the current account, using the current subdomain.</li>
|
51
|
+
</ol>
|
52
|
+
<p><strong>Passing the current account to acts_as_tenant yourself</strong></p>
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class ApplicationController < ActionController::Base
|
56
|
+
current_account = Account.method_to_find_the_current_account
|
57
|
+
set_current_tenant_to(current_account)
|
58
|
+
end
|
59
|
+
```
|
60
|
+
<p>`Acts_as_tenant` also adds a handy helper to your controllers `current_tenant`, containing the current tenant object.</p>
|
61
|
+
<h4>Great! Anything else I should know? A few caveats:</h4>
|
62
|
+
<ul>
|
63
|
+
<li>scoping of models *only* works if `acts_as_tenant` has a current_tenant available. If you do not set one by one of the methods described above, *no scope* will be applied!</li>
|
64
|
+
<li>for validating uniqueness within a tenant scope you must use the `validates_uniqueness_to_tenant`method. This method takes all the options the regular `validates_uniqueness_of` method takes.</li>
|
65
|
+
<li>it is probably best to add the `acts_as_tenant` declaration after any other `default_scope` declarations you add to a model (I am not exactly sure how rails 3 handles the chaining. If someone can enlighten me, thanks!).</li>
|
66
|
+
</ul>
|
67
|
+
<p>We have been testing <a href="https://github.com/ErwinM/acts_as_tenant">acts_as_tenant</a> within Roll Call during recent weeks and it seems to be behaving well. Having said that, we welcome any feedback. This is my first real attempt at a plugin and the possibility of various improvements is almost a given.</p>
|
data/lib/acts_as_tenant.rb
CHANGED
@@ -18,6 +18,10 @@ if defined?(ActionController::Base)
|
|
18
18
|
ActionController::Base.extend ActsAsTenant::ControllerExtensions
|
19
19
|
end
|
20
20
|
|
21
|
+
if defined?(ActionController::API)
|
22
|
+
ActionController::API.extend ActsAsTenant::ControllerExtensions
|
23
|
+
end
|
24
|
+
|
21
25
|
module ActsAsTenant
|
22
26
|
end
|
23
27
|
|
@@ -13,8 +13,10 @@ module ActsAsTenant
|
|
13
13
|
self.tenant_column = column.to_sym
|
14
14
|
|
15
15
|
self.class_eval do
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
before_action :find_tenant_by_subdomain
|
18
|
+
helper_method :current_tenant if respond_to?(:helper_method)
|
19
|
+
|
18
20
|
|
19
21
|
private
|
20
22
|
def find_tenant_by_subdomain
|
@@ -42,8 +44,10 @@ module ActsAsTenant
|
|
42
44
|
self.tenant_second_column = second_column.to_sym
|
43
45
|
|
44
46
|
self.class_eval do
|
45
|
-
|
46
|
-
|
47
|
+
|
48
|
+
before_action :find_tenant_by_subdomain_or_domain
|
49
|
+
helper_method :current_tenant if respond_to?(:helper_method)
|
50
|
+
|
47
51
|
|
48
52
|
private
|
49
53
|
def find_tenant_by_subdomain_or_domain
|
@@ -62,10 +66,10 @@ module ActsAsTenant
|
|
62
66
|
|
63
67
|
|
64
68
|
# This method sets up a method that allows manual setting of the current_tenant. This method should
|
65
|
-
# be used in a
|
69
|
+
# be used in a before_action. In addition, a helper is setup that returns the current_tenant
|
66
70
|
def set_current_tenant_through_filter
|
67
71
|
self.class_eval do
|
68
|
-
helper_method :current_tenant
|
72
|
+
helper_method :current_tenant if respond_to?(:helper_method)
|
69
73
|
|
70
74
|
private
|
71
75
|
def set_current_tenant(current_tenant_object)
|
@@ -24,7 +24,7 @@ describe ActsAsTenant do
|
|
24
24
|
@project = @account.projects.create!(:name => 'bar')
|
25
25
|
end
|
26
26
|
|
27
|
-
it { expect {@project.account_id = @account.id + 1}.to raise_error }
|
27
|
+
it { expect {@project.account_id = @account.id + 1}.to raise_error(ActsAsTenant::Errors::TenantIsImmutable) }
|
28
28
|
end
|
29
29
|
|
30
30
|
describe 'setting tenant_id to the same value should not error' do
|
@@ -8,7 +8,7 @@ end
|
|
8
8
|
class ApplicationController2 < ActionController::Base
|
9
9
|
include Rails.application.routes.url_helpers
|
10
10
|
set_current_tenant_through_filter
|
11
|
-
|
11
|
+
before_action :your_method_that_finds_the_current_tenant
|
12
12
|
|
13
13
|
def your_method_that_finds_the_current_tenant
|
14
14
|
current_account = Account.new
|
@@ -22,7 +22,7 @@ end
|
|
22
22
|
describe ApplicationController2, :type => :controller do
|
23
23
|
controller do
|
24
24
|
def index
|
25
|
-
render :
|
25
|
+
render :plain => "custom called"
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -30,4 +30,4 @@ describe ApplicationController2, :type => :controller do
|
|
30
30
|
get :index
|
31
31
|
expect(ActsAsTenant.current_tenant.name).to eq 'account1'
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|
@@ -12,7 +12,7 @@ end
|
|
12
12
|
describe ApplicationController, :type => :controller do
|
13
13
|
controller do
|
14
14
|
def index
|
15
|
-
render :
|
15
|
+
render :plain => "custom called"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -29,4 +29,4 @@ describe ApplicationController, :type => :controller do
|
|
29
29
|
get :index
|
30
30
|
expect(ActsAsTenant.current_tenant).to eq 'account1'
|
31
31
|
end
|
32
|
-
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erwin Matthijssen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: request_store
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '4.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '4.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.3
|
75
|
+
version: 1.5.3
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.3
|
82
|
+
version: 1.5.3
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: sqlite3
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,7 +123,9 @@ files:
|
|
123
123
|
- MIT-LICENSE
|
124
124
|
- README.md
|
125
125
|
- Rakefile
|
126
|
+
- _config.yml
|
126
127
|
- acts_as_tenant.gemspec
|
128
|
+
- docs/blog_post.md
|
127
129
|
- lib/acts_as_tenant.rb
|
128
130
|
- lib/acts_as_tenant/configuration.rb
|
129
131
|
- lib/acts_as_tenant/controller_extensions.rb
|
@@ -161,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
163
|
version: '0'
|
162
164
|
requirements: []
|
163
165
|
rubyforge_project: acts_as_tenant
|
164
|
-
rubygems_version: 2.
|
166
|
+
rubygems_version: 2.6.11
|
165
167
|
signing_key:
|
166
168
|
specification_version: 4
|
167
169
|
summary: Add multi-tenancy to Rails applications using a shared db strategy
|