mongoid-multitenancy 1.1.3 → 2.0.3
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/.travis.yml +4 -6
- data/CHANGELOG.md +34 -1
- data/Gemfile +4 -6
- data/README.md +51 -26
- data/gemfiles/Gemfile.mongoid-6 +14 -0
- data/gemfiles/Gemfile.mongoid-7 +14 -0
- data/lib/mongoid/multitenancy.rb +7 -6
- data/lib/mongoid/multitenancy/document.rb +11 -7
- data/lib/mongoid/multitenancy/validators/tenancy.rb +4 -4
- data/lib/mongoid/multitenancy/validators/tenant_uniqueness.rb +4 -10
- data/lib/mongoid/multitenancy/version.rb +1 -1
- data/mongoid-multitenancy.gemspec +3 -3
- data/spec/immutable_spec.rb +1 -1
- data/spec/indexable_spec.rb +4 -6
- data/spec/mandatory_spec.rb +12 -1
- data/spec/models/account.rb +1 -1
- data/spec/models/immutable.rb +4 -4
- data/spec/models/indexable.rb +2 -2
- data/spec/models/mandatory.rb +3 -3
- data/spec/models/mutable.rb +4 -4
- data/spec/models/mutable_child.rb +3 -3
- data/spec/models/no_scopable.rb +11 -0
- data/spec/models/optional.rb +2 -2
- data/spec/models/optional_exclude.rb +15 -0
- data/spec/mongoid-multitenancy_spec.rb +10 -1
- data/spec/mutable_child_spec.rb +1 -2
- data/spec/mutable_spec.rb +1 -1
- data/spec/optional_exclude_spec.rb +71 -0
- data/spec/optional_spec.rb +2 -3
- data/spec/scopable_spec.rb +29 -0
- data/spec/spec_helper.rb +2 -7
- data/spec/support/shared_examples.rb +8 -9
- metadata +21 -8
- data/gemfiles/Gemfile.mongoid-4.0 +0 -16
- data/gemfiles/Gemfile.mongoid-5.0 +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57b6a36dd90e26ab59298efdd32bfeb5b92a1bfd
|
4
|
+
data.tar.gz: da726e93d69c677784b0bb5213d21bda3a77af3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b11cda4238d5ef849b59ce910c70136692e04680c764159c128cf38eea5fd89851e8d9d728a3773edbf27ed0013d9fbc2e2777babe2b977c4bcab160198ecca2
|
7
|
+
data.tar.gz: bf624d27f91d4ac108f811445eaa9fbbb4837e88427cc4a5eb7d810c9adbb19f6310f7a94ac84592560f371ad9ed666999498a7395cc562f495d845be724880e
|
data/.travis.yml
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.
|
4
|
-
- 2.1.0
|
5
|
-
- 2.2.0
|
3
|
+
- 2.2.2
|
6
4
|
- 2.3.0
|
7
|
-
-
|
5
|
+
- 2.4.1
|
8
6
|
gemfile:
|
9
|
-
- gemfiles/Gemfile.mongoid-
|
10
|
-
- gemfiles/Gemfile.mongoid-
|
7
|
+
- gemfiles/Gemfile.mongoid-6
|
8
|
+
- gemfiles/Gemfile.mongoid-7
|
11
9
|
- Gemfile
|
12
10
|
services:
|
13
11
|
- mongodb
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [2.0.3] - 2020-06-25
|
8
|
+
### Fixed
|
9
|
+
|
10
|
+
* Full Support of mongoid 7
|
11
|
+
|
12
|
+
## [2.0.2] - 2018-08-23
|
13
|
+
### Added
|
14
|
+
|
15
|
+
* Support of mongoid 7
|
16
|
+
|
17
|
+
## [2.0.1] - 2017-12-14
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
* Add ensure block in method with_tenant
|
21
|
+
|
22
|
+
## [2.0] - 2017-07-21
|
23
|
+
### New Features
|
24
|
+
|
25
|
+
* Add support for mongoid 6
|
26
|
+
* Remove support for mongoid 4 & 5
|
27
|
+
|
28
|
+
## 1.2
|
29
|
+
|
30
|
+
### New Features
|
31
|
+
|
32
|
+
* Add *exclude_shared* option for the TenantUniquenessValidator
|
33
|
+
|
1
34
|
## 1.1
|
2
35
|
|
3
36
|
### New Features
|
@@ -12,7 +45,7 @@
|
|
12
45
|
|
13
46
|
### New Features
|
14
47
|
|
15
|
-
*
|
48
|
+
* Add support for mongoid 5
|
16
49
|
|
17
50
|
### Major Changes (Backwards Incompatible)
|
18
51
|
|
data/Gemfile
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem '
|
4
|
-
|
5
|
-
gem 'rake', '~> 10.0'
|
3
|
+
gem 'rake'
|
6
4
|
|
7
5
|
group :test do
|
8
|
-
gem 'database_cleaner'
|
6
|
+
gem 'database_cleaner'
|
9
7
|
gem 'coveralls', require: false
|
10
8
|
gem 'rspec', '~> 3.1'
|
11
|
-
gem 'yard'
|
12
|
-
gem 'mongoid-rspec', '
|
9
|
+
gem 'yard'
|
10
|
+
gem 'mongoid-rspec', git: 'https://github.com/mongoid-rspec/mongoid-rspec.git'
|
13
11
|
gem 'rubocop', require: false
|
14
12
|
end
|
15
13
|
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# mongoid-multitenancy [](https://travis-ci.org/PerfectMemory/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)
|
2
2
|
|
3
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
|
|
@@ -12,12 +12,17 @@ In addition, mongoid-multitenancy:
|
|
12
12
|
* is thread safe
|
13
13
|
* redefines some mongoid functions like `index`, `validates_with` and `delete_all` to take in account the multitenancy.
|
14
14
|
|
15
|
+
Compatibility
|
16
|
+
===============
|
17
|
+
|
18
|
+
mongoid-multitenancy 2.0 is compatible with mongoid 6/7. For mongoid 4/5 compatiblity, use mongoid-multitenancy 1.2.
|
19
|
+
|
15
20
|
Installation
|
16
21
|
===============
|
17
22
|
|
18
23
|
Add this line to your application's Gemfile:
|
19
24
|
|
20
|
-
gem 'mongoid-multitenancy'
|
25
|
+
gem 'mongoid-multitenancy', '~> 2.0'
|
21
26
|
|
22
27
|
And then execute:
|
23
28
|
|
@@ -74,7 +79,7 @@ class Article
|
|
74
79
|
include Mongoid::Document
|
75
80
|
include Mongoid::Multitenancy::Document
|
76
81
|
|
77
|
-
tenant(:
|
82
|
+
tenant(:tenant)
|
78
83
|
|
79
84
|
field :title, :type => String
|
80
85
|
end
|
@@ -85,10 +90,14 @@ The association passed to the `tenant` function must be valid.
|
|
85
90
|
|
86
91
|
`tenant` accepts several options:
|
87
92
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
93
|
+
| Option | Default | Description |
|
94
|
+
| ------------- | ------------- | ------------- |
|
95
|
+
| :optional | false | set to true when the tenant is optional |
|
96
|
+
| :immutable | true | set to true when the tenant field is immutable |
|
97
|
+
| :full_indexes | true | set to true to add the tenant field automatically to all the indexes |
|
98
|
+
| :index | false | set to true to define an index for the tenant field |
|
99
|
+
| :scopes | true | set to true to define scopes :shared and :unshared |
|
100
|
+
| :class_name, etc. | | all the other options will be passed to the mongoid relation (belongs_to) |
|
92
101
|
|
93
102
|
Some examples to illustrate this behavior:
|
94
103
|
|
@@ -97,15 +106,15 @@ Some examples to illustrate this behavior:
|
|
97
106
|
Mongoid::Multitenancy.current_tenant = Client.find_by(:name => 'Perfect Memory') # => <#Client _id:50ca04b86c82bfc125000025, :name: "Perfect Memory">
|
98
107
|
|
99
108
|
# All searches are scoped by the tenant, the following searches will only return objects belonging to the current client.
|
100
|
-
Article.all # => all articles where
|
109
|
+
Article.all # => all articles where tenant_id => 50ca04b86c82bfc125000025
|
101
110
|
|
102
111
|
# New objects are scoped to the current tenant
|
103
112
|
article = Article.new(:title => 'New blog')
|
104
|
-
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog',
|
113
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
105
114
|
|
106
115
|
# It can make the tenant field immutable once it is persisted to avoid inconsistency
|
107
116
|
article.persisted? # => true
|
108
|
-
article.
|
117
|
+
article.tenant = another_client
|
109
118
|
article.valid? # => false
|
110
119
|
```
|
111
120
|
|
@@ -120,21 +129,21 @@ class Article
|
|
120
129
|
include Mongoid::Document
|
121
130
|
include Mongoid::Multitenancy::Document
|
122
131
|
|
123
|
-
tenant(:
|
132
|
+
tenant(:tenant, optional: true)
|
124
133
|
|
125
134
|
field :title, :type => String
|
126
135
|
end
|
127
136
|
|
128
137
|
Mongoid::Multitenancy.with_tenant(client_instance) do
|
129
|
-
Article.all # => all articles where
|
138
|
+
Article.all # => all articles where tenant_id.in [50ca04b86c82bfc125000025, nil]
|
130
139
|
article = Article.new(:title => 'New article')
|
131
|
-
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog',
|
140
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
132
141
|
|
133
142
|
# tenant needs to be set manually to nil
|
134
|
-
article = Article.new(:title => 'New article', :
|
135
|
-
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog',
|
143
|
+
article = Article.new(:title => 'New article', :tenant => nil)
|
144
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
136
145
|
article.tenant = nil
|
137
|
-
article.save => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog',
|
146
|
+
article.save => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: nil>
|
138
147
|
end
|
139
148
|
```
|
140
149
|
|
@@ -173,7 +182,7 @@ class Article
|
|
173
182
|
include Mongoid::Document
|
174
183
|
include Mongoid::Multitenancy::Document
|
175
184
|
|
176
|
-
tenant :
|
185
|
+
tenant :tenant
|
177
186
|
|
178
187
|
field :slug
|
179
188
|
|
@@ -181,19 +190,19 @@ class Article
|
|
181
190
|
end
|
182
191
|
```
|
183
192
|
|
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
|
186
|
-
already uses it.
|
193
|
+
* When used with an *optional* tenant, the uniqueness constraint by default is not scoped if the item is shared, but is
|
194
|
+
scoped to the client new item otherwise. Note that by default in that case a private item cannot have a value if a shared item
|
195
|
+
already uses it. You can change that behaviour by setting the option `exclude_shared` to `true`.
|
187
196
|
|
188
197
|
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.
|
198
|
+
article has the slug "slugA", no client will be able to use that slug again, like a standard `validates_uniqueness_of` does.
|
190
199
|
|
191
200
|
```ruby
|
192
201
|
class Article
|
193
202
|
include Mongoid::Document
|
194
203
|
include Mongoid::Multitenancy::Document
|
195
204
|
|
196
|
-
tenant :
|
205
|
+
tenant :tenant, optional: true
|
197
206
|
|
198
207
|
field :slug
|
199
208
|
|
@@ -201,6 +210,22 @@ class Article
|
|
201
210
|
end
|
202
211
|
```
|
203
212
|
|
213
|
+
In the following case, 2 private articles can have the same slug if they belongs to 2 different clients even if a shared
|
214
|
+
article already uses that same slug, like a `validates_uniqueness_of scope: :tenant` does.
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
class Article
|
218
|
+
include Mongoid::Document
|
219
|
+
include Mongoid::Multitenancy::Document
|
220
|
+
|
221
|
+
tenant :tenant, optional: true
|
222
|
+
|
223
|
+
field :slug
|
224
|
+
|
225
|
+
validates_tenant_uniqueness_of :slug, exclude_shared: true
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
204
229
|
Mongoid indexes
|
205
230
|
-------------------
|
206
231
|
|
@@ -210,14 +235,14 @@ To create a single index on the tenant field, you can use the option `index: tru
|
|
210
235
|
|
211
236
|
On the example below, only one indexe will be created:
|
212
237
|
|
213
|
-
* { 'title_id' => 1, '
|
238
|
+
* { 'title_id' => 1, 'tenant_id' => 1 }
|
214
239
|
|
215
240
|
```ruby
|
216
241
|
class Article
|
217
242
|
include Mongoid::Document
|
218
243
|
include Mongoid::Multitenancy::Document
|
219
244
|
|
220
|
-
tenant :
|
245
|
+
tenant :tenant, full_indexes: true
|
221
246
|
|
222
247
|
field :title
|
223
248
|
|
@@ -227,7 +252,7 @@ end
|
|
227
252
|
|
228
253
|
On the example below, 2 indexes will be created:
|
229
254
|
|
230
|
-
* { '
|
255
|
+
* { 'tenant_id' => 1 }
|
231
256
|
* { 'title_id' => 1 }
|
232
257
|
|
233
258
|
```ruby
|
@@ -235,7 +260,7 @@ class Article
|
|
235
260
|
include Mongoid::Document
|
236
261
|
include Mongoid::Multitenancy::Document
|
237
262
|
|
238
|
-
tenant :
|
263
|
+
tenant :tenant, index: true
|
239
264
|
|
240
265
|
field :title
|
241
266
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'mongoid', '~> 6.0'
|
4
|
+
|
5
|
+
gem 'rake'
|
6
|
+
|
7
|
+
group :test do
|
8
|
+
gem 'database_cleaner'
|
9
|
+
gem 'coveralls', require: false
|
10
|
+
gem 'rspec', '~> 3.1'
|
11
|
+
gem 'yard'
|
12
|
+
gem 'mongoid-rspec', git: 'https://github.com/mongoid-rspec/mongoid-rspec.git'
|
13
|
+
gem 'rubocop', require: false
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'mongoid', '~> 7.0'
|
4
|
+
|
5
|
+
gem 'rake'
|
6
|
+
|
7
|
+
group :test do
|
8
|
+
gem 'database_cleaner'
|
9
|
+
gem 'coveralls', require: false
|
10
|
+
gem 'rspec', '~> 3.1'
|
11
|
+
gem 'yard'
|
12
|
+
gem 'mongoid-rspec', git: 'https://github.com/mongoid-rspec/mongoid-rspec.git'
|
13
|
+
gem 'rubocop', require: false
|
14
|
+
end
|
data/lib/mongoid/multitenancy.rb
CHANGED
@@ -7,7 +7,6 @@ require 'mongoid/multitenancy/validators/tenant_uniqueness'
|
|
7
7
|
module Mongoid
|
8
8
|
module Multitenancy
|
9
9
|
class << self
|
10
|
-
|
11
10
|
# Set the current tenant. Make it Thread aware
|
12
11
|
def current_tenant=(tenant)
|
13
12
|
Thread.current[:current_tenant] = tenant
|
@@ -22,11 +21,13 @@ module Mongoid
|
|
22
21
|
def with_tenant(tenant, &block)
|
23
22
|
raise ArgumentError, 'block required' if block.nil?
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
begin
|
25
|
+
old_tenant = current_tenant
|
26
|
+
self.current_tenant = tenant
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
self.current_tenant = old_tenant
|
30
|
+
end
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -7,7 +7,7 @@ module Mongoid
|
|
7
7
|
attr_accessor :tenant_field, :tenant_options
|
8
8
|
|
9
9
|
# List of authorized options
|
10
|
-
MULTITENANCY_OPTIONS = [:optional, :immutable, :full_indexes, :index]
|
10
|
+
MULTITENANCY_OPTIONS = [:optional, :immutable, :full_indexes, :index, :scopes].freeze
|
11
11
|
|
12
12
|
# Defines the tenant field for the document.
|
13
13
|
#
|
@@ -24,10 +24,13 @@ module Mongoid
|
|
24
24
|
# wil raise an Exception.
|
25
25
|
# @option options [ Boolean ] :optional If true allow the document
|
26
26
|
# to be shared among all the tenants.
|
27
|
-
#
|
27
|
+
# @option options [ Boolean ] :index If true build an index for
|
28
|
+
# the tenant field itself.
|
29
|
+
# @option options [ Boolean ] :scopes If true create scopes :shared
|
30
|
+
# and :unshared.
|
28
31
|
# @return [ Field ] The generated field
|
29
32
|
def tenant(association = :account, options = {})
|
30
|
-
options = { full_indexes: true, immutable: true }.merge!(options)
|
33
|
+
options = { full_indexes: true, immutable: true, scopes: true }.merge!(options)
|
31
34
|
assoc_options, multitenant_options = build_options(options)
|
32
35
|
|
33
36
|
# Setup the association between the class and the tenant class
|
@@ -40,7 +43,7 @@ module Mongoid
|
|
40
43
|
# Validates the tenant field
|
41
44
|
validates_tenancy_of tenant_field, multitenant_options
|
42
45
|
|
43
|
-
define_scopes
|
46
|
+
define_scopes if multitenant_options[:scopes]
|
44
47
|
define_initializer association
|
45
48
|
define_inherited association, options
|
46
49
|
define_index if multitenant_options[:index]
|
@@ -92,7 +95,7 @@ module Mongoid
|
|
92
95
|
end
|
93
96
|
|
94
97
|
# Redefine 'delete_all' to take in account the default scope
|
95
|
-
def delete_all(conditions =
|
98
|
+
def delete_all(conditions = {})
|
96
99
|
scoped.where(conditions).delete
|
97
100
|
end
|
98
101
|
|
@@ -106,6 +109,7 @@ module Mongoid
|
|
106
109
|
options.each do |k, v|
|
107
110
|
if MULTITENANCY_OPTIONS.include?(k)
|
108
111
|
multitenant_options[k] = v
|
112
|
+
assoc_options[k] = v if k == :optional
|
109
113
|
else
|
110
114
|
assoc_options[k] = v
|
111
115
|
end
|
@@ -131,7 +135,7 @@ module Mongoid
|
|
131
135
|
# Define the inherited method
|
132
136
|
def define_inherited(association, options)
|
133
137
|
define_singleton_method(:inherited) do |child|
|
134
|
-
child.tenant association, options
|
138
|
+
child.tenant association, options.merge(scopes: false)
|
135
139
|
super(child)
|
136
140
|
end
|
137
141
|
end
|
@@ -150,7 +154,7 @@ module Mongoid
|
|
150
154
|
where(tenant_field => tenant_id)
|
151
155
|
end
|
152
156
|
else
|
153
|
-
|
157
|
+
all
|
154
158
|
end
|
155
159
|
}
|
156
160
|
|
@@ -16,18 +16,18 @@ module Mongoid
|
|
16
16
|
def validate_each(object, attribute, value)
|
17
17
|
# Immutable Check
|
18
18
|
if options[:immutable]
|
19
|
-
if object.send(:attribute_changed?, attribute)
|
19
|
+
if object.send(:attribute_changed?, attribute) && object.send(:attribute_was, attribute)
|
20
20
|
object.errors.add(attribute, 'is immutable and cannot be updated')
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
# Ownership check
|
25
|
-
if value
|
26
|
-
object.errors.add(attribute,
|
25
|
+
if value && Mongoid::Multitenancy.current_tenant && value != Mongoid::Multitenancy.current_tenant.id
|
26
|
+
object.errors.add(attribute, 'not authorized')
|
27
27
|
end
|
28
28
|
|
29
29
|
# Optional Check
|
30
|
-
if !options[:optional]
|
30
|
+
if !options[:optional] && value.nil?
|
31
31
|
object.errors.add(attribute, 'is mandatory')
|
32
32
|
end
|
33
33
|
end
|
@@ -39,14 +39,8 @@ module Mongoid
|
|
39
39
|
criteria = with_tenant_criterion(criteria, klass, document)
|
40
40
|
criteria = criteria.merge(options[:conditions].call) if options[:conditions]
|
41
41
|
|
42
|
-
if
|
43
|
-
|
44
|
-
add_error(document, attribute, value)
|
45
|
-
end
|
46
|
-
else
|
47
|
-
if criteria.with(criteria.persistence_options).read(mode: :primary).exists?
|
48
|
-
add_error(document, attribute, value)
|
49
|
-
end
|
42
|
+
if criteria.read(mode: :primary).exists?
|
43
|
+
add_error(document, attribute, value)
|
50
44
|
end
|
51
45
|
end
|
52
46
|
|
@@ -58,8 +52,8 @@ module Mongoid
|
|
58
52
|
name = document.database_field_name(item)
|
59
53
|
tenant_value = document.attributes[name]
|
60
54
|
|
61
|
-
if document.class.tenant_options[:optional]
|
62
|
-
if
|
55
|
+
if document.class.tenant_options[:optional] && !options[:exclude_shared]
|
56
|
+
if tenant_value
|
63
57
|
criteria = criteria.where(:"#{item}".in => [tenant_value, nil])
|
64
58
|
end
|
65
59
|
else
|
@@ -8,12 +8,12 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.summary = 'Support of a multi-tenant database with Mongoid'
|
9
9
|
gem.homepage = 'https://github.com/PerfectMemory/mongoid-multitenancy'
|
10
10
|
gem.license = 'MIT'
|
11
|
-
gem.files = `git ls-files`.split(
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
11
|
+
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
14
|
gem.name = 'mongoid-multitenancy'
|
15
15
|
gem.require_paths = ['lib']
|
16
16
|
gem.version = Mongoid::Multitenancy::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency('mongoid', '>=
|
18
|
+
gem.add_dependency('mongoid', '>= 6', '< 8')
|
19
19
|
end
|
data/spec/immutable_spec.rb
CHANGED
data/spec/indexable_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'tenant' do
|
4
|
-
|
5
4
|
let(:client) do
|
6
5
|
Account.create!(name: 'client')
|
7
6
|
end
|
@@ -12,26 +11,25 @@ describe 'tenant' do
|
|
12
11
|
|
13
12
|
context 'without index: true' do
|
14
13
|
it 'does not create an index' do
|
15
|
-
expect(Immutable).not_to have_index_for(:
|
14
|
+
expect(Immutable).not_to have_index_for(tenant_id: 1)
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
19
18
|
context 'with index: true' do
|
20
19
|
it 'creates an index' do
|
21
|
-
expect(Indexable).to have_index_for(:
|
20
|
+
expect(Indexable).to have_index_for(tenant_id: 1)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
24
|
context 'with full_indexes: true' do
|
26
25
|
it 'add the tenant field on each index' do
|
27
|
-
expect(Immutable).to have_index_for(:
|
26
|
+
expect(Immutable).to have_index_for(tenant_id: 1, title: 1)
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
30
|
context 'with full_indexes: false' do
|
32
31
|
it 'does not add the tenant field on each index' do
|
33
|
-
expect(Indexable).not_to have_index_for(:
|
32
|
+
expect(Indexable).not_to have_index_for(tenant_id: 1, title: 1)
|
34
33
|
end
|
35
34
|
end
|
36
|
-
|
37
35
|
end
|
data/spec/mandatory_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Mandatory do
|
4
|
-
|
5
4
|
let(:client) do
|
6
5
|
Account.create!(name: 'client')
|
7
6
|
end
|
@@ -17,6 +16,18 @@ describe Mandatory do
|
|
17
16
|
it_behaves_like 'a tenantable model'
|
18
17
|
it { is_expected.to validate_tenant_uniqueness_of(:slug) }
|
19
18
|
|
19
|
+
describe '.shared' do
|
20
|
+
it 'is defined' do
|
21
|
+
expect(Mandatory).to respond_to(:shared)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.unshared' do
|
26
|
+
it 'is defined' do
|
27
|
+
expect(Mandatory).to respond_to(:unshared)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
20
31
|
describe '.default_scope' do
|
21
32
|
before do
|
22
33
|
Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(title: 'title X', slug: 'article-x') }
|
data/spec/models/account.rb
CHANGED
data/spec/models/immutable.rb
CHANGED
@@ -2,14 +2,14 @@ class Immutable
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Multitenancy::Document
|
4
4
|
|
5
|
-
tenant(:
|
5
|
+
tenant(:tenant, class_name: 'Account', immutable: true)
|
6
6
|
|
7
|
-
field :slug, :
|
8
|
-
field :title, :
|
7
|
+
field :slug, type: String
|
8
|
+
field :title, type: String
|
9
9
|
|
10
10
|
validates_tenant_uniqueness_of :slug
|
11
11
|
validates_presence_of :slug
|
12
12
|
validates_presence_of :title
|
13
13
|
|
14
|
-
index(
|
14
|
+
index(title: 1)
|
15
15
|
end
|
data/spec/models/indexable.rb
CHANGED
data/spec/models/mandatory.rb
CHANGED
@@ -2,14 +2,14 @@ class Mandatory
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Multitenancy::Document
|
4
4
|
|
5
|
-
tenant(:
|
5
|
+
tenant(:tenant, class_name: 'Account')
|
6
6
|
|
7
7
|
field :slug, type: String
|
8
|
-
field :title, type:String
|
8
|
+
field :title, type: String
|
9
9
|
|
10
10
|
validates_tenant_uniqueness_of :slug
|
11
11
|
validates_presence_of :slug
|
12
12
|
validates_presence_of :title
|
13
13
|
|
14
|
-
index(
|
14
|
+
index(title: 1)
|
15
15
|
end
|
data/spec/models/mutable.rb
CHANGED
@@ -2,14 +2,14 @@ class Mutable
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Multitenancy::Document
|
4
4
|
|
5
|
-
tenant :
|
5
|
+
tenant :tenant, class_name: 'Account', immutable: false, optional: false
|
6
6
|
|
7
|
-
field :slug, :
|
8
|
-
field :title, :
|
7
|
+
field :slug, type: String
|
8
|
+
field :title, type: String
|
9
9
|
|
10
10
|
validates_tenant_uniqueness_of :slug
|
11
11
|
validates_presence_of :slug
|
12
12
|
validates_presence_of :title
|
13
13
|
|
14
|
-
index(
|
14
|
+
index(title: 1)
|
15
15
|
end
|
data/spec/models/optional.rb
CHANGED
@@ -2,7 +2,7 @@ class Optional
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Multitenancy::Document
|
4
4
|
|
5
|
-
tenant(:
|
5
|
+
tenant(:tenant, class_name: 'Account', optional: true)
|
6
6
|
|
7
7
|
field :slug, type: String
|
8
8
|
field :title, type: String
|
@@ -11,5 +11,5 @@ class Optional
|
|
11
11
|
validates_presence_of :slug
|
12
12
|
validates_presence_of :title
|
13
13
|
|
14
|
-
index(
|
14
|
+
index(title: 1)
|
15
15
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class OptionalExclude
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Multitenancy::Document
|
4
|
+
|
5
|
+
tenant(:tenant, class_name: 'Account', optional: true)
|
6
|
+
|
7
|
+
field :slug, type: String
|
8
|
+
field :title, type: String
|
9
|
+
|
10
|
+
validates_tenant_uniqueness_of :slug, exclude_shared: true
|
11
|
+
validates_presence_of :slug
|
12
|
+
validates_presence_of :title
|
13
|
+
|
14
|
+
index(title: 1)
|
15
|
+
end
|
@@ -21,8 +21,17 @@ describe Mongoid::Multitenancy do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'restores the current tenant after the block' do
|
24
|
-
Mongoid::Multitenancy.with_tenant(another_client)
|
24
|
+
Mongoid::Multitenancy.with_tenant(another_client) { ; }
|
25
25
|
expect(Mongoid::Multitenancy.current_tenant).to eq client
|
26
26
|
end
|
27
|
+
|
28
|
+
context 'when the block fails' do
|
29
|
+
it 'restores the current tenant' do
|
30
|
+
begin
|
31
|
+
Mongoid::Multitenancy.with_tenant(another_client) { raise StandardError }
|
32
|
+
rescue StandardError; end
|
33
|
+
expect(Mongoid::Multitenancy.current_tenant).to eq client
|
34
|
+
end
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
data/spec/mutable_child_spec.rb
CHANGED
data/spec/mutable_spec.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OptionalExclude do
|
4
|
+
let(:client) do
|
5
|
+
Account.create!(name: 'client')
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:another_client) do
|
9
|
+
Account.create!(name: 'another client')
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:item) do
|
13
|
+
OptionalExclude.new(title: 'title X', slug: 'page-x')
|
14
|
+
end
|
15
|
+
|
16
|
+
it_behaves_like 'a tenantable model'
|
17
|
+
|
18
|
+
describe '#valid?' do
|
19
|
+
context 'with a tenant' do
|
20
|
+
before do
|
21
|
+
Mongoid::Multitenancy.current_tenant = client
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'is valid' do
|
25
|
+
expect(item).to be_valid
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with a uniqueness constraint' do
|
29
|
+
let(:duplicate) do
|
30
|
+
OptionalExclude.new(title: 'title Y', slug: 'page-x')
|
31
|
+
end
|
32
|
+
|
33
|
+
before do
|
34
|
+
item.save!
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'does not allow duplicates on the same tenant' do
|
38
|
+
expect(duplicate).not_to be_valid
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'allow duplicates on a different same tenant' do
|
42
|
+
Mongoid::Multitenancy.with_tenant(another_client) do
|
43
|
+
expect(duplicate).to be_valid
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'without a tenant' do
|
50
|
+
it 'is valid' do
|
51
|
+
expect(item).to be_valid
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with a uniqueness constraint' do
|
55
|
+
let(:duplicate) do
|
56
|
+
OptionalExclude.new(title: 'title Y', slug: 'page-x')
|
57
|
+
end
|
58
|
+
|
59
|
+
before do
|
60
|
+
item.save!
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'allow duplicates on any client' do
|
64
|
+
Mongoid::Multitenancy.with_tenant(client) do
|
65
|
+
expect(duplicate).to be_valid
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/spec/optional_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Optional do
|
4
|
-
|
5
4
|
let(:client) do
|
6
5
|
Account.create!(name: 'client')
|
7
6
|
end
|
@@ -25,13 +24,13 @@ describe Optional do
|
|
25
24
|
|
26
25
|
context 'when persisted' do
|
27
26
|
before do
|
28
|
-
item.
|
27
|
+
item.tenant = nil
|
29
28
|
item.save!
|
30
29
|
end
|
31
30
|
|
32
31
|
it 'does not override the client' do
|
33
32
|
item.reload
|
34
|
-
expect(Optional.last.
|
33
|
+
expect(Optional.last.tenant).to be_nil
|
35
34
|
end
|
36
35
|
end
|
37
36
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NoScopable do
|
4
|
+
it_behaves_like 'a tenantable model' do
|
5
|
+
let(:client) do
|
6
|
+
Account.create!(name: 'client')
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:another_client) do
|
10
|
+
Account.create!(name: 'another client')
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:item) do
|
14
|
+
NoScopable.new(title: 'title X', slug: 'page-x')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.shared' do
|
19
|
+
it 'is not defined' do
|
20
|
+
expect(NoScopable).not_to respond_to(:shared)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.unshared' do
|
25
|
+
it 'is not defined' do
|
26
|
+
expect(NoScopable).not_to respond_to(:unshared)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -15,12 +15,6 @@ require 'mongoid'
|
|
15
15
|
require 'mongoid-multitenancy'
|
16
16
|
require 'mongoid-rspec'
|
17
17
|
|
18
|
-
if Mongoid::VERSION.start_with? '5'
|
19
|
-
Mongo::Logger.logger.level = ::Logger::FATAL
|
20
|
-
elsif Mongoid::VERSION.start_with? '4'
|
21
|
-
Moped.logger = nil
|
22
|
-
end
|
23
|
-
|
24
18
|
require_relative 'support/shared_examples'
|
25
19
|
require_relative 'support/mongoid_matchers'
|
26
20
|
|
@@ -30,7 +24,8 @@ Mongoid.configure do |config|
|
|
30
24
|
config.connect_to 'mongoid_multitenancy'
|
31
25
|
end
|
32
26
|
|
33
|
-
Mongoid.logger =
|
27
|
+
Mongoid.logger.level = Logger::INFO
|
28
|
+
Mongo::Logger.logger.level = Logger::INFO
|
34
29
|
|
35
30
|
RSpec.configure do |config|
|
36
31
|
config.include Mongoid::Matchers
|
@@ -1,7 +1,6 @@
|
|
1
1
|
shared_examples_for 'a tenantable model' do
|
2
|
-
|
3
|
-
it { is_expected.to
|
4
|
-
it { is_expected.to have_index_for(client_id: 1, title: 1) }
|
2
|
+
it { is_expected.to belong_to(:tenant) }
|
3
|
+
it { is_expected.to have_index_for(tenant_id: 1, title: 1) }
|
5
4
|
|
6
5
|
describe '#initialize' do
|
7
6
|
context 'within a client context' do
|
@@ -10,7 +9,7 @@ shared_examples_for 'a tenantable model' do
|
|
10
9
|
end
|
11
10
|
|
12
11
|
it 'set the client' do
|
13
|
-
expect(item.
|
12
|
+
expect(item.tenant).to eq client
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
@@ -20,7 +19,7 @@ shared_examples_for 'a tenantable model' do
|
|
20
19
|
end
|
21
20
|
|
22
21
|
it 'does not set any client' do
|
23
|
-
expect(item.
|
22
|
+
expect(item.tenant).to be_nil
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
@@ -33,7 +32,7 @@ shared_examples_for 'a tenantable model' do
|
|
33
32
|
|
34
33
|
context 'with the client id' do
|
35
34
|
before do
|
36
|
-
item.
|
35
|
+
item.tenant = client
|
37
36
|
end
|
38
37
|
|
39
38
|
it 'is valid' do
|
@@ -43,7 +42,7 @@ shared_examples_for 'a tenantable model' do
|
|
43
42
|
|
44
43
|
context 'with another client id' do
|
45
44
|
before do
|
46
|
-
item.
|
45
|
+
item.tenant = another_client
|
47
46
|
end
|
48
47
|
|
49
48
|
it 'is not valid' do
|
@@ -59,7 +58,7 @@ shared_examples_for 'a tenantable model' do
|
|
59
58
|
|
60
59
|
context 'with the client id' do
|
61
60
|
before do
|
62
|
-
item.
|
61
|
+
item.tenant = client
|
63
62
|
end
|
64
63
|
|
65
64
|
it 'is valid' do
|
@@ -69,7 +68,7 @@ shared_examples_for 'a tenantable model' do
|
|
69
68
|
|
70
69
|
context 'with another client id' do
|
71
70
|
before do
|
72
|
-
item.
|
71
|
+
item.tenant = another_client
|
73
72
|
end
|
74
73
|
|
75
74
|
it 'is valid' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-multitenancy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aymeric Brisse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
29
|
+
version: '6'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
27
33
|
description: MultiTenancy with Mongoid
|
28
34
|
email:
|
29
35
|
- aymeric.brisse@mperfect-memory.com
|
@@ -39,8 +45,8 @@ files:
|
|
39
45
|
- LICENSE.TXT
|
40
46
|
- README.md
|
41
47
|
- Rakefile
|
42
|
-
- gemfiles/Gemfile.mongoid-
|
43
|
-
- gemfiles/Gemfile.mongoid-
|
48
|
+
- gemfiles/Gemfile.mongoid-6
|
49
|
+
- gemfiles/Gemfile.mongoid-7
|
44
50
|
- lib/mongoid-multitenancy.rb
|
45
51
|
- lib/mongoid/multitenancy.rb
|
46
52
|
- lib/mongoid/multitenancy/document.rb
|
@@ -58,11 +64,15 @@ files:
|
|
58
64
|
- spec/models/mandatory.rb
|
59
65
|
- spec/models/mutable.rb
|
60
66
|
- spec/models/mutable_child.rb
|
67
|
+
- spec/models/no_scopable.rb
|
61
68
|
- spec/models/optional.rb
|
69
|
+
- spec/models/optional_exclude.rb
|
62
70
|
- spec/mongoid-multitenancy_spec.rb
|
63
71
|
- spec/mutable_child_spec.rb
|
64
72
|
- spec/mutable_spec.rb
|
73
|
+
- spec/optional_exclude_spec.rb
|
65
74
|
- spec/optional_spec.rb
|
75
|
+
- spec/scopable_spec.rb
|
66
76
|
- spec/spec_helper.rb
|
67
77
|
- spec/support/mongoid_matchers.rb
|
68
78
|
- spec/support/shared_examples.rb
|
@@ -86,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
96
|
version: '0'
|
87
97
|
requirements: []
|
88
98
|
rubyforge_project:
|
89
|
-
rubygems_version: 2.
|
99
|
+
rubygems_version: 2.6.14.4
|
90
100
|
signing_key:
|
91
101
|
specification_version: 4
|
92
102
|
summary: Support of a multi-tenant database with Mongoid
|
@@ -101,12 +111,15 @@ test_files:
|
|
101
111
|
- spec/models/mandatory.rb
|
102
112
|
- spec/models/mutable.rb
|
103
113
|
- spec/models/mutable_child.rb
|
114
|
+
- spec/models/no_scopable.rb
|
104
115
|
- spec/models/optional.rb
|
116
|
+
- spec/models/optional_exclude.rb
|
105
117
|
- spec/mongoid-multitenancy_spec.rb
|
106
118
|
- spec/mutable_child_spec.rb
|
107
119
|
- spec/mutable_spec.rb
|
120
|
+
- spec/optional_exclude_spec.rb
|
108
121
|
- spec/optional_spec.rb
|
122
|
+
- spec/scopable_spec.rb
|
109
123
|
- spec/spec_helper.rb
|
110
124
|
- spec/support/mongoid_matchers.rb
|
111
125
|
- spec/support/shared_examples.rb
|
112
|
-
has_rdoc:
|
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
gem 'mongoid', '~> 4.0'
|
4
|
-
|
5
|
-
gem 'rake', '~> 10.0'
|
6
|
-
|
7
|
-
group :test do
|
8
|
-
gem 'database_cleaner', github: 'DatabaseCleaner/database_cleaner', ref: '1cab518'
|
9
|
-
gem 'coveralls', :require => false
|
10
|
-
gem 'rspec', '~> 3.1'
|
11
|
-
gem 'yard', '~> 0.8'
|
12
|
-
gem 'mongoid-rspec', '~> 2.1'
|
13
|
-
end
|
14
|
-
|
15
|
-
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
16
|
-
gemspec :path => '..'
|
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
gem 'mongoid', '~> 5.0'
|
4
|
-
|
5
|
-
gem 'rake', '~> 10.0'
|
6
|
-
|
7
|
-
group :test do
|
8
|
-
gem 'database_cleaner', github: 'DatabaseCleaner/database_cleaner', ref: '1cab518'
|
9
|
-
gem 'coveralls', :require => false
|
10
|
-
gem 'rspec', '~> 3.1'
|
11
|
-
gem 'yard', '~> 0.8'
|
12
|
-
gem 'mongoid-rspec', '~> 3.0'
|
13
|
-
end
|
14
|
-
|
15
|
-
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
16
|
-
gemspec :path => '..'
|