mongoid-multitenancy 0.4.4 → 1.0.0

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.
@@ -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) and 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 and Mongoid::Multitenancy.current_tenant and value != Mongoid::Multitenancy.current_tenant.id
26
+ object.errors.add(attribute, "not authorized")
27
+ end
28
+
29
+ # Optional Check
30
+ if !options[:optional] and value.nil?
31
+ object.errors.add(attribute, 'is mandatory')
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,72 @@
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
+ add_tenant_criterion(criteria, klass, document)
40
+
41
+ criteria = criteria.merge(options[:conditions].call) if options[:conditions]
42
+
43
+ if Mongoid::VERSION.start_with?('4')
44
+ if criteria.with(persistence_options(criteria)).exists?
45
+ add_error(document, attribute, value)
46
+ end
47
+ else
48
+ if criteria.with(criteria.persistence_options).read(mode: :primary).exists?
49
+ add_error(document, attribute, value)
50
+ end
51
+ end
52
+ end
53
+
54
+ # Create the validation criteria for a tenant model.
55
+ #
56
+ # @api private
57
+ def add_tenant_criterion(criteria, base, document)
58
+ tenant_value = document.send(base.tenant_field.to_sym)
59
+
60
+ if document.class.tenant_options[:optional]
61
+ if tenant_value
62
+ criteria.selector.update(criterion(document, base.tenant_field, {'$in' => [tenant_value, nil].mongoize}))
63
+ end
64
+ else
65
+ criteria.selector.update(criterion(document, base.tenant_field, tenant_value.mongoize))
66
+ end
67
+
68
+ criteria
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,5 +1,6 @@
1
1
  module Mongoid
2
2
  module Multitenancy
3
- VERSION = "0.4.4"
3
+ # Version
4
+ VERSION = '1.0.0'
4
5
  end
5
6
  end
@@ -2,18 +2,18 @@
2
2
  require File.expand_path('../lib/mongoid/multitenancy/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Aymeric Brisse"]
6
- gem.email = ["aymeric.brisse@mperfect-memory.com"]
7
- gem.description = %q{MultiTenancy with Mongoid}
8
- gem.summary = %q{Support of a multi-tenant database with Mongoid}
9
- gem.homepage = "https://github.com/PerfectMemory/mongoid-multitenancy"
5
+ gem.authors = ['Aymeric Brisse']
6
+ gem.email = ['aymeric.brisse@mperfect-memory.com']
7
+ gem.description = 'MultiTenancy with Mongoid'
8
+ gem.summary = 'Support of a multi-tenant database with Mongoid'
9
+ gem.homepage = 'https://github.com/PerfectMemory/mongoid-multitenancy'
10
10
  gem.license = 'MIT'
11
11
  gem.files = `git ls-files`.split($\)
12
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
- gem.name = "mongoid-multitenancy"
15
- gem.require_paths = ["lib"]
14
+ gem.name = 'mongoid-multitenancy'
15
+ gem.require_paths = ['lib']
16
16
  gem.version = Mongoid::Multitenancy::VERSION
17
17
 
18
- gem.add_dependency('mongoid', '>= 3.0')
18
+ gem.add_dependency('mongoid', '>= 4.0')
19
19
  end
@@ -1,34 +1,46 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Immutable do
4
+ let(:client) do
5
+ Account.create!(name: 'client')
6
+ end
4
7
 
5
- it_behaves_like "a tenantable model"
8
+ let(:another_client) do
9
+ Account.create!(name: 'another client')
10
+ end
6
11
 
7
- let(:client) { Account.create!(:name => "client") }
8
- let(:another_client) { Account.create!(:name => "another client") }
12
+ let(:item) do
13
+ Immutable.new(title: 'title X', slug: 'page-x')
14
+ end
9
15
 
10
- describe "#valid?" do
11
- before { Mongoid::Multitenancy.current_tenant = client; }
12
- after { Mongoid::Multitenancy.current_tenant = nil }
16
+ it_behaves_like 'a tenantable model'
13
17
 
14
- let(:item) { Immutable.new(:title => "title X", :slug => "page-x") }
18
+ describe '#valid?' do
19
+ before do
20
+ Mongoid::Multitenancy.current_tenant = client
21
+ end
15
22
 
16
- it_behaves_like "a tenant validator"
23
+ context 'when the tenant has not changed' do
24
+ before do
25
+ item.save!
26
+ end
17
27
 
18
- context "when the tenant has not changed" do
19
- before { item.save! }
20
- it 'should be valid' do
21
- item.title = "title X (2)"
22
- item.should be_valid
28
+ it 'is valid' do
29
+ item.title = 'title X (2)'
30
+ expect(item).to be_valid
23
31
  end
24
32
  end
25
33
 
26
- context "when the tenant has changed" do
27
- before { item.save!; Mongoid::Multitenancy.current_tenant = another_client }
28
- it 'should not be valid' do
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
29
41
  item.client = another_client
30
- item.should_not be_valid
42
+ expect(item).not_to be_valid
31
43
  end
32
44
  end
33
45
  end
34
- end
46
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe 'tenant' do
4
4
 
5
5
  let(:client) do
6
- Account.create!(:name => "client")
6
+ Account.create!(name: 'client')
7
7
  end
8
8
 
9
9
  before do
@@ -12,26 +12,26 @@ describe 'tenant' do
12
12
 
13
13
  context 'without index: true' do
14
14
  it 'does not create an index' do
15
- Immutable.should_not have_index_for(:client_id => 1)
15
+ expect(Immutable).not_to have_index_for(:client_id => 1)
16
16
  end
17
17
  end
18
18
 
19
19
  context 'with index: true' do
20
20
  it 'creates an index' do
21
- Indexable.should have_index_for(:client_id => 1)
21
+ expect(Indexable).to have_index_for(:client_id => 1)
22
22
  end
23
23
  end
24
24
 
25
25
  context 'with full_indexes: true' do
26
26
  it 'add the tenant field on each index' do
27
- Immutable.should have_index_for(:client_id => 1, :title => 1)
27
+ expect(Immutable).to have_index_for(:client_id => 1, title: 1)
28
28
  end
29
29
  end
30
30
 
31
31
  context 'with full_indexes: false' do
32
32
  it 'does not add the tenant field on each index' do
33
- Indexable.should_not have_index_for(:client_id => 1, :title => 1)
33
+ expect(Indexable).not_to have_index_for(:client_id => 1, title: 1)
34
34
  end
35
35
  end
36
36
 
37
- end
37
+ end
@@ -1,17 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Inheritance' do
4
+ let(:client) do
5
+ Account.create!(name: 'client')
6
+ end
4
7
 
5
- let(:client) { Account.create!(:name => "client") }
8
+ before do
9
+ Mongoid::Multitenancy.current_tenant = client
10
+ end
6
11
 
7
- describe "class" do
8
- it 'should use inheritance pattern' do
9
- MutableChild.create!(:title => "title X", :slug => "page-x")
12
+ describe 'class' do
13
+ it 'uses inheritance pattern' do
14
+ MutableChild.create!(title: 'title X', slug: 'page-x')
10
15
  expect(Mutable.last).to be_a MutableChild
11
16
  end
12
17
 
13
- it 'should keep options' do
14
- AnotherMutableChild.new(:title => "title X", :slug => "page-x").should be_valid
18
+ it 'keeps options' do
19
+ expect(AnotherMutableChild.new(title: 'title X', slug: 'page-x')).to be_valid
15
20
  end
16
21
  end
17
22
  end
@@ -2,81 +2,107 @@ require 'spec_helper'
2
2
 
3
3
  describe Mandatory do
4
4
 
5
- it_behaves_like "a tenantable model"
6
- it { should validate_uniqueness_of(:slug).scoped_to(:client_id) }
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
7
12
 
8
- let(:client) { Account.create!(:name => "client") }
9
- let(:another_client) { Account.create!(:name => "another client") }
13
+ let(:item) do
14
+ Mandatory.new(title: 'title X', slug: 'page-x')
15
+ end
16
+
17
+ it_behaves_like 'a tenantable model'
18
+ it { is_expected.to validate_tenant_uniqueness_of(:slug) }
10
19
 
11
- describe ".default_scope" do
12
- before {
13
- Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(:title => "title X", :slug => "article-x") }
14
- Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(:title => "title Y", :slug => "article-y") }
15
- }
20
+ describe '.default_scope' do
21
+ before do
22
+ Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(title: 'title X', slug: 'article-x') }
23
+ Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(title: 'title Y', slug: 'article-y') }
24
+ end
16
25
 
17
- context "with a current tenant" do
18
- before { Mongoid::Multitenancy.current_tenant = another_client }
19
- after { Mongoid::Multitenancy.current_tenant = nil }
26
+ context 'with a current tenant' do
27
+ before do
28
+ Mongoid::Multitenancy.current_tenant = another_client
29
+ end
20
30
 
21
- it "should filter on the current tenant" do
22
- Mandatory.all.to_a.should =~ [@itemY]
31
+ it 'filters on the current tenant' do
32
+ expect(Mandatory.all.to_a).to match_array [@itemY]
23
33
  end
24
34
  end
25
35
 
26
- context "without a current tenant" do
27
- before { Mongoid::Multitenancy.current_tenant = nil }
36
+ context 'without a current tenant' do
37
+ before do
38
+ Mongoid::Multitenancy.current_tenant = nil
39
+ end
28
40
 
29
- it "should not filter on any tenant" do
30
- Mandatory.all.to_a.should =~ [@itemX, @itemY]
41
+ it 'does not filter on any tenant' do
42
+ expect(Mandatory.all.to_a).to match_array [@itemX, @itemY]
31
43
  end
32
44
  end
33
45
  end
34
46
 
35
- describe "#delete_all" do
36
- before {
37
- Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(:title => "title X", :slug => "article-x") }
38
- Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(:title => "title Y", :slug => "article-y") }
39
- }
47
+ describe '#delete_all' do
48
+ before do
49
+ Mongoid::Multitenancy.with_tenant(client) { @itemX = Mandatory.create!(title: 'title X', slug: 'article-x') }
50
+ Mongoid::Multitenancy.with_tenant(another_client) { @itemY = Mandatory.create!(title: 'title Y', slug: 'article-y') }
51
+ end
40
52
 
41
- context "with a current tenant" do
42
- it "should only delete the current tenant" do
53
+ context 'with a current tenant' do
54
+ it 'only deletes the current tenant' do
43
55
  Mongoid::Multitenancy.with_tenant(another_client) { Mandatory.delete_all }
44
- Mandatory.all.to_a.should =~ [@itemX]
56
+ expect(Mandatory.all.to_a).to match_array [@itemX]
45
57
  end
46
58
  end
47
59
 
48
- context "without a current tenant" do
49
- it "should delete all the items" do
60
+ context 'without a current tenant' do
61
+ it 'deletes all the items' do
50
62
  Mandatory.delete_all
51
- Mandatory.all.to_a.should be_empty
63
+ expect(Mandatory.all.to_a).to be_empty
52
64
  end
53
65
  end
54
66
  end
55
67
 
56
- describe "#valid?" do
57
- after { Mongoid::Multitenancy.current_tenant = nil }
68
+ describe '#valid?' do
69
+ context 'with a tenant' do
70
+ before do
71
+ item.client = client
72
+ end
73
+
74
+ it 'is valid' do
75
+ expect(item).to be_valid
76
+ end
58
77
 
59
- let(:item) { Mandatory.new(:title => "title X", :slug => "page-x") }
78
+ context 'with a uniqueness constraint' do
79
+ let(:duplicate) do
80
+ Mandatory.new(title: 'title Y', slug: 'page-x')
81
+ end
60
82
 
61
- it_behaves_like "a tenant validator"
83
+ before do
84
+ item.save!
85
+ end
62
86
 
63
- context "with a current tenant" do
64
- before { Mongoid::Multitenancy.current_tenant = client }
87
+ it 'does not allow duplicates on the same tenant' do
88
+ expect(duplicate).not_to be_valid
89
+ end
65
90
 
66
- it "should set the client field" do
67
- item.valid?
68
- item.client.should eq client
91
+ it 'allow duplicates on a different same tenant' do
92
+ Mongoid::Multitenancy.with_tenant(another_client) do
93
+ expect(duplicate).to be_valid
94
+ end
95
+ end
69
96
  end
70
97
  end
71
98
 
72
- context "without a current tenant" do
73
- it "should not set the client field" do
74
- item.valid?
75
- item.client.should be_nil
99
+ context 'without a tenant' do
100
+ before do
101
+ item.client = nil
76
102
  end
77
103
 
78
- it "should be invalid" do
79
- item.should_not be_valid
104
+ it 'is not valid' do
105
+ expect(item).not_to be_valid
80
106
  end
81
107
  end
82
108
  end
@@ -2,14 +2,14 @@ class Immutable
2
2
  include Mongoid::Document
3
3
  include Mongoid::Multitenancy::Document
4
4
 
5
- tenant(:client, :class_name => 'Account', :immutable => true)
5
+ tenant(:client, class_name: 'Account', immutable: true)
6
6
 
7
7
  field :slug, :type => String
8
8
  field :title, :type => String
9
9
 
10
- validates_uniqueness_of :slug
10
+ validates_tenant_uniqueness_of :slug
11
11
  validates_presence_of :slug
12
12
  validates_presence_of :title
13
13
 
14
- index({ :title => 1 })
14
+ index({ title: 1 })
15
15
  end