mongoid-multitenancy 0.4.4 → 1.0.0
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 +4 -4
- data/.rspec +1 -0
- data/.travis.yml +4 -3
- data/CHANGELOG.md +13 -0
- data/Gemfile +5 -5
- data/README.md +44 -15
- data/Rakefile +1 -1
- data/gemfiles/Gemfile.mongoid-4.0 +3 -4
- data/gemfiles/{Gemfile.mongoid-3.0 → Gemfile.mongoid-5.0} +3 -4
- data/lib/mongoid/multitenancy.rb +6 -10
- data/lib/mongoid/multitenancy/document.rb +117 -49
- data/lib/mongoid/multitenancy/validators/tenancy.rb +36 -0
- data/lib/mongoid/multitenancy/validators/tenant_uniqueness.rb +72 -0
- data/lib/mongoid/multitenancy/version.rb +2 -1
- data/mongoid-multitenancy.gemspec +8 -8
- data/spec/immutable_spec.rb +30 -18
- data/spec/indexable_spec.rb +6 -6
- data/spec/inheritance_spec.rb +11 -6
- data/spec/mandatory_spec.rb +70 -44
- data/spec/models/immutable.rb +3 -3
- data/spec/models/indexable.rb +2 -2
- data/spec/models/mandatory.rb +5 -5
- data/spec/models/mutable.rb +3 -3
- data/spec/models/optional.rb +5 -5
- data/spec/mongoid-multitenancy_spec.rb +17 -11
- data/spec/mutable_child_spec.rb +31 -17
- data/spec/mutable_spec.rb +34 -18
- data/spec/optional_spec.rb +88 -46
- data/spec/spec_helper.rb +16 -60
- data/spec/support/database_cleaner.rb +38 -0
- data/spec/support/mongoid_matchers.rb +17 -0
- data/spec/support/shared_examples.rb +81 -0
- metadata +14 -8
- data/lib/mongoid/validators/tenant_validator.rb +0 -20
- data/spec/support/mongoid.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95438e3e7adc7dc21d06a0deec16979183b37738
|
4
|
+
data.tar.gz: 61ce79abafb106d53af8dbf35c74523a04b43a31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58cf2892dd86cd822f1fc436e745e53ffd128680164226323e5e32ac328bd14db877cd990ea29e01db130e4155b205a1de1a9b6cd040c7bc0381d1dfd89dc72a
|
7
|
+
data.tar.gz: 3ac676cd7c0df46eb10a703967f9375d51674f6a61f3a8ae637ea8cd7562b6361905def86aa52269af8dfcdfc1cbd29a9e4ea2eee8665ff35a1341207247d7fa
|
data/.rspec
CHANGED
data/.travis.yml
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 1.9.3
|
4
3
|
- 2.0.0
|
5
4
|
- 2.1.0
|
5
|
+
- 2.2.0
|
6
6
|
- ruby-head
|
7
7
|
- jruby
|
8
8
|
- jruby-head
|
9
9
|
gemfile:
|
10
|
-
- gemfiles/Gemfile.mongoid-3.0
|
11
10
|
- gemfiles/Gemfile.mongoid-4.0
|
11
|
+
- gemfiles/Gemfile.mongoid-5.0
|
12
12
|
- Gemfile
|
13
13
|
services:
|
14
|
-
- mongodb
|
14
|
+
- mongodb
|
15
|
+
sudo: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## 1.0.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
* Adds support for mongoid 5
|
6
|
+
|
7
|
+
### Major Changes (Backwards Incompatible)
|
8
|
+
|
9
|
+
* Drops support for mongoid 3
|
10
|
+
|
11
|
+
* An optional tenant is now automatically set if a current tenant is defined.
|
12
|
+
|
13
|
+
* A unique constraint with an optional tenant now uses the client scoping. An item cannot be shared if another client item has the same value.
|
data/Gemfile
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'mongoid', '~>
|
3
|
+
gem 'mongoid', '~> 5.0'
|
4
4
|
|
5
5
|
gem 'rake', '~> 10.0'
|
6
6
|
|
7
7
|
group :test do
|
8
|
-
gem 'coveralls', :
|
9
|
-
gem 'rspec', '~>
|
8
|
+
gem 'coveralls', require: false
|
9
|
+
gem 'rspec', '~> 3.1'
|
10
10
|
gem 'yard', '~> 0.8'
|
11
|
-
gem 'mongoid-rspec', '~>
|
12
|
-
gem '
|
11
|
+
gem 'mongoid-rspec', '~> 3.0'
|
12
|
+
gem 'rubocop', require: false
|
13
13
|
end
|
14
14
|
|
15
15
|
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
# mongoid-multitenancy [](https://travis-ci.org/PerfectMemory/mongoid-multitenancy
|
1
|
+
# mongoid-multitenancy [](https://travis-ci.org/PerfectMemory/mongoid-multitenancy) [](https://coveralls.io/github/PerfectMemory/mongoid-multitenancy?branch=master) [](https://codeclimate.com/github/PerfectMemory/mongoid-multitenancy) [](https://gemnasium.com/PerfectMemory/mongoid-multitenancy)
|
2
2
|
|
3
|
-
mongoid-multitenancy adds the ability to scope [Mongoid](https://github.com/mongoid/mongoid) models to a tenant in a **shared database strategy**. Tenants are represented by a tenant model, such as `Client`. mongoid-multitenancy 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.
|
3
|
+
mongoid-multitenancy adds the ability to scope [Mongoid](https://github.com/mongoid/mongoid) models to a tenant in a **shared database strategy**. Tenants are represented by a tenant model, such as `Client`. mongoid-multitenancy will help you set the current tenant on each request and ensures that all 'tenant models' are always properly scoped to the current tenant: when viewing, searching and creating.
|
4
4
|
|
5
5
|
It is directly inspired by the [acts_as_tenant gem](https://github.com/ErwinM/acts_as_tenant) for Active Record.
|
6
6
|
|
7
7
|
In addition, mongoid-multitenancy:
|
8
8
|
|
9
9
|
* allows you to set the current tenant
|
10
|
-
* redefines some mongoid functions like `index`, `validates_with` and `delete_all` to take in account the multitenancy
|
11
10
|
* allows shared items between the tenants
|
12
11
|
* allows you to define an immutable tenant field once it is persisted
|
13
|
-
* is thread safe
|
12
|
+
* is thread safe
|
13
|
+
* redefines some mongoid functions like `index`, `validates_with` and `delete_all` to take in account the multitenancy.
|
14
14
|
|
15
15
|
Installation
|
16
16
|
===============
|
@@ -30,7 +30,7 @@ Or install it yourself as:
|
|
30
30
|
Usage
|
31
31
|
===============
|
32
32
|
|
33
|
-
There are two steps
|
33
|
+
There are two steps to add multi-tenancy to your app with mongoid-multitenancy:
|
34
34
|
|
35
35
|
1. setting the current tenant and
|
36
36
|
2. scoping your models.
|
@@ -87,7 +87,8 @@ The association passed to the `tenant` function must be valid.
|
|
87
87
|
|
88
88
|
* :optional : set to true when the tenant is optional (default value is `false`)
|
89
89
|
* :immutable : set to true when the tenant field is immutable (default value is `true`)
|
90
|
-
* :
|
90
|
+
* :full_indexes : set to true to add the tenant field automatically to all the indexes (default value is `true`)
|
91
|
+
* :class_name, etc. : all the other options will be passed to the mongoid relation (belongs_to)
|
91
92
|
|
92
93
|
Some examples to illustrate this behavior:
|
93
94
|
|
@@ -110,7 +111,9 @@ article.valid? # => false
|
|
110
111
|
|
111
112
|
**Optional tenant**
|
112
113
|
|
113
|
-
When setting an optional tenant, for example to allow shared instances between all the tenants, the default scope will return both the tenant and the free-tenant items. That means that using `Article.delete_all` or `Article.destroy_all` will **remove the shared items too**.
|
114
|
+
When setting an optional tenant, for example to allow shared instances between all the tenants, the default scope will return both the tenant and the free-tenant items. That means that using `Article.delete_all` or `Article.destroy_all` will **remove the shared items too**.
|
115
|
+
|
116
|
+
Note: if a current tenant is set and you want to mark the item shared, you must explicitly set the tenant relation to nil after the initialization.
|
114
117
|
|
115
118
|
```ruby
|
116
119
|
class Article
|
@@ -125,11 +128,13 @@ end
|
|
125
128
|
Mongoid::Multitenancy.with_tenant(client_instance) do
|
126
129
|
Article.all # => all articles where client_id.in [50ca04b86c82bfc125000025, nil]
|
127
130
|
article = Article.new(:title => 'New article')
|
128
|
-
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id:
|
131
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id: 50ca04b86c82bfc125000025>
|
129
132
|
|
130
|
-
# tenant needs to be set manually
|
131
|
-
article
|
132
|
-
article.save => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id: 50ca04b86c82bfc125000025>
|
133
|
+
# tenant needs to be set manually to nil
|
134
|
+
article = Article.new(:title => 'New article', :client => nil)
|
135
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id: 50ca04b86c82bfc125000025>
|
136
|
+
article.tenant = nil
|
137
|
+
article.save => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id: nil>
|
133
138
|
end
|
134
139
|
```
|
135
140
|
|
@@ -156,19 +161,43 @@ Setting the current_tenant yourself requires you to use a before_filter to set t
|
|
156
161
|
Mongoid Uniqueness validators
|
157
162
|
-------------------
|
158
163
|
|
159
|
-
mongoid-multitenancy
|
160
|
-
|
164
|
+
mongoid-multitenancy brings a TenantUniqueness validator that will, depending on the tenant options, check that your uniqueness
|
165
|
+
constraints are respected:
|
166
|
+
|
167
|
+
* When used with a *mandatory* tenant, the uniqueness constraint is scoped to the current client.
|
168
|
+
|
169
|
+
In the following case, 2 articles can have the same slug if they belongs to 2 different clients.
|
161
170
|
|
162
171
|
```ruby
|
163
172
|
class Article
|
164
173
|
include Mongoid::Document
|
165
174
|
include Mongoid::Multitenancy::Document
|
166
175
|
|
167
|
-
tenant
|
176
|
+
tenant :client
|
177
|
+
|
178
|
+
field :slug
|
179
|
+
|
180
|
+
validates_tenant_uniqueness_of :slug
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
* When used with an *optional* tenant, the uniqueness constraint is not scoped if the item is shared, but is
|
185
|
+
scoped to the client new item otherwise. Note that a private item cannot have the the value if a shared item
|
186
|
+
already uses it.
|
187
|
+
|
188
|
+
In the following case, 2 private articles can have the same slug if they belongs to 2 different clients. But if a shared
|
189
|
+
article has the slug "slugA", no client will be able to use that slug again, like a standard validates_uniqueness_of does.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class Article
|
193
|
+
include Mongoid::Document
|
194
|
+
include Mongoid::Multitenancy::Document
|
195
|
+
|
196
|
+
tenant :client, optional: true
|
168
197
|
|
169
198
|
field :slug
|
170
199
|
|
171
|
-
|
200
|
+
validates_tenant_uniqueness_of :slug
|
172
201
|
end
|
173
202
|
```
|
174
203
|
|
data/Rakefile
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'mongoid', '~> 4.0
|
3
|
+
gem 'mongoid', '~> 4.0'
|
4
4
|
|
5
5
|
gem 'rake', '~> 10.0'
|
6
6
|
|
7
7
|
group :test do
|
8
8
|
gem 'coveralls', :require => false
|
9
|
-
gem 'rspec', '~>
|
9
|
+
gem 'rspec', '~> 3.1'
|
10
10
|
gem 'yard', '~> 0.8'
|
11
|
-
gem 'mongoid-rspec', '~> 1
|
12
|
-
gem 'database_cleaner', '~> 1.0'
|
11
|
+
gem 'mongoid-rspec', '~> 2.1'
|
13
12
|
end
|
14
13
|
|
15
14
|
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
@@ -1,15 +1,14 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'mongoid', '~>
|
3
|
+
gem 'mongoid', '~> 5.0'
|
4
4
|
|
5
5
|
gem 'rake', '~> 10.0'
|
6
6
|
|
7
7
|
group :test do
|
8
8
|
gem 'coveralls', :require => false
|
9
|
-
gem 'rspec', '~>
|
9
|
+
gem 'rspec', '~> 3.1'
|
10
10
|
gem 'yard', '~> 0.8'
|
11
|
-
gem 'mongoid-rspec', '~>
|
12
|
-
gem 'database_cleaner', '~> 1.0'
|
11
|
+
gem 'mongoid-rspec', '~> 3.0'
|
13
12
|
end
|
14
13
|
|
15
14
|
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
data/lib/mongoid/multitenancy.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'mongoid'
|
2
|
+
require 'mongoid/multitenancy/document'
|
3
|
+
require 'mongoid/multitenancy/version'
|
4
|
+
require 'mongoid/multitenancy/validators/tenancy'
|
5
|
+
require 'mongoid/multitenancy/validators/tenant_uniqueness'
|
5
6
|
|
6
7
|
module Mongoid
|
7
8
|
module Multitenancy
|
8
9
|
class << self
|
9
10
|
|
10
|
-
# Returns true if using Mongoid 4
|
11
|
-
def mongoid4?
|
12
|
-
Mongoid::VERSION.start_with? '4'
|
13
|
-
end
|
14
|
-
|
15
11
|
# Set the current tenant. Make it Thread aware
|
16
12
|
def current_tenant=(tenant)
|
17
13
|
Thread.current[:current_tenant] = tenant
|
@@ -25,7 +21,7 @@ module Mongoid
|
|
25
21
|
# Affects a tenant temporary for a block execution
|
26
22
|
def with_tenant(tenant, &block)
|
27
23
|
if block.nil?
|
28
|
-
raise ArgumentError,
|
24
|
+
raise ArgumentError, 'block required'
|
29
25
|
end
|
30
26
|
|
31
27
|
old_tenant = self.current_tenant
|
@@ -6,64 +6,88 @@ module Mongoid
|
|
6
6
|
module ClassMethods
|
7
7
|
attr_accessor :tenant_field, :tenant_options
|
8
8
|
|
9
|
+
# List of authorized options
|
9
10
|
MULTITENANCY_OPTIONS = [:optional, :immutable, :full_indexes, :index]
|
10
11
|
|
12
|
+
# Defines the tenant field for the document.
|
13
|
+
#
|
14
|
+
# @example Define a tenant.
|
15
|
+
# tenant :client, optional: false, immutable: true, full_indexes: true
|
16
|
+
#
|
17
|
+
# @param [ Symbol ] name The name of the relation.
|
18
|
+
# @param [ Hash ] options The relation options.
|
19
|
+
# All the belongs_to options are allowed plus the following ones:
|
20
|
+
#
|
21
|
+
# @option options [ Boolean ] :full_indexes If true the tenant field
|
22
|
+
# will be added for each index.
|
23
|
+
# @option options [ Boolean ] :immutable If true changing the tenant
|
24
|
+
# wil raise an Exception.
|
25
|
+
# @option options [ Boolean ] :optional If true allow the document
|
26
|
+
# to be shared among all the tenants.
|
27
|
+
#
|
28
|
+
# @return [ Field ] The generated field
|
11
29
|
def tenant(association = :account, options = {})
|
12
|
-
options = { full_indexes: true, immutable: true }.merge(options)
|
30
|
+
options = { full_indexes: true, immutable: true }.merge!(options)
|
31
|
+
assoc_options, multitenant_options = build_options(options)
|
13
32
|
|
14
33
|
# Setup the association between the class and the tenant class
|
15
|
-
belongs_to association,
|
34
|
+
belongs_to association, assoc_options
|
16
35
|
|
17
36
|
# Get the tenant model and its foreign key
|
18
37
|
self.tenant_field = reflect_on_association(association).foreign_key
|
19
|
-
self.tenant_options =
|
38
|
+
self.tenant_options = multitenant_options
|
20
39
|
|
21
40
|
# Validates the tenant field
|
22
|
-
|
41
|
+
validates_tenancy_of tenant_field, multitenant_options
|
23
42
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
where(self.tenant_field.to_sym.in => [Multitenancy.current_tenant.id, nil])
|
29
|
-
else
|
30
|
-
where(self.tenant_field => Multitenancy.current_tenant.id)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
where(nil)
|
34
|
-
end
|
35
|
-
}
|
36
|
-
|
37
|
-
self.define_singleton_method(:inherited) do |child|
|
38
|
-
child.tenant association, options
|
39
|
-
super(child)
|
40
|
-
end
|
41
|
-
|
42
|
-
if options[:index]
|
43
|
-
index({self.tenant_field => 1}, { background: true })
|
44
|
-
end
|
43
|
+
define_default_scope
|
44
|
+
define_initializer association
|
45
|
+
define_inherited association, options
|
46
|
+
define_index if multitenant_options[:index]
|
45
47
|
end
|
46
48
|
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
49
|
+
# Validates whether or not a field is unique against the documents in the
|
50
|
+
# database.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
#
|
54
|
+
# class Person
|
55
|
+
# include Mongoid::Document
|
56
|
+
# include Mongoid::Multitenancy::Document
|
57
|
+
# field :title
|
58
|
+
#
|
59
|
+
# validates_tenant_uniqueness_of :title
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @param [ Array ] *args The arguments to pass to the validator.
|
63
|
+
def validates_tenant_uniqueness_of(*args)
|
64
|
+
validates_with(TenantUniquenessValidator, _merge_attributes(args))
|
65
|
+
end
|
60
66
|
|
61
|
-
|
67
|
+
# Validates whether or not a tenant field is correct.
|
68
|
+
#
|
69
|
+
# @example Define the tenant validator
|
70
|
+
#
|
71
|
+
# class Person
|
72
|
+
# include Mongoid::Document
|
73
|
+
# include Mongoid::Multitenancy::Document
|
74
|
+
# field :title
|
75
|
+
# tenant :client
|
76
|
+
#
|
77
|
+
# validates_tenant_of :client
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @param [ Array ] *args The arguments to pass to the validator.
|
81
|
+
def validates_tenancy_of(*args)
|
82
|
+
validates_with(TenancyValidator, _merge_attributes(args))
|
62
83
|
end
|
63
84
|
|
64
85
|
# Redefine 'index' to include the tenant field in first position
|
65
86
|
def index(spec, options = nil)
|
66
|
-
|
87
|
+
if tenant_options[:full_indexes]
|
88
|
+
spec = { tenant_field => 1 }.merge(spec)
|
89
|
+
end
|
90
|
+
|
67
91
|
super(spec, options)
|
68
92
|
end
|
69
93
|
|
@@ -74,24 +98,68 @@ module Mongoid
|
|
74
98
|
|
75
99
|
private
|
76
100
|
|
77
|
-
|
78
|
-
|
101
|
+
# @private
|
102
|
+
def build_options(options)
|
103
|
+
assoc_options = {}
|
104
|
+
multitenant_options = {}
|
79
105
|
|
80
106
|
options.each do |k, v|
|
81
|
-
|
107
|
+
if MULTITENANCY_OPTIONS.include?(k)
|
108
|
+
multitenant_options[k] = v
|
109
|
+
else
|
110
|
+
assoc_options[k] = v
|
111
|
+
end
|
82
112
|
end
|
83
113
|
|
84
|
-
|
114
|
+
[assoc_options, multitenant_options]
|
85
115
|
end
|
86
116
|
|
87
|
-
|
88
|
-
|
117
|
+
# @private
|
118
|
+
#
|
119
|
+
# Define the after_initialize
|
120
|
+
def define_initializer(association)
|
121
|
+
# Apply the default value when the default scope is complex (optional tenant)
|
122
|
+
after_initialize lambda {
|
123
|
+
if Multitenancy.current_tenant && send(association.to_sym).nil?
|
124
|
+
send "#{association}=".to_sym, Multitenancy.current_tenant
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
89
128
|
|
90
|
-
|
91
|
-
|
129
|
+
# @private
|
130
|
+
#
|
131
|
+
# Define the inherited method
|
132
|
+
def define_inherited(association, options)
|
133
|
+
define_singleton_method(:inherited) do |child|
|
134
|
+
child.tenant association, options
|
135
|
+
super(child)
|
92
136
|
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @private
|
140
|
+
#
|
141
|
+
# Set the default scope
|
142
|
+
def define_default_scope
|
143
|
+
# Set the default_scope to scope to current tenant
|
144
|
+
default_scope lambda {
|
145
|
+
if Multitenancy.current_tenant
|
146
|
+
tenant_id = Multitenancy.current_tenant.id
|
147
|
+
if tenant_options[:optional]
|
148
|
+
where(tenant_field.to_sym.in => [tenant_id, nil])
|
149
|
+
else
|
150
|
+
where(tenant_field => tenant_id)
|
151
|
+
end
|
152
|
+
else
|
153
|
+
where(nil)
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
93
157
|
|
94
|
-
|
158
|
+
# @private
|
159
|
+
#
|
160
|
+
# Create the index
|
161
|
+
def define_index
|
162
|
+
index({ tenant_field => 1 }, background: true)
|
95
163
|
end
|
96
164
|
end
|
97
165
|
end
|