acts_as_tenant 0.2.9 → 0.3.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/README.md +20 -2
- data/acts_as_tenant.gemspec +4 -0
- data/lib/acts_as_tenant.rb +7 -4
- data/lib/acts_as_tenant/configuration.rb +26 -0
- data/lib/acts_as_tenant/controller_extensions.rb +5 -33
- data/lib/acts_as_tenant/errors.rb +19 -0
- data/lib/acts_as_tenant/model_extensions.rb +104 -117
- data/lib/acts_as_tenant/version.rb +1 -1
- data/spec/acts_as_tenant/configuration_spec.rb +28 -0
- data/spec/{model_extensions_spec.rb → acts_as_tenant/model_extensions_spec.rb} +97 -98
- data/spec/spec_helper.rb +3 -18
- metadata +69 -27
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ae0af3ec60e0f5aec616181f0cb84dbf819838d5
|
4
|
+
data.tar.gz: 24f836a129e42dbe7bf60aff0eb5cf3942eabc4d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5946028e86ba16bde4ed1ad232b2f89d75302111e44bcb765e144688e16f21c697d1951fae80160e5263c96c43871380e007a2ef813631278c932b2896f7e2c
|
7
|
+
data.tar.gz: 2a2f574538b2507d3837b1d6d3919acf409df8599afded9127c7d991baac5ba40f85e77f135ae1655bacce560ffe14672b462a43b909958c059ede1e17f4ca0a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
0.3.1
|
2
|
+
-----
|
3
|
+
* Added support for Rails 4
|
4
|
+
|
5
|
+
0.3.0
|
6
|
+
-----
|
7
|
+
* You can now raise an exception if a query on a scope model is made without a tenant set. Adding an initializer that sets config.require_tenant to true will accomplish this. See readme for more details.
|
8
|
+
* `ActsAsTenant.with_tenant` will now return the value of the block it evaluates instead of the original tenant. The original tenant is restored automatically.
|
9
|
+
* acts_as_tenant now raises standard errors which can be caught individually.
|
10
|
+
* `set_current_tenant_to`, which was deprecated some versions ago and could lead to weird errors, has been removed.
|
11
|
+
|
12
|
+
|
1
13
|
0.2.9
|
2
14
|
-----
|
3
15
|
* Added support for many-to-many associations (thx Nucleoid)
|
data/README.md
CHANGED
@@ -65,14 +65,19 @@ This approach is useful when running background processes for a specified tenant
|
|
65
65
|
any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
|
66
66
|
|
67
67
|
**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.
|
68
|
-
|
68
|
+
|
69
|
+
**Require tenant to be set always**
|
70
|
+
|
71
|
+
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.
|
72
|
+
|
69
73
|
Scoping your models
|
70
74
|
-------------------
|
71
75
|
class Addaccounttousers < ActiveRecord::Migration
|
72
76
|
def up
|
73
77
|
add_column :users, :account_id, :integer
|
74
78
|
end
|
75
|
-
|
79
|
+
end
|
80
|
+
|
76
81
|
class User < ActiveRecord::Base
|
77
82
|
acts_as_tenant(:account)
|
78
83
|
end
|
@@ -110,6 +115,19 @@ If you need to validate for uniqueness, chances are that you want to scope this
|
|
110
115
|
|
111
116
|
All options available to Rails' own `validates_uniqueness_of` are also available to this method.
|
112
117
|
|
118
|
+
Configuration options
|
119
|
+
---------------------
|
120
|
+
An initializer can be created to control (currently one) option in ActsAsTenant. Defaults
|
121
|
+
are shown below with sample overrides following. In `config/initializer/acts_as_tenant.rb`:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
ActsAsTenant.configure do |config|
|
125
|
+
config.require_tenant = false # true
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
* `config.require_tenant` when set to true will raise an ActsAsTenant::NoTenant error whenever a query is made without a tenant set.
|
130
|
+
|
113
131
|
Note on testing
|
114
132
|
---------------
|
115
133
|
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`.
|
data/acts_as_tenant.gemspec
CHANGED
@@ -18,9 +18,13 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
+
#add_runtime_dependency("rails")
|
22
|
+
s.add_runtime_dependency('request_store', '>= 1.0.5')
|
21
23
|
s.add_dependency('rails','>= 3.1')
|
24
|
+
#s.add_dependency('request_store', '>= 1.0.5')
|
22
25
|
|
23
26
|
s.add_development_dependency('rspec')
|
24
27
|
s.add_development_dependency('database_cleaner')
|
25
28
|
s.add_development_dependency('sqlite3')
|
29
|
+
s.add_development_dependency('debugger')
|
26
30
|
end
|
data/lib/acts_as_tenant.rb
CHANGED
@@ -3,13 +3,15 @@
|
|
3
3
|
require "active_record"
|
4
4
|
require "action_controller"
|
5
5
|
require "active_model"
|
6
|
+
require "request_store"
|
6
7
|
|
7
8
|
#$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
9
|
|
9
|
-
require "acts_as_tenant"
|
10
10
|
require "acts_as_tenant/version"
|
11
|
-
require "acts_as_tenant/
|
12
|
-
require "acts_as_tenant/
|
11
|
+
require "acts_as_tenant/errors"
|
12
|
+
require "acts_as_tenant/configuration"
|
13
|
+
require "acts_as_tenant/controller_extensions"
|
14
|
+
require "acts_as_tenant/model_extensions"
|
13
15
|
|
14
16
|
#$LOAD_PATH.shift
|
15
17
|
|
@@ -18,5 +20,6 @@ if defined?(ActiveRecord::Base)
|
|
18
20
|
ActionController::Base.extend ActsAsTenant::ControllerExtensions
|
19
21
|
end
|
20
22
|
|
21
|
-
|
23
|
+
module ActsAsTenant
|
24
|
+
end
|
22
25
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActsAsTenant
|
2
|
+
@@configuration = nil
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
@@configuration = Configuration.new
|
6
|
+
|
7
|
+
if block_given?
|
8
|
+
yield configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@@configuration || configure
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
attr_writer :require_tenant
|
20
|
+
|
21
|
+
def require_tenant
|
22
|
+
@require_tenant ||= false
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -2,29 +2,25 @@ 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
|
6
|
-
#
|
5
|
+
# it up in the tenant-model passed to the method. The method will look for the subdomain
|
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
|
-
attr_accessor :current_tenant
|
11
10
|
end
|
12
|
-
|
11
|
+
|
13
12
|
self.tenant_class = tenant.to_s.camelcase.constantize
|
14
13
|
self.tenant_column = column.to_sym
|
15
|
-
|
14
|
+
|
16
15
|
self.class_eval do
|
17
16
|
before_filter :find_tenant_by_subdomain
|
18
|
-
|
19
17
|
helper_method :current_tenant
|
20
18
|
|
21
19
|
private
|
22
20
|
def find_tenant_by_subdomain
|
23
21
|
ActsAsTenant.current_tenant = tenant_class.where(tenant_column => request.subdomains.first).first
|
24
|
-
@current_tenant_instance = ActsAsTenant.current_tenant
|
25
22
|
end
|
26
23
|
|
27
|
-
# helper method to have the current_tenant available in the controller
|
28
24
|
def current_tenant
|
29
25
|
ActsAsTenant.current_tenant
|
30
26
|
end
|
@@ -41,31 +37,7 @@ module ActsAsTenant
|
|
41
37
|
ActsAsTenant.current_tenant = current_tenant_object
|
42
38
|
end
|
43
39
|
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# this method allows manual setting of the current_tenant by passing in a tenant object
|
55
|
-
#
|
56
|
-
def set_current_tenant_to(current_tenant_object)
|
57
|
-
self.class_eval do
|
58
|
-
cattr_accessor :tenant_class
|
59
|
-
attr_accessor :current_tenant
|
60
|
-
before_filter lambda {
|
61
|
-
ActiveSupport::Deprecation.warn "set_current_tenant_to is deprecated and will be removed from Acts_as_tenant in a future releases, please use set_current_tenant_through_filter instead.", caller
|
62
|
-
@current_tenant_instance = ActsAsTenant.current_tenant = current_tenant_object
|
63
|
-
}
|
64
|
-
|
65
|
-
helper_method :current_tenant
|
66
|
-
|
67
|
-
private
|
68
|
-
# helper method to have the current_tenant available in the controller
|
40
|
+
private
|
69
41
|
def current_tenant
|
70
42
|
ActsAsTenant.current_tenant
|
71
43
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActsAsTenant
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
class ModelNotScopedByTenant < ActsAsTenant::Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class NoTenantSet < ActsAsTenant::Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class ModelNotScopedByTenant < ActsAsTenant::Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class TenantIsImmutable < ActsAsTenant::Error
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -1,117 +1,104 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def validates_uniqueness_to_tenant(fields, args ={})
|
109
|
-
raise "[ActsAsTenant] validates_uniqueness_to_tenant: no current tenant" unless respond_to?(:is_scoped_by_tenant?)
|
110
|
-
tenant_id = lambda { "#{ActsAsTenant.tenant_class.to_s.downcase}_id"}.call
|
111
|
-
args[:scope].nil? ? args[:scope] = tenant_id : args[:scope] << tenant_id
|
112
|
-
validates_uniqueness_of(fields, args)
|
113
|
-
end
|
114
|
-
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
1
|
+
module ActsAsTenant
|
2
|
+
@@tenant_klass = nil
|
3
|
+
|
4
|
+
def self.set_tenant_klass(klass)
|
5
|
+
@@tenant_klass = klass
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.tenant_klass
|
9
|
+
@@tenant_klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.fkey
|
13
|
+
"#{@@tenant_klass.to_s}_id"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.current_tenant=(tenant)
|
17
|
+
RequestStore.store[:current_tenant] = tenant
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.current_tenant
|
21
|
+
RequestStore.store[:current_tenant]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.with_tenant(tenant, &block)
|
25
|
+
if block.nil?
|
26
|
+
raise ArgumentError, "block required"
|
27
|
+
end
|
28
|
+
|
29
|
+
old_tenant = self.current_tenant
|
30
|
+
self.current_tenant = tenant
|
31
|
+
value = block.call
|
32
|
+
self.current_tenant = old_tenant
|
33
|
+
return value
|
34
|
+
end
|
35
|
+
|
36
|
+
module ModelExtensions
|
37
|
+
def self.included(base)
|
38
|
+
base.extend(ClassMethods)
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def acts_as_tenant(association = :account)
|
43
|
+
belongs_to association
|
44
|
+
ActsAsTenant.set_tenant_klass(association)
|
45
|
+
|
46
|
+
default_scope lambda {
|
47
|
+
if ActsAsTenant.configuration.require_tenant && ActsAsTenant.current_tenant.nil?
|
48
|
+
raise ActsAsTenant::Errors::NoTenantSet
|
49
|
+
end
|
50
|
+
where({ActsAsTenant.fkey => ActsAsTenant.current_tenant.id}) if ActsAsTenant.current_tenant
|
51
|
+
}
|
52
|
+
|
53
|
+
# Add the following validations to the receiving model:
|
54
|
+
# - new instances should have the tenant set
|
55
|
+
# - validate that associations belong to the tenant, currently only for belongs_to
|
56
|
+
#
|
57
|
+
before_validation Proc.new {|m|
|
58
|
+
if ActsAsTenant.current_tenant
|
59
|
+
m.send "#{association}_id=".to_sym, ActsAsTenant.current_tenant.id
|
60
|
+
end
|
61
|
+
}, :on => :create
|
62
|
+
|
63
|
+
reflect_on_all_associations.each do |a|
|
64
|
+
unless a == reflect_on_association(association) || a.macro != :belongs_to || a.options[:polymorphic]
|
65
|
+
association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
|
66
|
+
validates_each a.foreign_key.to_sym do |record, attr, value|
|
67
|
+
record.errors.add attr, "association is invalid [ActsAsTenant]" unless value.nil? || association_class.where(:id => value).present?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Dynamically generate the following methods:
|
73
|
+
# - Rewrite the accessors to make tenant immutable
|
74
|
+
# - Add a helper method to verify if a model has been scoped by AaT
|
75
|
+
#
|
76
|
+
define_method "#{ActsAsTenant.fkey}=" do |integer|
|
77
|
+
raise ActsAsTenant::Errors::TenantIsImmutable unless new_record?
|
78
|
+
write_attribute("#{ActsAsTenant.fkey}", integer)
|
79
|
+
end
|
80
|
+
|
81
|
+
define_method "#{ActsAsTenant.tenant_klass.to_s}=" do |model|
|
82
|
+
raise ActsAsTenant::Errors::TenantIsImmutable unless new_record?
|
83
|
+
super(model)
|
84
|
+
end
|
85
|
+
|
86
|
+
def scoped_by_tenant?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def validates_uniqueness_to_tenant(fields, args ={})
|
92
|
+
raise ActsAsTenant::Errors::ModelNotScopedByTenant unless respond_to?(:scoped_by_tenant?)
|
93
|
+
tenant_id = lambda { "#{ActsAsTenant.fkey}"}.call
|
94
|
+
if args[:scope]
|
95
|
+
args[:scope] = Array(args[:scope]) << tenant_id
|
96
|
+
else
|
97
|
+
args[:scope] = tenant_id
|
98
|
+
end
|
99
|
+
|
100
|
+
validates_uniqueness_of(fields, args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActsAsTenant::Configuration do
|
4
|
+
describe 'no configuration given' do
|
5
|
+
before do
|
6
|
+
ActsAsTenant.configure
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'provides defaults' do
|
10
|
+
ActsAsTenant.configuration.require_tenant.should_not be_true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'with config block' do
|
15
|
+
after do
|
16
|
+
ActsAsTenant.configure
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'stores config' do
|
20
|
+
ActsAsTenant.configure do |config|
|
21
|
+
config.require_tenant = true
|
22
|
+
end
|
23
|
+
|
24
|
+
ActsAsTenant.configuration.require_tenant.should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
# Setup the db
|
4
|
-
ActiveRecord::Schema.define(:version =>
|
4
|
+
ActiveRecord::Schema.define(:version => 1) do
|
5
5
|
create_table :accounts, :force => true do |t|
|
6
6
|
t.column :name, :string
|
7
7
|
end
|
@@ -10,43 +10,35 @@ ActiveRecord::Schema.define(:version => 2) do
|
|
10
10
|
t.column :name, :string
|
11
11
|
t.column :account_id, :integer
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
create_table :managers, :force => true do |t|
|
15
15
|
t.column :name, :string
|
16
16
|
t.column :project_id, :integer
|
17
17
|
t.column :account_id, :integer
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
create_table :tasks, :force => true do |t|
|
21
21
|
t.column :name, :string
|
22
22
|
t.column :account_id, :integer
|
23
23
|
t.column :project_id, :integer
|
24
24
|
t.column :completed, :boolean
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
create_table :countries, :force => true do |t|
|
28
28
|
t.column :name, :string
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
create_table :cities, :force => true do |t|
|
32
32
|
t.column :name, :string
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
create_table :sub_tasks, :force => true do |t|
|
36
36
|
t.column :name, :string
|
37
|
+
t.column :attribute2, :string
|
37
38
|
t.column :something_else, :integer
|
38
|
-
end
|
39
|
-
|
40
|
-
create_table :tools, :force => true do |t|
|
41
|
-
t.column :name, :string
|
42
39
|
t.column :account_id, :integer
|
43
40
|
end
|
44
41
|
|
45
|
-
create_table :managers_tools, {:force => true, id: false} do |t|
|
46
|
-
t.integer :manager_id
|
47
|
-
t.integer :tool_id
|
48
|
-
end
|
49
|
-
|
50
42
|
end
|
51
43
|
|
52
44
|
# Setup the models
|
@@ -58,21 +50,19 @@ class Project < ActiveRecord::Base
|
|
58
50
|
has_one :manager
|
59
51
|
has_many :tasks
|
60
52
|
acts_as_tenant :account
|
61
|
-
|
53
|
+
|
62
54
|
validates_uniqueness_to_tenant :name
|
63
55
|
end
|
64
56
|
|
65
57
|
class Manager < ActiveRecord::Base
|
66
58
|
belongs_to :project
|
67
|
-
has_and_belongs_to_many :tools
|
68
|
-
|
69
59
|
acts_as_tenant :account
|
70
60
|
end
|
71
61
|
|
72
62
|
class Task < ActiveRecord::Base
|
73
63
|
belongs_to :project
|
74
|
-
default_scope
|
75
|
-
|
64
|
+
default_scope -> { where(:completed => nil).order("name") }
|
65
|
+
|
76
66
|
acts_as_tenant :account
|
77
67
|
validates_uniqueness_of :name
|
78
68
|
end
|
@@ -84,12 +74,7 @@ end
|
|
84
74
|
class SubTask < ActiveRecord::Base
|
85
75
|
acts_as_tenant :account
|
86
76
|
belongs_to :something_else, :class_name => "Project"
|
87
|
-
|
88
|
-
|
89
|
-
class Tool < ActiveRecord::Base
|
90
|
-
has_and_belongs_to_many :managers
|
91
|
-
|
92
|
-
acts_as_tenant :account
|
77
|
+
validates_uniqueness_to_tenant :name, scope: :attribute2
|
93
78
|
end
|
94
79
|
|
95
80
|
# Start testing!
|
@@ -100,11 +85,11 @@ describe ActsAsTenant do
|
|
100
85
|
before { ActsAsTenant.current_tenant = :foo }
|
101
86
|
it { ActsAsTenant.current_tenant == :foo }
|
102
87
|
end
|
103
|
-
|
88
|
+
|
104
89
|
describe 'is_scoped_as_tenant should return the correct value' do
|
105
|
-
it {Project.respond_to?(:
|
90
|
+
it {Project.respond_to?(:scoped_by_tenant?).should == true}
|
106
91
|
end
|
107
|
-
|
92
|
+
|
108
93
|
describe 'Project.all should be scoped to the current tenant if set' do
|
109
94
|
before do
|
110
95
|
@account1 = Account.create!(:name => 'foo')
|
@@ -112,15 +97,15 @@ describe ActsAsTenant do
|
|
112
97
|
|
113
98
|
@project1 = @account1.projects.create!(:name => 'foobar')
|
114
99
|
@project2 = @account2.projects.create!(:name => 'baz')
|
115
|
-
|
100
|
+
|
116
101
|
ActsAsTenant.current_tenant= @account1
|
117
102
|
@projects = Project.all
|
118
103
|
end
|
119
|
-
|
104
|
+
|
120
105
|
it { @projects.length.should == 1 }
|
121
106
|
it { @projects.should == [@project1] }
|
122
107
|
end
|
123
|
-
|
108
|
+
|
124
109
|
describe 'Project.unscoped.all should return the unscoped value' do
|
125
110
|
before do
|
126
111
|
@account1 = Account.create!(:name => 'foo')
|
@@ -128,147 +113,123 @@ describe ActsAsTenant do
|
|
128
113
|
|
129
114
|
@project1 = @account1.projects.create!(:name => 'foobar')
|
130
115
|
@project2 = @account2.projects.create!(:name => 'baz')
|
131
|
-
|
116
|
+
|
132
117
|
ActsAsTenant.current_tenant= @account1
|
133
|
-
@projects = Project.unscoped
|
118
|
+
@projects = Project.unscoped
|
134
119
|
end
|
135
|
-
|
136
|
-
it { @projects.
|
120
|
+
|
121
|
+
it { @projects.count.should == 2 }
|
137
122
|
end
|
138
|
-
|
123
|
+
|
139
124
|
describe 'Associations should be correctly scoped by current tenant' do
|
140
125
|
before do
|
141
126
|
@account = Account.create!(:name => 'foo')
|
142
127
|
@project = @account.projects.create!(:name => 'foobar', :account_id => @account.id )
|
143
|
-
# the next line would normally be nearly impossible: a task assigned to a tenant project,
|
128
|
+
# the next line would normally be nearly impossible: a task assigned to a tenant project,
|
144
129
|
# but the task has no tenant assigned
|
145
|
-
@task1 = Task.create!(:name => 'no_tenant', :project => @project)
|
146
|
-
|
130
|
+
@task1 = Task.create!(:name => 'no_tenant', :project => @project)
|
131
|
+
|
147
132
|
ActsAsTenant.current_tenant = @account
|
148
133
|
@task2 = @project.tasks.create!(:name => 'baz')
|
149
134
|
@tasks = @project.tasks
|
150
135
|
end
|
151
|
-
|
136
|
+
|
152
137
|
it 'should correctly set the tenant on the task created with current_tenant set' do
|
153
138
|
@task2.account.should == @account
|
154
139
|
end
|
155
|
-
|
140
|
+
|
156
141
|
it 'should filter out the non-tenant task from the project' do
|
157
142
|
@tasks.length.should == 1
|
158
143
|
end
|
159
144
|
end
|
160
|
-
|
145
|
+
|
161
146
|
describe 'When dealing with a user defined default_scope' do
|
162
147
|
before do
|
163
148
|
@account = Account.create!(:name => 'foo')
|
164
149
|
@project1 = Project.create!(:name => 'inaccessible')
|
165
150
|
@task1 = Task.create!(:name => 'no_tenant', :project => @project1)
|
166
|
-
|
151
|
+
|
167
152
|
ActsAsTenant.current_tenant = @account
|
168
153
|
@project2 = Project.create!(:name => 'accessible')
|
169
154
|
@task2 = @project2.tasks.create!(:name => 'bar')
|
170
155
|
@task3 = @project2.tasks.create!(:name => 'baz')
|
171
156
|
@task4 = @project2.tasks.create!(:name => 'foo')
|
172
157
|
@task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
|
173
|
-
|
158
|
+
|
174
159
|
@tasks= Task.all
|
175
160
|
end
|
176
|
-
|
161
|
+
|
177
162
|
it 'should apply both the tenant scope and the user defined default_scope, including :order' do
|
178
|
-
@tasks.length.should == 3
|
179
|
-
@tasks.should == [@task2, @task3, @task4]
|
163
|
+
@tasks.length.should == 3
|
164
|
+
@tasks.should == [@task2, @task3, @task4]
|
180
165
|
end
|
181
166
|
end
|
182
|
-
|
167
|
+
|
183
168
|
describe 'tenant_id should be immutable' do
|
184
169
|
before do
|
185
170
|
@account = Account.create!(:name => 'foo')
|
186
171
|
@project = @account.projects.create!(:name => 'bar')
|
187
172
|
end
|
188
|
-
|
173
|
+
|
189
174
|
it { lambda {@project.account_id = @account.id + 1}.should raise_error }
|
190
175
|
end
|
191
|
-
|
176
|
+
|
192
177
|
describe 'Associations can only be made with in-scope objects' do
|
193
178
|
before do
|
194
179
|
@account = Account.create!(:name => 'foo')
|
195
180
|
@project1 = Project.create!(:name => 'inaccessible_project', :account_id => @account.id + 1)
|
196
|
-
|
181
|
+
|
197
182
|
ActsAsTenant.current_tenant = @account
|
198
183
|
@project2 = Project.create!(:name => 'accessible_project')
|
199
184
|
@task = @project2.tasks.create!(:name => 'bar')
|
200
185
|
end
|
201
|
-
|
186
|
+
|
202
187
|
it { @task.update_attributes(:project_id => @project1.id).should == false }
|
203
188
|
end
|
204
|
-
|
189
|
+
|
205
190
|
describe 'When using validates_uniqueness_to_tenant in a aat model' do
|
206
191
|
before do
|
207
192
|
@account = Account.create!(:name => 'foo')
|
208
193
|
ActsAsTenant.current_tenant = @account
|
209
194
|
@project1 = Project.create!(:name => 'bar')
|
210
195
|
end
|
211
|
-
|
196
|
+
|
212
197
|
it 'should not be possible to create a duplicate within the same tenant' do
|
213
|
-
|
198
|
+
Project.create(:name => 'bar').valid?.should == false
|
214
199
|
end
|
215
|
-
|
200
|
+
|
216
201
|
it 'should be possible to create a duplicate outside the tenant scope' do
|
217
|
-
|
218
|
-
ActsAsTenant.current_tenant =
|
219
|
-
|
202
|
+
account = Account.create!(:name => 'baz')
|
203
|
+
ActsAsTenant.current_tenant = account
|
204
|
+
Project.create(:name => 'bar').valid?.should == true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'applies additional scopes' do
|
208
|
+
subtask1 = SubTask.create!(:name => 'foo', :attribute2 => 'unique_scope')
|
209
|
+
SubTask.create(:name => 'foo', :attribute2 => 'another_scope').should be_valid
|
210
|
+
SubTask.create(:name => 'foo', :attribute2 => 'unique_scope').should_not be_valid
|
220
211
|
end
|
221
212
|
end
|
222
|
-
|
213
|
+
|
223
214
|
describe 'When using validates_uniqueness_of in a NON-aat model' do
|
224
215
|
before do
|
225
216
|
@city1 = City.create!(:name => 'foo')
|
226
217
|
end
|
227
218
|
it 'should not be possible to create duplicates' do
|
228
|
-
|
219
|
+
City.create(:name => 'foo').valid?.should == false
|
229
220
|
end
|
230
221
|
end
|
231
|
-
|
222
|
+
|
232
223
|
describe "It should be possible to use aliased associations" do
|
233
|
-
it {
|
224
|
+
it { SubTask.create(:name => 'foo').valid?.should == true }
|
234
225
|
end
|
235
|
-
|
226
|
+
|
236
227
|
describe "It should be possible to create and save an AaT-enabled child without it having a parent" do
|
237
228
|
@account = Account.create!(:name => 'baz')
|
238
229
|
ActsAsTenant.current_tenant = @account
|
239
230
|
Task.create(:name => 'bar').valid?.should == true
|
240
231
|
end
|
241
232
|
|
242
|
-
describe "It should be possible to use direct many-to-many associations" do
|
243
|
-
@manager = Manager.create!(:name => 'fool')
|
244
|
-
@manager.tools.new(:name => 'golden hammer')
|
245
|
-
@manager.save.should == true
|
246
|
-
end
|
247
|
-
|
248
|
-
describe "It should be possible to use direct many-to-many associations" do
|
249
|
-
@manager = Manager.create!(:name => 'fool')
|
250
|
-
@manager.tools.new(:name => 'golden hammer')
|
251
|
-
@manager.save.should == true
|
252
|
-
end
|
253
|
-
|
254
|
-
describe "When using direct many-to-many associations they are correctly scoped to the tenant" do
|
255
|
-
before do
|
256
|
-
@account1 = Account.create!(:name => 'foo')
|
257
|
-
@account2 = Account.create!(:name => 'bar')
|
258
|
-
|
259
|
-
ActsAsTenant.current_tenant= @account1
|
260
|
-
@manager1 = Manager.create!(:name => 'fool')
|
261
|
-
@tool1 = @manager1.tools.create!(:name => 'golden hammer')
|
262
|
-
|
263
|
-
ActsAsTenant.current_tenant= @account2
|
264
|
-
@manager2 = Manager.create!(:name => 'pitty')
|
265
|
-
@tool2 = @manager2.tools.create!(:name => 'golden saw')
|
266
|
-
|
267
|
-
@tools = Tool.all
|
268
|
-
end
|
269
|
-
it { @tools.should == [@tool2] }
|
270
|
-
end
|
271
|
-
|
272
233
|
describe "::with_tenant" do
|
273
234
|
it "should set current_tenant to the specified tenant inside the block" do
|
274
235
|
@account = Account.create!(:name => 'baz')
|
@@ -278,11 +239,10 @@ describe ActsAsTenant do
|
|
278
239
|
end
|
279
240
|
end
|
280
241
|
|
281
|
-
|
282
|
-
it "should return current_tenant to the previous tenant once exiting the block" do
|
242
|
+
it "should reset current_tenant to the previous tenant once exiting the block" do
|
283
243
|
@account1 = Account.create!(:name => 'foo')
|
284
244
|
@account2 = Account.create!(:name => 'bar')
|
285
|
-
|
245
|
+
|
286
246
|
ActsAsTenant.current_tenant = @account1
|
287
247
|
ActsAsTenant.with_tenant @account2 do
|
288
248
|
|
@@ -291,8 +251,47 @@ describe ActsAsTenant do
|
|
291
251
|
ActsAsTenant.current_tenant.should eq(@account1)
|
292
252
|
end
|
293
253
|
|
254
|
+
it "should return the value of the block" do
|
255
|
+
@account1 = Account.create!(:name => 'foo')
|
256
|
+
@account2 = Account.create!(:name => 'bar')
|
257
|
+
|
258
|
+
ActsAsTenant.current_tenant = @account1
|
259
|
+
value = ActsAsTenant.with_tenant @account2 do
|
260
|
+
"something"
|
261
|
+
end
|
262
|
+
|
263
|
+
value.should eq "something"
|
264
|
+
end
|
265
|
+
|
294
266
|
it "should raise an error when no block is provided" do
|
295
267
|
expect { ActsAsTenant.with_tenant(nil) }.to raise_error(ArgumentError, /block required/)
|
296
268
|
end
|
297
269
|
end
|
270
|
+
|
271
|
+
context "tenant required" do
|
272
|
+
describe "raises exception if no tenant specified" do
|
273
|
+
before do
|
274
|
+
@account1 = Account.create!(:name => 'foo')
|
275
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
276
|
+
ActsAsTenant.configuration.stub(require_tenant: true)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should raise an error when no tenant is provided" do
|
280
|
+
expect { Project.all.load }.to raise_error(ActsAsTenant::Errors::NoTenantSet)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "no tenant required" do
|
286
|
+
describe "does not raise exception if no tenant specified" do
|
287
|
+
before do
|
288
|
+
@account1 = Account.create!(:name => 'foo')
|
289
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should not raise an error when no tenant is provided" do
|
293
|
+
expect { Project.all }.to_not raise_error
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
298
297
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,30 +1,14 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
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
3
|
|
12
|
-
|
13
|
-
|
4
|
+
require 'database_cleaner'
|
5
|
+
require 'acts_as_tenant'
|
14
6
|
|
15
7
|
config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
|
16
8
|
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
17
9
|
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
18
10
|
|
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
11
|
RSpec.configure do |config|
|
27
|
-
|
28
12
|
config.before(:suite) do
|
29
13
|
DatabaseCleaner.strategy = :transaction
|
30
14
|
DatabaseCleaner.clean_with(:truncation)
|
@@ -36,6 +20,7 @@ RSpec.configure do |config|
|
|
36
20
|
|
37
21
|
config.after(:each) do
|
38
22
|
DatabaseCleaner.clean
|
23
|
+
ActsAsTenant.current_tenant = nil
|
39
24
|
end
|
40
25
|
|
41
26
|
end
|
metadata
CHANGED
@@ -1,60 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Erwin Matthijssen
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-06-24 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: request_store
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.5
|
14
27
|
- !ruby/object:Gem::Dependency
|
15
28
|
name: rails
|
16
|
-
requirement:
|
17
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
18
30
|
requirements:
|
19
|
-
- -
|
31
|
+
- - '>='
|
20
32
|
- !ruby/object:Gem::Version
|
21
33
|
version: '3.1'
|
22
34
|
type: :runtime
|
23
35
|
prerelease: false
|
24
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
25
41
|
- !ruby/object:Gem::Dependency
|
26
42
|
name: rspec
|
27
|
-
requirement:
|
28
|
-
none: false
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
29
44
|
requirements:
|
30
|
-
- -
|
45
|
+
- - '>='
|
31
46
|
- !ruby/object:Gem::Version
|
32
47
|
version: '0'
|
33
48
|
type: :development
|
34
49
|
prerelease: false
|
35
|
-
version_requirements:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
36
55
|
- !ruby/object:Gem::Dependency
|
37
56
|
name: database_cleaner
|
38
|
-
requirement:
|
39
|
-
none: false
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
40
58
|
requirements:
|
41
|
-
- -
|
59
|
+
- - '>='
|
42
60
|
- !ruby/object:Gem::Version
|
43
61
|
version: '0'
|
44
62
|
type: :development
|
45
63
|
prerelease: false
|
46
|
-
version_requirements:
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
47
69
|
- !ruby/object:Gem::Dependency
|
48
70
|
name: sqlite3
|
49
|
-
requirement:
|
50
|
-
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
|
-
- -
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: debugger
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
53
88
|
- !ruby/object:Gem::Version
|
54
89
|
version: '0'
|
55
90
|
type: :development
|
56
91
|
prerelease: false
|
57
|
-
version_requirements:
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
58
97
|
description: Integrates multi-tenancy into a Rails application in a convenient and
|
59
98
|
out-of-your way manner
|
60
99
|
email:
|
@@ -71,38 +110,41 @@ files:
|
|
71
110
|
- Rakefile
|
72
111
|
- acts_as_tenant.gemspec
|
73
112
|
- lib/acts_as_tenant.rb
|
113
|
+
- lib/acts_as_tenant/configuration.rb
|
74
114
|
- lib/acts_as_tenant/controller_extensions.rb
|
115
|
+
- lib/acts_as_tenant/errors.rb
|
75
116
|
- lib/acts_as_tenant/model_extensions.rb
|
76
117
|
- lib/acts_as_tenant/version.rb
|
77
118
|
- rails/init.rb
|
119
|
+
- spec/acts_as_tenant/configuration_spec.rb
|
120
|
+
- spec/acts_as_tenant/model_extensions_spec.rb
|
78
121
|
- spec/database.yml
|
79
|
-
- spec/model_extensions_spec.rb
|
80
122
|
- spec/spec_helper.rb
|
81
123
|
homepage: http://www.rollcallapp.com/blog
|
82
124
|
licenses: []
|
125
|
+
metadata: {}
|
83
126
|
post_install_message:
|
84
127
|
rdoc_options: []
|
85
128
|
require_paths:
|
86
129
|
- lib
|
87
130
|
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
-
none: false
|
89
131
|
requirements:
|
90
|
-
- -
|
132
|
+
- - '>='
|
91
133
|
- !ruby/object:Gem::Version
|
92
134
|
version: '0'
|
93
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
-
none: false
|
95
136
|
requirements:
|
96
|
-
- -
|
137
|
+
- - '>='
|
97
138
|
- !ruby/object:Gem::Version
|
98
139
|
version: '0'
|
99
140
|
requirements: []
|
100
141
|
rubyforge_project: acts_as_tenant
|
101
|
-
rubygems_version:
|
142
|
+
rubygems_version: 2.0.3
|
102
143
|
signing_key:
|
103
|
-
specification_version:
|
144
|
+
specification_version: 4
|
104
145
|
summary: Add multi-tenancy to Rails applications using a shared db strategy
|
105
146
|
test_files:
|
147
|
+
- spec/acts_as_tenant/configuration_spec.rb
|
148
|
+
- spec/acts_as_tenant/model_extensions_spec.rb
|
106
149
|
- spec/database.yml
|
107
|
-
- spec/model_extensions_spec.rb
|
108
150
|
- spec/spec_helper.rb
|