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
@@ -0,0 +1,67 @@
1
+ module Mongoid
2
+ module Multitenancy
3
+ # Validates whether or not a field is unique against the documents in the
4
+ # database.
5
+ #
6
+ # @example Define the tenant uniqueness validator.
7
+ #
8
+ # class Person
9
+ # include Mongoid::Document
10
+ # include Mongoid::Multitenancy::Document
11
+ # field :title
12
+ # tenant :client
13
+ #
14
+ # validates_tenant_uniqueness_of :title
15
+ # end
16
+ #
17
+ # It is also possible to limit the uniqueness constraint to a set of
18
+ # records matching certain conditions:
19
+ # class Person
20
+ # include Mongoid::Document
21
+ # include Mongoid::Multitenancy::Document
22
+ # field :title
23
+ # field :active, type: Boolean
24
+ # tenant :client
25
+ #
26
+ # validates_tenant_uniqueness_of :title, conditions: -> {where(active: true)}
27
+ # end
28
+ class TenantUniquenessValidator < Mongoid::Validatable::UniquenessValidator
29
+ # Validate a tenant root document.
30
+ def validate_root(document, attribute, value)
31
+ klass = document.class
32
+
33
+ while klass.superclass.respond_to?(:validators) && klass.superclass.validators.include?(self)
34
+ klass = klass.superclass
35
+ end
36
+ criteria = create_criteria(klass, document, attribute, value)
37
+
38
+ # <<Add the tenant Criteria>>
39
+ criteria = with_tenant_criterion(criteria, klass, document)
40
+ criteria = criteria.merge(options[:conditions].call) if options[:conditions]
41
+
42
+ if criteria.read(mode: :primary).exists?
43
+ add_error(document, attribute, value)
44
+ end
45
+ end
46
+
47
+ # Add the scope criteria for a tenant model criteria.
48
+ #
49
+ # @api private
50
+ def with_tenant_criterion(criteria, base, document)
51
+ item = base.tenant_field.to_sym
52
+ name = document.database_field_name(item)
53
+ tenant_value = document.attributes[name]
54
+
55
+ if document.class.tenant_options[:optional] && !options[:exclude_shared]
56
+ if tenant_value
57
+ criteria = criteria.where(:"#{item}".in => [tenant_value, nil])
58
+ end
59
+ else
60
+ criteria = criteria.where(item => tenant_value)
61
+ end
62
+
63
+ criteria
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,6 @@
1
+ module Mongoid
2
+ module Multitenancy
3
+ # Version
4
+ VERSION = '1.0.1'.freeze
5
+ end
6
+ end
@@ -0,0 +1,34 @@
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'
6
+
7
+ module Mongoid
8
+ module Multitenancy
9
+ class << self
10
+ # Set the current tenant. Make it Thread aware
11
+ def current_tenant=(tenant)
12
+ Thread.current[:current_tenant] = tenant
13
+ end
14
+
15
+ # Returns the current tenant
16
+ def current_tenant
17
+ Thread.current[:current_tenant]
18
+ end
19
+
20
+ # Affects a tenant temporary for a block execution
21
+ def with_tenant(tenant, &block)
22
+ raise ArgumentError, 'block required' if block.nil?
23
+
24
+ begin
25
+ old_tenant = current_tenant
26
+ self.current_tenant = tenant
27
+ yield
28
+ ensure
29
+ self.current_tenant = old_tenant
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ require 'mongoid/multitenancy'
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/mongoid/multitenancy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Aymeric Brisse']
6
+ gem.email = ['aymeric.brisse@mperfect-memory.com']
7
+ gem.description = 'MultiTenancy with Mongoid - This is a fork for mongoid-multitenancy just to allow mongoid version < 10'
8
+ gem.summary = 'Support of a multi-tenant database with Mongoid'
9
+ gem.homepage = 'https://github.com/PerfectMemory/mongoid-multitenancy'
10
+ gem.license = 'MIT'
11
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = 'mongoid-multitenancy-forked'
15
+ gem.require_paths = ['lib']
16
+ gem.version = Mongoid::Multitenancy::VERSION
17
+
18
+ gem.add_dependency('mongoid', '>= 6', '< 10')
19
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Immutable 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
+ Immutable.new(title: 'title X', slug: 'page-x')
14
+ end
15
+
16
+ it_behaves_like 'a tenantable model'
17
+
18
+ describe '#valid?' do
19
+ before do
20
+ Mongoid::Multitenancy.current_tenant = client
21
+ end
22
+
23
+ context 'when the tenant has not changed' do
24
+ before do
25
+ item.save!
26
+ end
27
+
28
+ it 'is valid' do
29
+ item.title = 'title X (2)'
30
+ expect(item).to be_valid
31
+ end
32
+ end
33
+
34
+ context 'when the tenant has changed' do
35
+ before do
36
+ item.save!
37
+ Mongoid::Multitenancy.current_tenant = another_client
38
+ end
39
+
40
+ it 'is not valid' do
41
+ item.tenant = another_client
42
+ expect(item).not_to be_valid
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'tenant' do
4
+ let(:client) do
5
+ Account.create!(name: 'client')
6
+ end
7
+
8
+ before do
9
+ Mongoid::Multitenancy.current_tenant = client
10
+ end
11
+
12
+ describe 'tenant full_indexes option' do
13
+ context 'without index option' do
14
+ it 'does not create an index' do
15
+ expect(IndexableWithoutIndex).not_to have_index_for(tenant_id: 1)
16
+ end
17
+ end
18
+
19
+ context 'with index: false' do
20
+ it 'does not create an index' do
21
+ expect(IndexableWithIndexFalse).not_to have_index_for(tenant_id: 1)
22
+ end
23
+ end
24
+
25
+ context 'with index: true' do
26
+ it 'creates an index' do
27
+ expect(IndexableWithIndexTrue).to have_index_for(tenant_id: 1)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'index full_index option' do
33
+ context 'without tenant full_indexes option specified' do
34
+ it 'adds the tenant field on each index' do
35
+ expect(IndexableWithoutFullIndexes).to have_index_for(tenant_id: 1, title: 1)
36
+ end
37
+
38
+ it 'adds the tenant field on the index with full_index: true' do
39
+ expect(IndexableWithoutFullIndexes).to have_index_for(tenant_id: 1, name: 1)
40
+ end
41
+
42
+ it 'does not add the tenant field on the index with full_index: false' do
43
+ expect(IndexableWithoutFullIndexes).not_to have_index_for(tenant_id: 1, slug: 1)
44
+ expect(IndexableWithoutFullIndexes).to have_index_for(slug: 1)
45
+ end
46
+ end
47
+
48
+ context 'with full_indexes: true' do
49
+ it 'adds the tenant field on each index' do
50
+ expect(IndexableWithFullIndexesTrue).to have_index_for(tenant_id: 1, title: 1)
51
+ end
52
+
53
+ it 'does not add the tenant field on the index with full_index: false' do
54
+ expect(IndexableWithFullIndexesTrue).not_to have_index_for(tenant_id: 1, name: 1)
55
+ expect(IndexableWithFullIndexesTrue).to have_index_for(name: 1)
56
+ end
57
+ end
58
+
59
+ context 'with full_indexes: false' do
60
+ it 'does not add the tenant field on each index' do
61
+ expect(IndexableWithFullIndexesFalse).not_to have_index_for(tenant_id: 1, title: 1)
62
+ expect(IndexableWithFullIndexesFalse).to have_index_for(title: 1)
63
+ end
64
+
65
+ it 'does add the tenant field on the index with full_index: true' do
66
+ expect(IndexableWithFullIndexesFalse).to have_index_for(tenant_id: 1, name: 1)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Inheritance' do
4
+ let(:client) do
5
+ Account.create!(name: 'client')
6
+ end
7
+
8
+ before do
9
+ Mongoid::Multitenancy.current_tenant = client
10
+ end
11
+
12
+ describe 'class' do
13
+ it 'uses inheritance pattern' do
14
+ MutableChild.create!(title: 'title X', slug: 'page-x')
15
+ expect(Mutable.last).to be_a MutableChild
16
+ end
17
+
18
+ it 'keeps options' do
19
+ expect(AnotherMutableChild.new(title: 'title X', slug: 'page-x')).to be_valid
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mandatory 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
+ Mandatory.new(title: 'title X', slug: 'page-x')
14
+ end
15
+
16
+ it_behaves_like 'a tenantable model'
17
+ it { is_expected.to validate_tenant_uniqueness_of(:slug) }
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
+
31
+ describe '.default_scope' do
32
+ before do
33
+ Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(title: 'title X', slug: 'article-x') }
34
+ Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(title: 'title Y', slug: 'article-y') }
35
+ end
36
+
37
+ context 'with a current tenant' do
38
+ before do
39
+ Mongoid::Multitenancy.current_tenant = another_client
40
+ end
41
+
42
+ it 'filters on the current tenant' do
43
+ expect(Mandatory.all.to_a).to match_array [@itemY]
44
+ end
45
+ end
46
+
47
+ context 'without a current tenant' do
48
+ before do
49
+ Mongoid::Multitenancy.current_tenant = nil
50
+ end
51
+
52
+ it 'does not filter on any tenant' do
53
+ expect(Mandatory.all.to_a).to match_array [@itemX, @itemY]
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#delete_all' do
59
+ before do
60
+ Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(title: 'title X', slug: 'article-x') }
61
+ Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(title: 'title Y', slug: 'article-y') }
62
+ end
63
+
64
+ context 'with a current tenant' do
65
+ it 'only deletes the current tenant' do
66
+ Mongoid::Multitenancy.with_tenant(another_client) { Mandatory.delete_all }
67
+ expect(Mandatory.all.to_a).to match_array [@itemX]
68
+ end
69
+ end
70
+
71
+ context 'without a current tenant' do
72
+ it 'deletes all the items' do
73
+ Mandatory.delete_all
74
+ expect(Mandatory.all.to_a).to be_empty
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#valid?' do
80
+ context 'with a tenant' do
81
+ before do
82
+ Mongoid::Multitenancy.current_tenant = client
83
+ end
84
+
85
+ it 'is valid' do
86
+ expect(item).to be_valid
87
+ end
88
+
89
+ context 'with a uniqueness constraint' do
90
+ let(:duplicate) do
91
+ Mandatory.new(title: 'title Y', slug: 'page-x')
92
+ end
93
+
94
+ before do
95
+ item.save!
96
+ end
97
+
98
+ it 'does not allow duplicates on the same tenant' do
99
+ expect(duplicate).not_to be_valid
100
+ end
101
+
102
+ it 'allow duplicates on a different same tenant' do
103
+ Mongoid::Multitenancy.with_tenant(another_client) do
104
+ expect(duplicate).to be_valid
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ context 'without a tenant' do
111
+ it 'is not valid' do
112
+ expect(item).not_to be_valid
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,5 @@
1
+ class Account
2
+ include Mongoid::Document
3
+
4
+ field :name, type: String
5
+ end
@@ -0,0 +1,15 @@
1
+ class Immutable
2
+ include Mongoid::Document
3
+ include Mongoid::Multitenancy::Document
4
+
5
+ tenant(:tenant, class_name: 'Account', immutable: true)
6
+
7
+ field :slug, type: String
8
+ field :title, type: String
9
+
10
+ validates_tenant_uniqueness_of :slug
11
+ validates_presence_of :slug
12
+ validates_presence_of :title
13
+
14
+ index(title: 1)
15
+ end
@@ -0,0 +1,61 @@
1
+ class IndexableWithoutIndex
2
+ include Mongoid::Document
3
+ include Mongoid::Multitenancy::Document
4
+
5
+ tenant :tenant, class_name: 'Account'
6
+ end
7
+
8
+ class IndexableWithIndexTrue
9
+ include Mongoid::Document
10
+ include Mongoid::Multitenancy::Document
11
+
12
+ tenant :tenant, class_name: 'Account', index: true
13
+ end
14
+
15
+ class IndexableWithIndexFalse
16
+ include Mongoid::Document
17
+ include Mongoid::Multitenancy::Document
18
+
19
+ tenant :tenant, class_name: 'Account', index: false
20
+ end
21
+
22
+ class IndexableWithoutFullIndexes
23
+ include Mongoid::Document
24
+ include Mongoid::Multitenancy::Document
25
+
26
+ field :title, type: String
27
+ field :name, type: String
28
+ field :slug, type: String
29
+
30
+ tenant :tenant, class_name: 'Account'
31
+
32
+ index(title: 1)
33
+ index({ name: 1 }, { full_index: true })
34
+ index({ slug: 1 }, { full_index: false })
35
+ end
36
+
37
+ class IndexableWithFullIndexesFalse
38
+ include Mongoid::Document
39
+ include Mongoid::Multitenancy::Document
40
+
41
+ field :title, type: String
42
+ field :name, type: String
43
+
44
+ tenant :tenant, class_name: 'Account', full_indexes: false
45
+
46
+ index(title: 1)
47
+ index({ name: 1 }, { full_index: true })
48
+ end
49
+
50
+ class IndexableWithFullIndexesTrue
51
+ include Mongoid::Document
52
+ include Mongoid::Multitenancy::Document
53
+
54
+ field :title, type: String
55
+ field :name, type: String
56
+
57
+ tenant :tenant, class_name: 'Account', full_indexes: true
58
+
59
+ index(title: 1)
60
+ index({ name: 1 }, { full_index: false })
61
+ end
@@ -0,0 +1,15 @@
1
+ class Mandatory
2
+ include Mongoid::Document
3
+ include Mongoid::Multitenancy::Document
4
+
5
+ tenant(:tenant, class_name: 'Account')
6
+
7
+ field :slug, type: String
8
+ field :title, type: String
9
+
10
+ validates_tenant_uniqueness_of :slug
11
+ validates_presence_of :slug
12
+ validates_presence_of :title
13
+
14
+ index(title: 1)
15
+ end
@@ -0,0 +1,15 @@
1
+ class Mutable
2
+ include Mongoid::Document
3
+ include Mongoid::Multitenancy::Document
4
+
5
+ tenant :tenant, class_name: 'Account', immutable: false, optional: false
6
+
7
+ field :slug, type: String
8
+ field :title, type: String
9
+
10
+ validates_tenant_uniqueness_of :slug
11
+ validates_presence_of :slug
12
+ validates_presence_of :title
13
+
14
+ index(title: 1)
15
+ end
@@ -0,0 +1,9 @@
1
+ require 'models/mutable'
2
+
3
+ class MutableChild < Mutable
4
+ field :random, type: String
5
+ end
6
+
7
+ class AnotherMutableChild < Mutable
8
+ field :random, type: String
9
+ end
@@ -0,0 +1,11 @@
1
+ class NoScopable
2
+ include Mongoid::Document
3
+ include Mongoid::Multitenancy::Document
4
+
5
+ tenant :tenant, class_name: 'Account', scopes: false
6
+
7
+ field :slug, type: String
8
+ field :title, type: String
9
+
10
+ index(title: 1)
11
+ end
@@ -0,0 +1,15 @@
1
+ class Optional
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
11
+ validates_presence_of :slug
12
+ validates_presence_of :title
13
+
14
+ index(title: 1)
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
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Multitenancy 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
+ before do
13
+ Mongoid::Multitenancy.current_tenant = client
14
+ end
15
+
16
+ describe '.with_tenant' do
17
+ it 'changes temporary the current tenant within the block' do
18
+ Mongoid::Multitenancy.with_tenant(another_client) do
19
+ expect(Mongoid::Multitenancy.current_tenant).to eq another_client
20
+ end
21
+ end
22
+
23
+ it 'restores the current tenant after the block' do
24
+ Mongoid::Multitenancy.with_tenant(another_client) { ; }
25
+ expect(Mongoid::Multitenancy.current_tenant).to eq client
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
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe MutableChild 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
+ MutableChild.new(title: 'title X', slug: 'page-x')
14
+ end
15
+
16
+ it_behaves_like 'a tenantable model'
17
+
18
+ describe '#valid?' do
19
+ before do
20
+ Mongoid::Multitenancy.current_tenant = client
21
+ end
22
+
23
+ context 'when the tenant has not changed' do
24
+ before do
25
+ item.save!
26
+ end
27
+
28
+ it 'is valid' do
29
+ item.title = 'title X (2)'
30
+ expect(item).to be_valid
31
+ end
32
+ end
33
+
34
+ context 'when the tenant has changed' do
35
+ before do
36
+ item.save!
37
+ Mongoid::Multitenancy.current_tenant = another_client
38
+ end
39
+
40
+ it 'is valid' do
41
+ item.tenant = another_client
42
+ expect(item).to be_valid
43
+ end
44
+ end
45
+ end
46
+ end