mongoid-multitenancy-forked 1.0.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/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +15 -0
- data/LICENSE.TXT +20 -0
- data/README.md +284 -0
- data/Rakefile +8 -0
- data/gemfiles/Gemfile.mongoid-6 +14 -0
- data/gemfiles/Gemfile.mongoid-7 +14 -0
- data/lib/mongoid/multitenancy/document.rb +176 -0
- data/lib/mongoid/multitenancy/validators/tenancy.rb +36 -0
- data/lib/mongoid/multitenancy/validators/tenant_uniqueness.rb +67 -0
- data/lib/mongoid/multitenancy/version.rb +6 -0
- data/lib/mongoid/multitenancy.rb +34 -0
- data/lib/mongoid-multitenancy-forked.rb +1 -0
- data/mongoid-multitenancy-forked.gemspec +19 -0
- data/spec/immutable_spec.rb +46 -0
- data/spec/indexable_spec.rb +70 -0
- data/spec/inheritance_spec.rb +22 -0
- data/spec/mandatory_spec.rb +116 -0
- data/spec/models/account.rb +5 -0
- data/spec/models/immutable.rb +15 -0
- data/spec/models/indexable.rb +61 -0
- data/spec/models/mandatory.rb +15 -0
- data/spec/models/mutable.rb +15 -0
- data/spec/models/mutable_child.rb +9 -0
- data/spec/models/no_scopable.rb +11 -0
- data/spec/models/optional.rb +15 -0
- data/spec/models/optional_exclude.rb +15 -0
- data/spec/mongoid-multitenancy_spec.rb +37 -0
- data/spec/mutable_child_spec.rb +46 -0
- data/spec/mutable_spec.rb +50 -0
- data/spec/optional_exclude_spec.rb +71 -0
- data/spec/optional_spec.rb +207 -0
- data/spec/scopable_spec.rb +29 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/mongoid_matchers.rb +17 -0
- data/spec/support/shared_examples.rb +80 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ca01ab6c53dd07de80b689a0364f4fc57ddb975fbce3951251a4540a8df890db
|
4
|
+
data.tar.gz: 8e86b6a4acfa0d6b036a7af7fbeaef21a386a1b543bbfa7920df70d64fb592e8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 578304563260d2096c4d59883eb1174a2bbeb7a5c46d48fa13b3a0ced7e7d94b6cbfe442f1131cd838b38b2d9c7c744a693f0ebcf6ad3b909fb1512014a50cf1
|
7
|
+
data.tar.gz: abaa3606883c0c64677e6bec188c40afdfacb5270dafdf7638fe5dc5575aefc3e7cbbd4af106b88cf4b9b995ac50854039008526c3c2cc34b67461e060e5e2be
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,56 @@
|
|
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
|
+
|
34
|
+
## 1.1
|
35
|
+
|
36
|
+
### New Features
|
37
|
+
|
38
|
+
* Add scopes *shared* and *unshared* (1b5c420)
|
39
|
+
|
40
|
+
### Fixes
|
41
|
+
|
42
|
+
* When a tenant is optional, do not override the tenant during persisted document initialization (81a9b45)
|
43
|
+
|
44
|
+
## 1.0.0
|
45
|
+
|
46
|
+
### New Features
|
47
|
+
|
48
|
+
* Add support for mongoid 5
|
49
|
+
|
50
|
+
### Major Changes (Backwards Incompatible)
|
51
|
+
|
52
|
+
* Drops support for mongoid 3
|
53
|
+
|
54
|
+
* An optional tenant is now automatically set if a current tenant is defined.
|
55
|
+
|
56
|
+
* 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
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rake'
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem 'database_cleaner-mongoid'
|
7
|
+
gem 'coveralls', require: false
|
8
|
+
gem 'rspec', '~> 3.1'
|
9
|
+
gem 'yard'
|
10
|
+
gem 'mongoid-rspec', git: 'https://github.com/mongoid-rspec/mongoid-rspec.git'
|
11
|
+
gem 'rubocop', require: false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Specify your gem's dependencies in mongoid-multitenancy.gemspec
|
15
|
+
gemspec
|
data/LICENSE.TXT
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Perfect Memory
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,284 @@
|
|
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)
|
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 that all 'tenant models' are always properly scoped to the current tenant: when viewing, searching and creating.
|
4
|
+
|
5
|
+
It is directly inspired by the [acts_as_tenant gem](https://github.com/ErwinM/acts_as_tenant) for Active Record.
|
6
|
+
|
7
|
+
In addition, mongoid-multitenancy:
|
8
|
+
|
9
|
+
* allows you to set the current tenant
|
10
|
+
* allows shared items between the tenants
|
11
|
+
* allows you to define an immutable tenant field once it is persisted
|
12
|
+
* is thread safe
|
13
|
+
* redefines some mongoid functions like `index`, `validates_with` and `delete_all` to take in account the multitenancy.
|
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
|
+
|
20
|
+
Installation
|
21
|
+
===============
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
gem 'mongoid-multitenancy', '~> 2.0'
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install mongoid-multitenancy
|
34
|
+
|
35
|
+
Usage
|
36
|
+
===============
|
37
|
+
|
38
|
+
There are two steps to add multi-tenancy to your app with mongoid-multitenancy:
|
39
|
+
|
40
|
+
1. setting the current tenant and
|
41
|
+
2. scoping your models.
|
42
|
+
|
43
|
+
Setting the current tenant
|
44
|
+
--------------------------
|
45
|
+
There are two ways to set the current tenant: (1) by setting the current tenant manually, or (2) by setting the current tenant for a block.
|
46
|
+
|
47
|
+
**Setting the current tenant in a controller, manually**
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
Mongoid::Multitenancy.current_tenant = client_instance
|
51
|
+
```
|
52
|
+
|
53
|
+
Setting the current_tenant yourself requires you to use a before_filter to set the Mongoid::Multitenancy.current_tenant variable.
|
54
|
+
|
55
|
+
**Setting the current tenant for a block**
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Mongoid::Multitenancy.with_tenant(client_instance) do
|
59
|
+
# Current tenant is set for all code in this block
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
This approach is useful when running background processes for a specified tenant. For example, by putting this in your worker's run method,
|
64
|
+
any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
|
65
|
+
|
66
|
+
**Note:** If the current tenant is not set by one of these methods, mongoid-multitenancy will apply a global scope to your models, not related to any tenant. So make sure you use one of the two methods to tell mongoid-multitenancy about the current tenant.
|
67
|
+
|
68
|
+
Scoping your models
|
69
|
+
-------------------
|
70
|
+
```ruby
|
71
|
+
class Client
|
72
|
+
include Mongoid::Document
|
73
|
+
|
74
|
+
field :name, :type => String
|
75
|
+
validates_uniqueness_of :name
|
76
|
+
end
|
77
|
+
|
78
|
+
class Article
|
79
|
+
include Mongoid::Document
|
80
|
+
include Mongoid::Multitenancy::Document
|
81
|
+
|
82
|
+
tenant(:tenant)
|
83
|
+
|
84
|
+
field :title, :type => String
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Adding `tenant` to your model declaration will scope that model to the current tenant **BUT ONLY if a current tenant has been set**.
|
89
|
+
The association passed to the `tenant` function must be valid.
|
90
|
+
|
91
|
+
`tenant` accepts several options:
|
92
|
+
|
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) |
|
101
|
+
|
102
|
+
Some examples to illustrate this behavior:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# This manually sets the current tenant for testing purposes. In your app this is handled by the gem.
|
106
|
+
Mongoid::Multitenancy.current_tenant = Client.find_by(:name => 'Perfect Memory') # => <#Client _id:50ca04b86c82bfc125000025, :name: "Perfect Memory">
|
107
|
+
|
108
|
+
# All searches are scoped by the tenant, the following searches will only return objects belonging to the current client.
|
109
|
+
Article.all # => all articles where tenant_id => 50ca04b86c82bfc125000025
|
110
|
+
|
111
|
+
# New objects are scoped to the current tenant
|
112
|
+
article = Article.new(:title => 'New blog')
|
113
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
114
|
+
|
115
|
+
# It can make the tenant field immutable once it is persisted to avoid inconsistency
|
116
|
+
article.persisted? # => true
|
117
|
+
article.tenant = another_client
|
118
|
+
article.valid? # => false
|
119
|
+
```
|
120
|
+
|
121
|
+
**Optional tenant**
|
122
|
+
|
123
|
+
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**.
|
124
|
+
|
125
|
+
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.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class Article
|
129
|
+
include Mongoid::Document
|
130
|
+
include Mongoid::Multitenancy::Document
|
131
|
+
|
132
|
+
tenant(:tenant, optional: true)
|
133
|
+
|
134
|
+
field :title, :type => String
|
135
|
+
end
|
136
|
+
|
137
|
+
Mongoid::Multitenancy.with_tenant(client_instance) do
|
138
|
+
Article.all # => all articles where tenant_id.in [50ca04b86c82bfc125000025, nil]
|
139
|
+
article = Article.new(:title => 'New article')
|
140
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
141
|
+
|
142
|
+
# tenant needs to be set manually to nil
|
143
|
+
article = Article.new(:title => 'New article', :tenant => nil)
|
144
|
+
article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: 50ca04b86c82bfc125000025>
|
145
|
+
article.tenant = nil
|
146
|
+
article.save => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', tenant_id: nil>
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
Rails
|
151
|
+
-------------------
|
152
|
+
|
153
|
+
If you are using Rails, you may want to set the current tenant at each request.
|
154
|
+
|
155
|
+
**Manually set the current tenant in ApplicationController using the host request**
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class ApplicationController < ActionController::Base
|
159
|
+
before_filter :set_current_client
|
160
|
+
|
161
|
+
def set_current_client
|
162
|
+
current_client = Client.find_by_host(request.host)
|
163
|
+
Mongoid::Multitenancy.current_tenant = current_client
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Setting the current_tenant yourself requires you to use a before_filter to set the Mongoid::Multitenancy.current_tenant variable.
|
169
|
+
|
170
|
+
Mongoid Uniqueness validators
|
171
|
+
-------------------
|
172
|
+
|
173
|
+
mongoid-multitenancy brings a TenantUniqueness validator that will, depending on the tenant options, check that your uniqueness
|
174
|
+
constraints are respected:
|
175
|
+
|
176
|
+
* When used with a *mandatory* tenant, the uniqueness constraint is scoped to the current client.
|
177
|
+
|
178
|
+
In the following case, 2 articles can have the same slug if they belongs to 2 different clients.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
class Article
|
182
|
+
include Mongoid::Document
|
183
|
+
include Mongoid::Multitenancy::Document
|
184
|
+
|
185
|
+
tenant :tenant
|
186
|
+
|
187
|
+
field :slug
|
188
|
+
|
189
|
+
validates_tenant_uniqueness_of :slug
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
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`.
|
196
|
+
|
197
|
+
In the following case, 2 private articles can have the same slug if they belongs to 2 different clients. But if a shared
|
198
|
+
article has the slug "slugA", no client will be able to use that slug again, like a standard `validates_uniqueness_of` does.
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
class Article
|
202
|
+
include Mongoid::Document
|
203
|
+
include Mongoid::Multitenancy::Document
|
204
|
+
|
205
|
+
tenant :tenant, optional: true
|
206
|
+
|
207
|
+
field :slug
|
208
|
+
|
209
|
+
validates_tenant_uniqueness_of :slug
|
210
|
+
end
|
211
|
+
```
|
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
|
+
|
229
|
+
Mongoid indexes
|
230
|
+
-------------------
|
231
|
+
|
232
|
+
mongoid-multitenancy automatically adds the tenant foreign key in all your mongoid indexes to avoid to redefine all your validators. If you prefer to define the indexes manually, you can use the option `full_indexes: false` on the tenant or `full_index: true/false` on the indexes.
|
233
|
+
|
234
|
+
To create a single index on the tenant field, you can use the option `index: true` like any `belongs_to` declaration (false by default)
|
235
|
+
|
236
|
+
On the example below, only one index will be created:
|
237
|
+
|
238
|
+
* { 'tenant_id' => 1, 'title' => 1 }
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
class Article
|
242
|
+
include Mongoid::Document
|
243
|
+
include Mongoid::Multitenancy::Document
|
244
|
+
|
245
|
+
tenant :tenant, full_indexes: true
|
246
|
+
|
247
|
+
field :title
|
248
|
+
|
249
|
+
index({ :title => 1 })
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
On the example below, 3 indexes will be created:
|
254
|
+
|
255
|
+
* { 'tenant_id' => 1 }
|
256
|
+
* { 'tenant_id' => 1, 'title' => 1 }
|
257
|
+
* { 'name' => 1 }
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class Article
|
261
|
+
include Mongoid::Document
|
262
|
+
include Mongoid::Multitenancy::Document
|
263
|
+
|
264
|
+
tenant :tenant, index: true
|
265
|
+
|
266
|
+
field :title
|
267
|
+
field :name
|
268
|
+
|
269
|
+
index({ :title => 1 })
|
270
|
+
index({ :name => 1 }, { full_index: false })
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
Author & Credits
|
275
|
+
----------------
|
276
|
+
mongoid-multitenancy is written by [Aymeric Brisse](https://github.com/abrisse/), from [Perfect Memory](http://www.perfect-memory.com).
|
277
|
+
|
278
|
+
## Contributing
|
279
|
+
|
280
|
+
1. Fork it
|
281
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
282
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
283
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
284
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -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
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Multitenancy
|
3
|
+
module Document
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
attr_accessor :tenant_field, :tenant_options
|
8
|
+
|
9
|
+
# List of authorized options
|
10
|
+
MULTITENANCY_OPTIONS = [:optional, :immutable, :full_indexes, :index, :scopes].freeze
|
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
|
+
# @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.
|
31
|
+
# @return [ Field ] The generated field
|
32
|
+
def tenant(association = :account, options = {})
|
33
|
+
options = { full_indexes: true, immutable: true, scopes: true }.merge!(options)
|
34
|
+
assoc_options, multitenant_options = build_options(options)
|
35
|
+
|
36
|
+
# Setup the association between the class and the tenant class
|
37
|
+
belongs_to association, assoc_options
|
38
|
+
|
39
|
+
# Get the tenant model and its foreign key
|
40
|
+
self.tenant_field = reflect_on_association(association).foreign_key.to_sym
|
41
|
+
self.tenant_options = multitenant_options
|
42
|
+
|
43
|
+
# Validates the tenant field
|
44
|
+
validates_tenancy_of tenant_field, multitenant_options
|
45
|
+
|
46
|
+
define_scopes if multitenant_options[:scopes]
|
47
|
+
define_initializer association
|
48
|
+
define_inherited association, options
|
49
|
+
define_index if multitenant_options[:index]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Validates whether or not a field is unique against the documents in the
|
53
|
+
# database.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
#
|
57
|
+
# class Person
|
58
|
+
# include Mongoid::Document
|
59
|
+
# include Mongoid::Multitenancy::Document
|
60
|
+
# field :title
|
61
|
+
#
|
62
|
+
# validates_tenant_uniqueness_of :title
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# @param [ Array ] *args The arguments to pass to the validator.
|
66
|
+
def validates_tenant_uniqueness_of(*args)
|
67
|
+
validates_with(TenantUniquenessValidator, _merge_attributes(args))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Validates whether or not a tenant field is correct.
|
71
|
+
#
|
72
|
+
# @example Define the tenant validator
|
73
|
+
#
|
74
|
+
# class Person
|
75
|
+
# include Mongoid::Document
|
76
|
+
# include Mongoid::Multitenancy::Document
|
77
|
+
# field :title
|
78
|
+
# tenant :client
|
79
|
+
#
|
80
|
+
# validates_tenant_of :client
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @param [ Array ] *args The arguments to pass to the validator.
|
84
|
+
def validates_tenancy_of(*args)
|
85
|
+
validates_with(TenancyValidator, _merge_attributes(args))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Redefine 'index' to include the tenant field in first position
|
89
|
+
def index(spec, options = nil)
|
90
|
+
super_options = (options || {}).dup
|
91
|
+
full_index = super_options.delete(:full_index)
|
92
|
+
if full_index.nil? ? tenant_options[:full_indexes] : full_index
|
93
|
+
spec = { tenant_field => 1 }.merge(spec)
|
94
|
+
end
|
95
|
+
|
96
|
+
super(spec, super_options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Redefine 'delete_all' to take in account the default scope
|
100
|
+
def delete_all(conditions = {})
|
101
|
+
scoped.where(conditions).delete
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# @private
|
107
|
+
def build_options(options)
|
108
|
+
assoc_options = {}
|
109
|
+
multitenant_options = {}
|
110
|
+
|
111
|
+
options.each do |k, v|
|
112
|
+
if MULTITENANCY_OPTIONS.include?(k)
|
113
|
+
multitenant_options[k] = v
|
114
|
+
assoc_options[k] = v if k == :optional
|
115
|
+
else
|
116
|
+
assoc_options[k] = v
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
[assoc_options, multitenant_options]
|
121
|
+
end
|
122
|
+
|
123
|
+
# @private
|
124
|
+
#
|
125
|
+
# Define the after_initialize
|
126
|
+
def define_initializer(association)
|
127
|
+
# Apply the default value when the default scope is complex (optional tenant)
|
128
|
+
after_initialize lambda {
|
129
|
+
if Multitenancy.current_tenant && new_record?
|
130
|
+
send "#{association}=".to_sym, Multitenancy.current_tenant
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
# @private
|
136
|
+
#
|
137
|
+
# Define the inherited method
|
138
|
+
def define_inherited(association, options)
|
139
|
+
define_singleton_method(:inherited) do |child|
|
140
|
+
child.tenant association, options.merge(scopes: false)
|
141
|
+
super(child)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# @private
|
146
|
+
#
|
147
|
+
# Define the scopes
|
148
|
+
def define_scopes
|
149
|
+
# Set the default_scope to scope to current tenant
|
150
|
+
default_scope lambda {
|
151
|
+
if Multitenancy.current_tenant
|
152
|
+
tenant_id = Multitenancy.current_tenant.id
|
153
|
+
if tenant_options[:optional]
|
154
|
+
where(tenant_field.in => [tenant_id, nil])
|
155
|
+
else
|
156
|
+
where(tenant_field => tenant_id)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
all
|
160
|
+
end
|
161
|
+
}
|
162
|
+
|
163
|
+
scope :shared, -> { where(tenant_field => nil) }
|
164
|
+
scope :unshared, -> { where(tenant_field => Multitenancy.current_tenant.id) }
|
165
|
+
end
|
166
|
+
|
167
|
+
# @private
|
168
|
+
#
|
169
|
+
# Create the index
|
170
|
+
def define_index
|
171
|
+
index({ tenant_field => 1 }, background: true)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Multitenancy
|
3
|
+
# Validates whether or not a tenant field is correct.
|
4
|
+
#
|
5
|
+
# @example Define the tenant validator
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# include Mongoid::Document
|
9
|
+
# include Mongoid::Multitenancy::Document
|
10
|
+
# field :title
|
11
|
+
# tenant :client
|
12
|
+
#
|
13
|
+
# validates_tenancy_of :client
|
14
|
+
# end
|
15
|
+
class TenancyValidator < ActiveModel::EachValidator
|
16
|
+
def validate_each(object, attribute, value)
|
17
|
+
# Immutable Check
|
18
|
+
if options[:immutable]
|
19
|
+
if object.send(:attribute_changed?, attribute) && object.send(:attribute_was, attribute)
|
20
|
+
object.errors.add(attribute, 'is immutable and cannot be updated')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ownership check
|
25
|
+
if value && Mongoid::Multitenancy.current_tenant && value != Mongoid::Multitenancy.current_tenant.id
|
26
|
+
object.errors.add(attribute, 'not authorized')
|
27
|
+
end
|
28
|
+
|
29
|
+
# Optional Check
|
30
|
+
if !options[:optional] && value.nil?
|
31
|
+
object.errors.add(attribute, 'is mandatory')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|