mongoid-multitenancy 0.4.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a20d8d26c776318f1eb6407d0dc19017a721cdb
4
- data.tar.gz: 1404d16b8b568ebf9a7744ce7f6fa5c5338fa91b
3
+ metadata.gz: 95438e3e7adc7dc21d06a0deec16979183b37738
4
+ data.tar.gz: 61ce79abafb106d53af8dbf35c74523a04b43a31
5
5
  SHA512:
6
- metadata.gz: bed65acf48172bfaac61012ebdb5bc67bb73e96500835273e7ef45743cc7e77bddd5a32ed4b2ed52f11441e735f484311d1a192047a2b594c11b07e4ba4e1dce
7
- data.tar.gz: 1271fa93a141b513d1d5587b6d4bd7ce4c0b294635d243292f35cc62cfe60349abe78b499ba689bc7913868746cb1ee6a91400d4a5ecf7bb0e1a691bf196db5c
6
+ metadata.gz: 58cf2892dd86cd822f1fc436e745e53ffd128680164226323e5e32ac328bd14db877cd990ea29e01db130e4155b205a1de1a9b6cd040c7bc0381d1dfd89dc72a
7
+ data.tar.gz: 3ac676cd7c0df46eb10a703967f9375d51674f6a61f3a8ae637ea8cd7562b6361905def86aa52269af8dfcdfc1cbd29a9e4ea2eee8665ff35a1341207247d7fa
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --order random
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', '~> 4.0'
3
+ gem 'mongoid', '~> 5.0'
4
4
 
5
5
  gem 'rake', '~> 10.0'
6
6
 
7
7
  group :test do
8
- gem 'coveralls', :require => false
9
- gem 'rspec', '~> 2.12'
8
+ gem 'coveralls', require: false
9
+ gem 'rspec', '~> 3.1'
10
10
  gem 'yard', '~> 0.8'
11
- gem 'mongoid-rspec', '~> 1.5'
12
- gem 'database_cleaner', '~> 1.0'
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 [![Build Status](https://travis-ci.org/PerfectMemory/mongoid-multitenancy.png?branch=master)](https://travis-ci.org/PerfectMemory/mongoid-multitenancy.png?branch=master) [![Coverage Status](https://coveralls.io/repos/PerfectMemory/mongoid-multitenancy/badge.png?branch=master)](https://coveralls.io/r/PerfectMemory/mongoid-multitenancy) [![Code Climate](https://codeclimate.com/github/PerfectMemory/mongoid-multitenancy.png)](https://codeclimate.com/github/PerfectMemory/mongoid-multitenancy) [![Dependency Status](https://gemnasium.com/PerfectMemory/mongoid-multitenancy.png)](https://gemnasium.com/PerfectMemory/mongoid-multitenancy)
1
+ # mongoid-multitenancy [![Build Status](https://travis-ci.org/PerfectMemory/mongoid-multitenancy.png?branch=master)](https://travis-ci.org/PerfectMemory/mongoid-multitenancy) [![Coverage Status](https://coveralls.io/repos/PerfectMemory/mongoid-multitenancy/badge.svg?branch=master&service=github)](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) [![Dependency Status](https://gemnasium.com/PerfectMemory/mongoid-multitenancy.png)](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 in adding multi-tenancy to your app with acts_as_tenant:
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
- * :class_name, etc. : all the other options will be passed to the mongoid relation
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**. And that means too that **the tenant must be set manually**.
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: nil>
131
+ article.save # => <#Article _id: 50ca04b86c82bfc125000044, title: 'New blog', client_id: 50ca04b86c82bfc125000025>
129
132
 
130
- # tenant needs to be set manually
131
- article.tenant = client_instance
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 will automatically add the tenant foreign key in the scope list for each of uniqueness validators in order
160
- to avoid to redefine all your validators.
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(:client)
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
- validates_uniqueness_of :slug # => :scope => client_id is added automatically
200
+ validates_tenant_uniqueness_of :slug
172
201
  end
173
202
  ```
174
203
 
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require 'yard'
5
5
  YARD::Rake::YardocTask.new
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task :default => :spec
8
+ task default: :spec
@@ -1,15 +1,14 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'mongoid', '~> 4.0.alpha'
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', '~> 2.12'
9
+ gem 'rspec', '~> 3.1'
10
10
  gem 'yard', '~> 0.8'
11
- gem 'mongoid-rspec', '~> 1.5'
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.0'
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', '~> 2.12'
9
+ gem 'rspec', '~> 3.1'
10
10
  gem 'yard', '~> 0.8'
11
- gem 'mongoid-rspec', '~> 1.5'
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
@@ -1,17 +1,13 @@
1
- require "mongoid"
2
- require "mongoid/multitenancy/document"
3
- require "mongoid/multitenancy/version"
4
- require "mongoid/validators/tenant_validator"
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, "block required"
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, extract_association_options(options)
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 = extract_tenant_options(options)
38
+ self.tenant_options = multitenant_options
20
39
 
21
40
  # Validates the tenant field
22
- validates tenant_field, tenant: options
41
+ validates_tenancy_of tenant_field, multitenant_options
23
42
 
24
- # Set the default_scope to scope to current tenant
25
- default_scope lambda {
26
- if Multitenancy.current_tenant
27
- if options[:optional]
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
- # Redefine 'validates_with' to add the tenant scope when using a UniquenessValidator
48
- def validates_with(*args, &block)
49
- if !self.tenant_options[:optional]
50
- validator = if Mongoid::Multitenancy.mongoid4?
51
- Validatable::UniquenessValidator
52
- else
53
- Validations::UniquenessValidator
54
- end
55
-
56
- if args.first.ancestors.include?(validator)
57
- args.last[:scope] = Array(args.last[:scope]) << self.tenant_field
58
- end
59
- end
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
- super(*args, &block)
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
- spec = { self.tenant_field => 1 }.merge(spec) if self.tenant_options[:full_indexes]
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
- def extract_association_options(options)
78
- new_options = {}
101
+ # @private
102
+ def build_options(options)
103
+ assoc_options = {}
104
+ multitenant_options = {}
79
105
 
80
106
  options.each do |k, v|
81
- new_options[k] = v unless MULTITENANCY_OPTIONS.include?(k)
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
- new_options
114
+ [assoc_options, multitenant_options]
85
115
  end
86
116
 
87
- def extract_tenant_options(options)
88
- new_options = {}
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
- options.each do |k, v|
91
- new_options[k] = v if MULTITENANCY_OPTIONS.include?(k)
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
- new_options
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