acts_as_tenant 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZmFmMDEwY2Y3YTYwOGY4MzRmYTRmYTQ3ZDYwNTFlMTE5YTRmN2U5Mg==
4
+ ZTU3MTcwNGYyNzk1NDk3OWMyNmQxYWJjNDdkNjI5NDVjZjc4MjA0MQ==
5
5
  data.tar.gz: !binary |-
6
- YjNmNmNhNDBiNjE3NjQxNTI2MjM0NTNkZWFhZTRjYTBhMjBkZjhlMw==
6
+ NTUxMDJkZmI1NmE3MGY3NzliMzI5N2ZiOGY4MTA1ZGQyZDlkNDRlYg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NWZhZGE3MDA5OTczZDNmNmY1MDllZDIwYjgwYWMwNGUyMjQ3ZTdmNWRkNTgz
10
- Y2Q3YTU1YTVkNGJjYzI4YjBlMGQwMWNhN2Q4OWU4YTBhZTkyMzYxZjgyOTMw
11
- ZGY3ODFjMzVjNmUyZTBmNjE0MjA5YzMwZTllODc4ZGUyMjdjMTE=
9
+ ZDFkMWRmYjRhNTdmZmYxNDFmOWE4YjYxNTVhMTU0ZDYxNjE3OTk2Yjk0Yzdh
10
+ OWRlZGFmODNlN2E1NWJlNmRmNmVkYTEzOWVlOGViODBlNzY5YmYxY2QzMDZl
11
+ NTgwMjU5MjU3MGFkN2Q5MzBkY2MwMzljMDhiNTViZTI1ZmY3OTk=
12
12
  data.tar.gz: !binary |-
13
- YTgzOTVhMzZiMzFhZjQ1MjNjMmUzZWNlZjRlOWFkYTY1YzhmNWRhMzViMzI2
14
- ZTI1OTZiODRiMTY2NjY2NjBjYTRhYTNlNjljMmU2MDllNDU3Mzg5OTYyZTZm
15
- MWRjYWM2ZDY3YmRiOGQ2YzYyNjA2MGE5MGI1YzhiMjk1NzliZjI=
13
+ ODI0OTM1YzE1NGM1N2QwNzBiNzZlMzI1MTM1YjhkZDU0Nzc2OGU4Y2FkMTdi
14
+ YTZmYjU3YjJiZjdkOGI5NWFmNDI4ZGNhMjk4NTZmM2VkYTYzZDg1MTc3NDQx
15
+ NmYxZmM4YmM2ODg5MjhmYjgzNDEwZTk0ZTVmODZkMGM1YWVlMjQ=
@@ -1,6 +1,12 @@
1
+ 0.3.9
2
+ -----
3
+ * Added ability to configure a default tenant for testing purposes. (thx iangreenleaf)
4
+ * AaT will now accept a string for a tenant_id (thx calebthompson)
5
+ * Improvements to readme (thx stgeneral)
6
+
1
7
  0.3.8
2
8
  -----
3
- * Added Mongoid compatibility [thx iangreenlead]
9
+ * Added Mongoid compatibility [thx iangreenleaf]
4
10
 
5
11
  0.3.7
6
12
  -----
data/README.md CHANGED
@@ -18,11 +18,13 @@ In addition, acts_as_tenant:
18
18
 
19
19
  Installation
20
20
  ------------
21
- 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.
21
+ 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.
22
22
 
23
23
  To use it, add it to your Gemfile:
24
24
 
25
- gem 'acts_as_tenant'
25
+ ```ruby
26
+ gem 'acts_as_tenant'
27
+ ```
26
28
 
27
29
  Getting started
28
30
  ===============
@@ -33,98 +35,118 @@ There are two steps in adding multi-tenancy to your app with acts_as_tenant:
33
35
 
34
36
  Setting the current tenant
35
37
  --------------------------
36
- There are three ways to set the current tenant: (1) by using the subdomain to lookup the current tenant, (2) by setting the current tenant in the controller, and
37
- (3) by setting the current tenant for a block.
38
+ There are three ways to set the current tenant:
38
39
 
39
- **Use the subdomain to lookup the current tenant**
40
+ 1. by using the subdomain to lookup the current tenant,
41
+ 2. by setting the current tenant in the controller, and
42
+ 3. by setting the current tenant for a block.
40
43
 
41
- class ApplicationController < ActionController::Base
42
- set_current_tenant_by_subdomain(:account, :subdomain)
43
- end
44
+ ### Use the subdomain to lookup the current tenant ###
45
+
46
+ ```ruby
47
+ class ApplicationController < ActionController::Base
48
+ set_current_tenant_by_subdomain(:account, :subdomain)
49
+ end
50
+ ```
44
51
 
45
52
  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.
46
53
 
47
- Alternatively, you could locate the tenant using the method set_current_tenant_by_subdomain_or_domain( :account, :subdomain, :domain ) which will try to match a record first by subdomain. in case it fails, by domain.
54
+ Alternatively, you could locate the tenant using the method `set_current_tenant_by_subdomain_or_domain( :account, :subdomain, :domain )` which will try to match a record first by subdomain. in case it fails, by domain.
48
55
 
49
- **Setting the current tenant in a controller, manually**
56
+ ### Setting the current tenant in a controller, manually ###
50
57
 
51
- class ApplicationController < ActionController::Base
52
- set_current_tenant_through_filter
53
- before_filter :your_method_that_finds_the_current_tenant
58
+ ```ruby
59
+ class ApplicationController < ActionController::Base
60
+ set_current_tenant_through_filter
61
+ before_filter :your_method_that_finds_the_current_tenant
62
+
63
+ def your_method_that_finds_the_current_tenant
64
+ current_account = Account.find_it
65
+ set_current_tenant(current_account)
66
+ end
67
+ end
68
+ ```
54
69
 
55
- def your_method_that_finds_the_current_tenant
56
- current_account = Account.find_it
57
- set_current_tenant(current_account)
58
- end
59
- end
60
- 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_filter to setup the current tenant. Next you should actually setup that before_filter to fetch the current tenant and pass it to `acts_as_tenant` by using `set_current_tenant(current_tenant)` in the before_filter.
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_filter to setup the current tenant. Next you should actually setup that before_filter to fetch the current tenant and pass it to `acts_as_tenant` by using `set_current_tenant(current_tenant)` in the before_filter.
61
71
 
62
72
 
63
- **Setting the current tenant for a block**
73
+ ### Setting the current tenant for a block ###
64
74
 
65
- ActsAsTenant.with_tenant(current_account) do
66
- # Current tenant is set for all code in this block
67
- end
75
+ ```ruby
76
+ ActsAsTenant.with_tenant(current_account) do
77
+ # Current tenant is set for all code in this block
78
+ end
79
+ ```
68
80
 
69
81
  This approach is useful when running background processes for a specified tenant. For example, by putting this in your worker's run method,
70
82
  any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
71
83
 
72
- **note:** If the current tenant is not set by one 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.
84
+ **Note:** If the current tenant is not set by one 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.
73
85
 
74
- **Require tenant to be set always**
86
+ ### Require tenant to be set always ###
75
87
 
76
88
  If you want to require the tenant to be set at all times, you can configure acts_as_tenant to raise an error when a query is made without a tenant available. See below under configuarion options.
77
89
 
78
90
  Scoping your models
79
91
  -------------------
80
- class Addaccounttousers < ActiveRecord::Migration
81
- def up
82
- add_column :users, :account_id, :integer
83
- end
84
- end
85
92
 
86
- class User < ActiveRecord::Base
87
- acts_as_tenant(:account)
88
- end
93
+ ```ruby
94
+ class AddAccountToUsers < ActiveRecord::Migration
95
+ def up
96
+ add_column :users, :account_id, :integer
97
+ add_index :users, :account_id
98
+ end
99
+ end
89
100
 
90
- 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**.
101
+ class User < ActiveRecord::Base
102
+ acts_as_tenant(:account)
103
+ end
104
+ ```
105
+
106
+ `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**.
91
107
 
92
108
  Some examples to illustrate this behavior:
93
109
 
94
- # This manually sets the current tenant for testing purposes. In your app this is handled by the gem.
95
- ActsAsTenant.current_tenant = Account.find(3)
110
+ ```ruby
111
+ # This manually sets the current tenant for testing purposes. In your app this is handled by the gem.
112
+ ActsAsTenant.current_tenant = Account.find(3)
96
113
 
97
- # All searches are scoped by the tenant, the following searches will only return objects
98
- # where account_id == 3
99
- Project.all => # all projects with account_id => 3
100
- Project.tasks.all # => all tasks with account_id => 3
114
+ # All searches are scoped by the tenant, the following searches will only return objects
115
+ # where account_id == 3
116
+ Project.all => # all projects with account_id => 3
117
+ Project.tasks.all # => all tasks with account_id => 3
101
118
 
102
- # New objects are scoped to the current tenant
103
- @project = Project.new(:name => 'big project') # => <#Project id: nil, name: 'big project', :account_id: 3>
119
+ # New objects are scoped to the current tenant
120
+ @project = Project.new(:name => 'big project') # => <#Project id: nil, name: 'big project', :account_id: 3>
104
121
 
105
- # It will not allow the creation of objects outside the current_tenant scope
106
- @project.account_id = 2
107
- @project.save # => false
122
+ # It will not allow the creation of objects outside the current_tenant scope
123
+ @project.account_id = 2
124
+ @project.save # => false
108
125
 
109
- # It will not allow association with objects outside the current tenant scope
110
- # Assuming the Project with ID: 2 does not belong to Account with ID: 3
111
- @task = Task.new # => <#Task id: nil, name: nil, project_id: nil, :account_id: 3>
126
+ # It will not allow association with objects outside the current tenant scope
127
+ # Assuming the Project with ID: 2 does not belong to Account with ID: 3
128
+ @task = Task.new # => <#Task id: nil, name: nil, project_id: nil, :account_id: 3>
129
+ ```
112
130
 
113
- 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.
131
+ 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`.
114
132
 
115
- **Validating attribute uniqueness**
133
+ ### Validating attribute uniqueness ###
116
134
 
117
135
  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:
118
136
 
119
- validates_uniqueness_to_tenant :name, :email
137
+ ```ruby
138
+ validates_uniqueness_to_tenant :name, :email
139
+ ```
120
140
 
121
141
  All options available to Rails' own `validates_uniqueness_of` are also available to this method.
122
142
 
123
- **Custom foreign_key**
143
+ ### Custom foreign_key ###
124
144
 
125
145
  You can explicitely specifiy a foreign_key for AaT to use should the key differ from the default:
126
146
 
127
- acts_as_tenant(:account, :foreign_key => 'accountID) # by default AaT expects account_id
147
+ ```ruby
148
+ acts_as_tenant(:account, :foreign_key => 'accountID) # by default AaT expects account_id
149
+ ```
128
150
 
129
151
  Configuration options
130
152
  ---------------------
@@ -149,9 +171,23 @@ Add the following code to your `config/initializer/acts_as_tenant.rb`:
149
171
  require 'acts_as_tenant/sidekiq'
150
172
  ```
151
173
 
152
- Note on testing
174
+ Testing
153
175
  ---------------
154
- Whenever you set the `current_tenant` in your tests, either through integration tests or directly by calling `ActsAsTenant.current_tenant = some_tenant`, make sure to clean up the tenant after each test by calling `ActsAsTenant.current_tenant = nil`.
176
+
177
+ If you set the `current_tenant` in your tests, make sure to clean up the tenant after each test by calling `ActsAsTenant.current_tenant = nil`. If you are manually setting the `current_tenant` in integration tests, please be aware that the value will not survive across multiple requests, even if they take place within the same test.
178
+
179
+ If you'd like to set a default tenant that will survive across multiple requests, assign a value to `default_tenant`. You might use a before hook like this:
180
+
181
+ ```ruby
182
+ # Make the default tenant globally available to the tests
183
+ $default_account = Account.create!
184
+ # Specify this account as the current tenant unless overridden
185
+ ActsAsTenant.default_tenant = $default_account
186
+ # Stub out the method setting a tenant in a controller hook
187
+ allow_any_instance_of(ApplicationController).to receive(:set_current_tenant)
188
+ ```
189
+
190
+ This can later be overridden by using any of the standard methods for specifying a different tenant. If you don't want this setting to apply to all of your tests, remember to clear it when you're finished by setting `ActsAsTenant.default_tenant = nil`.
155
191
 
156
192
  To Do
157
193
  -----
@@ -1,26 +1,26 @@
1
1
  module ActsAsTenant
2
2
  module ControllerExtensions
3
-
3
+
4
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. The method will look for the subdomain
5
+ # it up in the tenant-model passed to the method. The method will look for the subdomain
6
6
  # in a column referenced by the second argument.
7
7
  def set_current_tenant_by_subdomain(tenant = :account, column = :subdomain )
8
8
  self.class_eval do
9
9
  cattr_accessor :tenant_class, :tenant_column
10
10
  end
11
-
11
+
12
12
  self.tenant_class = tenant.to_s.camelcase.constantize
13
13
  self.tenant_column = column.to_sym
14
-
14
+
15
15
  self.class_eval do
16
16
  before_filter :find_tenant_by_subdomain
17
17
  helper_method :current_tenant
18
-
18
+
19
19
  private
20
20
  def find_tenant_by_subdomain
21
21
  ActsAsTenant.current_tenant = tenant_class.where(tenant_column => request.subdomains.last).first
22
22
  end
23
-
23
+
24
24
  def current_tenant
25
25
  ActsAsTenant.current_tenant
26
26
  end
@@ -28,48 +28,48 @@ module ActsAsTenant
28
28
  end
29
29
 
30
30
  # 01/27/2014 Christian Yerena / @preth00nker
31
- # this method adds the possibility of use the domain as a possible second argument to find
32
- # the current_tenant.
31
+ # this method adds the possibility of use the domain as a possible second argument to find
32
+ # the current_tenant.
33
33
  def set_current_tenant_by_subdomain_or_domain(tenant = :account, primary_column = :subdomain, second_column = :domain )
34
34
  self.class_eval do
35
35
  cattr_accessor :tenant_class, :tenant_primary_column, :tenant_second_column
36
36
  end
37
-
37
+
38
38
  self.tenant_class = tenant.to_s.camelcase.constantize
39
39
  self.tenant_primary_column = primary_column.to_sym
40
40
  self.tenant_second_column = second_column.to_sym
41
-
41
+
42
42
  self.class_eval do
43
43
  before_filter :find_tenant_by_subdomain_or_domain
44
44
  helper_method :current_tenant
45
-
45
+
46
46
  private
47
47
  def find_tenant_by_subdomain_or_domain
48
48
  ActsAsTenant.current_tenant = tenant_class.where(tenant_primary_column => request.subdomains.last).first || tenant_class.where(tenant_second_column => request.domain).first
49
49
  end
50
-
50
+
51
51
  def current_tenant
52
52
  ActsAsTenant.current_tenant
53
53
  end
54
54
  end
55
55
  end
56
56
 
57
-
57
+
58
58
  # This method sets up a method that allows manual setting of the current_tenant. This method should
59
59
  # be used in a before_filter. In addition, a helper is setup that returns the current_tenant
60
60
  def set_current_tenant_through_filter
61
61
  self.class_eval do
62
62
  helper_method :current_tenant
63
+
64
+ private
65
+ def set_current_tenant(current_tenant_object)
66
+ ActsAsTenant.current_tenant = current_tenant_object
67
+ end
63
68
 
64
- def set_current_tenant(current_tenant_object)
65
- ActsAsTenant.current_tenant = current_tenant_object
66
- end
67
-
68
- private
69
69
  def current_tenant
70
70
  ActsAsTenant.current_tenant
71
71
  end
72
72
  end
73
73
  end
74
74
  end
75
- end
75
+ end
@@ -18,7 +18,11 @@ module ActsAsTenant
18
18
  end
19
19
 
20
20
  def self.current_tenant
21
- RequestStore.store[:current_tenant]
21
+ RequestStore.store[:current_tenant] || self.default_tenant
22
+ end
23
+
24
+ class << self
25
+ attr_accessor :default_tenant
22
26
  end
23
27
 
24
28
  def self.with_tenant(tenant, &block)
@@ -94,13 +98,15 @@ module ActsAsTenant
94
98
  # - Add a helper method to verify if a model has been scoped by AaT
95
99
  to_include = Module.new do
96
100
  define_method "#{fkey}=" do |integer|
97
- raise ActsAsTenant::Errors::TenantIsImmutable unless new_record? || send(fkey).nil? || send(fkey) == integer
98
101
  write_attribute("#{fkey}", integer)
102
+ raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil?
103
+ integer
99
104
  end
100
105
 
101
106
  define_method "#{ActsAsTenant.tenant_klass.to_s}=" do |model|
102
- raise ActsAsTenant::Errors::TenantIsImmutable unless new_record? || send(fkey).nil? || send(fkey) == integer
103
107
  super(model)
108
+ raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil?
109
+ model
104
110
  end
105
111
 
106
112
  define_method "#{ActsAsTenant.tenant_klass.to_s}" do
@@ -1,3 +1,3 @@
1
1
  module ActsAsTenant
2
- VERSION = "0.3.8"
2
+ VERSION = "0.3.9"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require 'rails/all'
2
2
  require 'database_cleaner'
3
+ require 'yaml'
3
4
 
4
5
  dbconfig = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
5
6
  ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
@@ -27,6 +27,24 @@ describe ActsAsTenant do
27
27
  it { expect {@project.account_id = @account.id + 1}.to raise_error }
28
28
  end
29
29
 
30
+ describe 'setting tenant_id to the same value should not error' do
31
+ before do
32
+ @account = Account.create!(:name => 'foo')
33
+ @project = @account.projects.create!(:name => 'bar')
34
+ end
35
+
36
+ it { expect {@project.account_id = @account.id}.not_to raise_error }
37
+ end
38
+
39
+ describe 'setting tenant_id to a string with same to_i value should not error' do
40
+ before do
41
+ @account = Account.create!(:name => 'foo')
42
+ @project = @account.projects.create!(:name => 'bar')
43
+ end
44
+
45
+ it { expect {@project.account_id = @account.id.to_s}.not_to raise_error }
46
+ end
47
+
30
48
  describe 'tenant_id should be mutable, if not already set' do
31
49
  before do
32
50
  @account = Account.create!(:name => 'foo')
@@ -291,4 +309,48 @@ describe ActsAsTenant do
291
309
  end
292
310
  end
293
311
  end
312
+
313
+ describe "ActsAsTenant.default_tenant=" do
314
+ before(:each) do
315
+ @account = Account.create!
316
+ end
317
+
318
+ after(:each) do
319
+ ActsAsTenant.default_tenant = nil
320
+ end
321
+
322
+ it "provides current_tenant" do
323
+ ActsAsTenant.default_tenant = @account
324
+ expect(ActsAsTenant.current_tenant).to eq(@account)
325
+ end
326
+
327
+ it "can be overridden by assignment" do
328
+ ActsAsTenant.default_tenant = @account
329
+ @account2 = Account.create!
330
+ ActsAsTenant.current_tenant = @account2
331
+ expect(ActsAsTenant.current_tenant).not_to eq(@account)
332
+ end
333
+
334
+ it "can be overridden by with_tenant" do
335
+ ActsAsTenant.default_tenant = @account
336
+ @account2 = Account.create!
337
+ ActsAsTenant.with_tenant @account2 do
338
+ expect(ActsAsTenant.current_tenant).to eq(@account2)
339
+ end
340
+ expect(ActsAsTenant.current_tenant).to eq(@account)
341
+ end
342
+
343
+ it "doesn't override existing current_tenant" do
344
+ @account2 = Account.create!
345
+ ActsAsTenant.current_tenant = @account2
346
+ ActsAsTenant.default_tenant = @account
347
+ expect(ActsAsTenant.current_tenant).to eq(@account2)
348
+ end
349
+
350
+ it "survives request resets" do
351
+ ActsAsTenant.default_tenant = @account
352
+ RequestStore.clear!
353
+ expect(ActsAsTenant.current_tenant).to eq(@account)
354
+ end
355
+ end
294
356
  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.3.8
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erwin Matthijssen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-10 00:00:00.000000000 Z
11
+ date: 2015-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: request_store