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.
@@ -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