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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +12 -0
  5. data/CHANGELOG.md +56 -0
  6. data/Gemfile +15 -0
  7. data/LICENSE.TXT +20 -0
  8. data/README.md +284 -0
  9. data/Rakefile +8 -0
  10. data/gemfiles/Gemfile.mongoid-6 +14 -0
  11. data/gemfiles/Gemfile.mongoid-7 +14 -0
  12. data/lib/mongoid/multitenancy/document.rb +176 -0
  13. data/lib/mongoid/multitenancy/validators/tenancy.rb +36 -0
  14. data/lib/mongoid/multitenancy/validators/tenant_uniqueness.rb +67 -0
  15. data/lib/mongoid/multitenancy/version.rb +6 -0
  16. data/lib/mongoid/multitenancy.rb +34 -0
  17. data/lib/mongoid-multitenancy-forked.rb +1 -0
  18. data/mongoid-multitenancy-forked.gemspec +19 -0
  19. data/spec/immutable_spec.rb +46 -0
  20. data/spec/indexable_spec.rb +70 -0
  21. data/spec/inheritance_spec.rb +22 -0
  22. data/spec/mandatory_spec.rb +116 -0
  23. data/spec/models/account.rb +5 -0
  24. data/spec/models/immutable.rb +15 -0
  25. data/spec/models/indexable.rb +61 -0
  26. data/spec/models/mandatory.rb +15 -0
  27. data/spec/models/mutable.rb +15 -0
  28. data/spec/models/mutable_child.rb +9 -0
  29. data/spec/models/no_scopable.rb +11 -0
  30. data/spec/models/optional.rb +15 -0
  31. data/spec/models/optional_exclude.rb +15 -0
  32. data/spec/mongoid-multitenancy_spec.rb +37 -0
  33. data/spec/mutable_child_spec.rb +46 -0
  34. data/spec/mutable_spec.rb +50 -0
  35. data/spec/optional_exclude_spec.rb +71 -0
  36. data/spec/optional_spec.rb +207 -0
  37. data/spec/scopable_spec.rb +29 -0
  38. data/spec/spec_helper.rb +44 -0
  39. data/spec/support/mongoid_matchers.rb +17 -0
  40. data/spec/support/shared_examples.rb +80 -0
  41. 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
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ Gemfile*.lock
20
+
21
+ .idea
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --order random
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.3.0
5
+ - 2.4.1
6
+ gemfile:
7
+ - gemfiles/Gemfile.mongoid-6
8
+ - gemfiles/Gemfile.mongoid-7
9
+ - Gemfile
10
+ services:
11
+ - mongodb
12
+ sudo: false
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 [![Build Status](https://api.travis-ci.org/PerfectMemory/mongoid-multitenancy.png?branch=master)](https://travis-ci.org/PerfectMemory/mongoid-multitenancy) [![Coverage Status](https://coveralls.io/repos/github/PerfectMemory/mongoid-multitenancy/badge.svg?branch=master)](https://coveralls.io/github/PerfectMemory/mongoid-multitenancy?branch=master) [![Code Climate](https://codeclimate.com/github/PerfectMemory/mongoid-multitenancy.png)](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,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ YARD::Rake::YardocTask.new
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -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