activerecord-multi-tenant 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Appraisals +12 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +151 -0
- data/README.md +88 -0
- data/Rakefile +7 -0
- data/activerecord-multi-tenant.gemspec +25 -0
- data/docker-compose.yml +18 -0
- data/gemfiles/rails_3.2.gemfile +7 -0
- data/gemfiles/rails_3.2.gemfile.lock +97 -0
- data/gemfiles/rails_4.2.gemfile +6 -0
- data/gemfiles/rails_4.2.gemfile.lock +110 -0
- data/gemfiles/rails_5.0.gemfile +6 -0
- data/gemfiles/rails_5.0.gemfile.lock +115 -0
- data/lib/activerecord-multi-tenant.rb +2 -0
- data/lib/activerecord-multi-tenant/controller_extensions.rb +22 -0
- data/lib/activerecord-multi-tenant/default_scope.rb +1 -1
- data/lib/activerecord-multi-tenant/migrations.rb +1 -1
- data/lib/activerecord-multi-tenant/model_extensions.rb +120 -0
- data/lib/activerecord-multi-tenant/multi_tenant.rb +33 -44
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/activerecord-multi-tenant/controller_extensions_spec.rb +30 -0
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +153 -0
- data/spec/activerecord-multi-tenant/record_callback_spec.rb +22 -0
- data/spec/activerecord-multi-tenant/record_finding_spec.rb +11 -0
- data/spec/activerecord-multi-tenant/record_modifications_spec.rb +20 -0
- data/spec/database.yml +9 -0
- data/spec/schema.rb +104 -0
- data/spec/spec_helper.rb +43 -0
- metadata +44 -5
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Account
|
4
|
+
attr_accessor :name
|
5
|
+
end
|
6
|
+
|
7
|
+
class ApplicationController < ActionController::Base
|
8
|
+
include Rails.application.routes.url_helpers
|
9
|
+
set_current_tenant_through_filter
|
10
|
+
before_action :your_method_that_finds_the_current_tenant
|
11
|
+
|
12
|
+
def your_method_that_finds_the_current_tenant
|
13
|
+
current_account = Account.new
|
14
|
+
current_account.name = 'account1'
|
15
|
+
set_current_tenant(current_account)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ApplicationController, type: :controller do
|
20
|
+
controller do
|
21
|
+
def index
|
22
|
+
render body: 'custom called'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'Finds the correct tenant using the filter command' do
|
27
|
+
get :index
|
28
|
+
expect(MultiTenant.current_tenant.name).to eq 'account1'
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultiTenant do
|
4
|
+
after { MultiTenant.current_tenant = nil }
|
5
|
+
|
6
|
+
# Setting and getting
|
7
|
+
describe 'Setting the current tenant' do
|
8
|
+
before { MultiTenant.current_tenant = :foo }
|
9
|
+
it { MultiTenant.current_tenant == :foo }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'is_scoped_as_tenant should return the correct value when true' do
|
13
|
+
it {expect(Project.respond_to?(:scoped_by_tenant?)).to eq(true)}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'is_scoped_as_tenant should return the correct value when false' do
|
17
|
+
it {expect(UnscopedModel.respond_to?(:scoped_by_tenant?)).to eq(false)}
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'immutability' do
|
21
|
+
before do
|
22
|
+
@account = Account.create! name: 'foo'
|
23
|
+
@project = @account.projects.create! name: 'bar'
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'tenant_id should be immutable, if already set' do
|
27
|
+
it { expect { @project.account_id = @account.id + 1 }.to raise_error(MultiTenant::TenantIsImmutable) }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'setting tenant_id to the same value should not error' do
|
31
|
+
it { expect { @project.account_id = @account.id }.not_to raise_error }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'setting tenant_id to a string with same to_i value should not error' do
|
35
|
+
it { expect { @project.account_id = @account.id.to_s }.not_to raise_error }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'tenant_id should auto populate after initialization' do
|
40
|
+
before do
|
41
|
+
@account = Account.create! name: 'foo'
|
42
|
+
MultiTenant.current_tenant = @account
|
43
|
+
end
|
44
|
+
it {expect(Project.new.account_id).to eq(@account.id)}
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'Handles custom partition_key on tenant model' do
|
48
|
+
before do
|
49
|
+
@account = Account.create! name: 'foo'
|
50
|
+
MultiTenant.current_tenant = @account
|
51
|
+
@custom_partition_key_task = CustomPartitionKeyTask.create! name: 'foo'
|
52
|
+
end
|
53
|
+
|
54
|
+
it { expect(@custom_partition_key_task.account).to eq(@account) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Scoping models
|
58
|
+
describe 'Project.all should be scoped to the current tenant if set' do
|
59
|
+
before do
|
60
|
+
@account1 = Account.create! name: 'foo'
|
61
|
+
@account2 = Account.create! name: 'bar'
|
62
|
+
|
63
|
+
@project1 = @account1.projects.create! name: 'foobar'
|
64
|
+
@project2 = @account2.projects.create! name: 'baz'
|
65
|
+
|
66
|
+
MultiTenant.current_tenant = @account1
|
67
|
+
@projects = Project.all
|
68
|
+
end
|
69
|
+
|
70
|
+
it { expect(@projects.length).to eq(1) }
|
71
|
+
it { expect(@projects).to eq([@project1]) }
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'Querying the tenant from a scoped model with a tenant set' do
|
75
|
+
before do
|
76
|
+
@account = Account.create! name: 'foo'
|
77
|
+
@project = @account.projects.create! name: 'foobar'
|
78
|
+
MultiTenant.current_tenant= @account1
|
79
|
+
end
|
80
|
+
|
81
|
+
it { @project.account }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Associations
|
85
|
+
describe 'Associations should be correctly scoped by current tenant' do
|
86
|
+
before do
|
87
|
+
@account = Account.create! name: 'foo'
|
88
|
+
@project = Project.create! name: 'foobar', account: @account
|
89
|
+
|
90
|
+
MultiTenant.current_tenant = @account
|
91
|
+
@task = @project.tasks.create! name: 'baz'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should correctly set the tenant on the task created with current_tenant set' do
|
95
|
+
expect(@task.account).to eq(@account)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "It should be possible to use aliased associations" do
|
100
|
+
before do
|
101
|
+
@account = Account.create! name: 'baz'
|
102
|
+
MultiTenant.current_tenant = @account
|
103
|
+
end
|
104
|
+
|
105
|
+
it { expect(AliasedTask.create(:name => 'foo', :project_alias => @project2).valid?).to eq(true) }
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "It should be possible to use associations with partition_key from polymorphic" do
|
109
|
+
before do
|
110
|
+
@account = Account.create!(name: 'foo')
|
111
|
+
MultiTenant.current_tenant = @account
|
112
|
+
@project = Project.create!(name: 'project', account: @account)
|
113
|
+
@comment = Comment.new commentable: @project, account: @account
|
114
|
+
end
|
115
|
+
|
116
|
+
it { expect(@comment.save!).to eq(true) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# ::with
|
120
|
+
describe "::with" do
|
121
|
+
it "should set current_tenant to the specified tenant inside the block" do
|
122
|
+
@account = Account.create!(:name => 'baz')
|
123
|
+
|
124
|
+
MultiTenant.with(@account) do
|
125
|
+
expect(MultiTenant.current_tenant).to eq(@account)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should reset current_tenant to the previous tenant once exiting the block" do
|
130
|
+
@account1 = Account.create!(:name => 'foo')
|
131
|
+
@account2 = Account.create!(:name => 'bar')
|
132
|
+
|
133
|
+
MultiTenant.current_tenant = @account1
|
134
|
+
MultiTenant.with @account2 do
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
expect(MultiTenant.current_tenant).to eq(@account1)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should return the value of the block" do
|
142
|
+
@account1 = Account.create!(:name => 'foo')
|
143
|
+
@account2 = Account.create!(:name => 'bar')
|
144
|
+
|
145
|
+
MultiTenant.current_tenant = @account1
|
146
|
+
value = MultiTenant.with @account2 do
|
147
|
+
"something"
|
148
|
+
end
|
149
|
+
|
150
|
+
expect(value).to eq "something"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ProjectWithCallbacks < ActiveRecord::Base
|
4
|
+
self.table_name = :projects
|
5
|
+
|
6
|
+
multi_tenant :account
|
7
|
+
|
8
|
+
after_update do |record|
|
9
|
+
# Ensure that we don't have TenantIdWrapper here
|
10
|
+
record.account.update! name: 'callback'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe MultiTenant, 'Callbacks' do
|
15
|
+
let(:account) { Account.create! name: 'test' }
|
16
|
+
let(:project) { ProjectWithCallbacks.create! account: account, name: 'something' }
|
17
|
+
|
18
|
+
it 'takes callbacks into account' do
|
19
|
+
project.update! name: 'something else'
|
20
|
+
expect(account.reload.name).to eq 'callback'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultiTenant, 'Record finding' do
|
4
|
+
it 'searches for tenant object using the scope' do
|
5
|
+
account = Account.create! name: 'test'
|
6
|
+
project = account.projects.create! name: 'something'
|
7
|
+
MultiTenant.with(account) do
|
8
|
+
expect(Project.find(project.id)).to be_present
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultiTenant, 'Record modifications' do
|
4
|
+
let(:account) { Account.create! name: 'test' }
|
5
|
+
let(:project) { account.projects.create! name: 'something' }
|
6
|
+
|
7
|
+
it 'includes the tenant_id in UPDATEs' do
|
8
|
+
project.update! name: 'something else'
|
9
|
+
MultiTenant.with(account) do
|
10
|
+
expect(Project.find(project.id).name).to eq 'something else'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'includes the tenant_id in DELETEs' do
|
15
|
+
project.destroy!
|
16
|
+
MultiTenant.with(account) do
|
17
|
+
expect(Project.find_by(id: project.id)).not_to be_present
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/database.yml
ADDED
data/spec/schema.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
false && ActiveRecord::Schema.define(version: 1) do
|
2
|
+
create_table :accounts, force: true do |t|
|
3
|
+
t.column :name, :string
|
4
|
+
t.column :subdomain, :string
|
5
|
+
t.column :domain, :string
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :projects, force: true, partition_key: :account_id do |t|
|
9
|
+
t.column :name, :string
|
10
|
+
t.column :account_id, :integer
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :managers, force: true, partition_key: :account_id do |t|
|
14
|
+
t.column :name, :string
|
15
|
+
t.column :project_id, :integer
|
16
|
+
t.column :account_id, :integer
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :tasks, force: true, partition_key: :account_id do |t|
|
20
|
+
t.column :name, :string
|
21
|
+
t.column :account_id, :integer
|
22
|
+
t.column :project_id, :integer
|
23
|
+
t.column :completed, :boolean
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table :countries, force: true do |t|
|
27
|
+
t.column :name, :string
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :unscoped_models, force: true do |t|
|
31
|
+
t.column :name, :string
|
32
|
+
end
|
33
|
+
|
34
|
+
create_table :aliased_tasks, force: true, partition_key: :account_id do |t|
|
35
|
+
t.column :name, :string
|
36
|
+
t.column :project_alias_id, :integer
|
37
|
+
t.column :account_id, :integer
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :custom_partition_key_tasks, force: true, partition_key: :accountID do |t|
|
41
|
+
t.column :name, :string
|
42
|
+
t.column :accountID, :integer
|
43
|
+
end
|
44
|
+
|
45
|
+
create_table :comments, force: true, partition_key: :account_id do |t|
|
46
|
+
t.column :commentable_id, :integer
|
47
|
+
t.column :commentable_type, :string
|
48
|
+
t.column :account_id, :integer
|
49
|
+
end
|
50
|
+
|
51
|
+
create_distributed_table :accounts, :id
|
52
|
+
create_distributed_table :projects, :account_id
|
53
|
+
create_distributed_table :managers, :account_id
|
54
|
+
create_distributed_table :tasks, :account_id
|
55
|
+
create_distributed_table :aliased_tasks, :account_id
|
56
|
+
create_distributed_table :custom_partition_key_tasks, :accountID
|
57
|
+
create_distributed_table :comments, :account_id
|
58
|
+
end
|
59
|
+
|
60
|
+
class Account < ActiveRecord::Base
|
61
|
+
multi_tenant :account
|
62
|
+
has_many :projects
|
63
|
+
end
|
64
|
+
|
65
|
+
class Project < ActiveRecord::Base
|
66
|
+
multi_tenant :account
|
67
|
+
has_one :manager
|
68
|
+
has_many :tasks
|
69
|
+
|
70
|
+
validates_uniqueness_of :name, scope: [:account]
|
71
|
+
end
|
72
|
+
|
73
|
+
class Manager < ActiveRecord::Base
|
74
|
+
multi_tenant :account
|
75
|
+
belongs_to :project
|
76
|
+
end
|
77
|
+
|
78
|
+
class Task < ActiveRecord::Base
|
79
|
+
multi_tenant :account
|
80
|
+
belongs_to :project
|
81
|
+
default_scope -> { where(completed: nil).order('name') }
|
82
|
+
|
83
|
+
validates_uniqueness_of :name
|
84
|
+
end
|
85
|
+
|
86
|
+
class UnscopedModel < ActiveRecord::Base
|
87
|
+
validates_uniqueness_of :name
|
88
|
+
end
|
89
|
+
|
90
|
+
class AliasedTask < ActiveRecord::Base
|
91
|
+
multi_tenant :account
|
92
|
+
belongs_to :project_alias, class_name: 'Project'
|
93
|
+
end
|
94
|
+
|
95
|
+
class CustomPartitionKeyTask < ActiveRecord::Base
|
96
|
+
multi_tenant :account, partition_key: 'accountID'
|
97
|
+
validates_uniqueness_of :name, scope: [:account]
|
98
|
+
end
|
99
|
+
|
100
|
+
class Comment < ActiveRecord::Base
|
101
|
+
multi_tenant :account
|
102
|
+
belongs_to :commentable, polymorphic: true
|
103
|
+
belongs_to :task, -> { where(comments: { commentable_type: 'Task' }) }, foreign_key: 'commentable_id'
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'active_record/railtie'
|
5
|
+
require 'action_controller/railtie'
|
6
|
+
require 'rspec/rails'
|
7
|
+
require 'database_cleaner'
|
8
|
+
|
9
|
+
require 'activerecord-multi-tenant'
|
10
|
+
|
11
|
+
dbconfig = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
|
12
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
13
|
+
ActiveRecord::Base.establish_connection(dbconfig['test'])
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.after(:each) do
|
17
|
+
MultiTenant.current_tenant = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
config.before(:suite) do
|
21
|
+
DatabaseCleaner[:active_record].strategy = :transaction
|
22
|
+
DatabaseCleaner[:active_record].clean_with(:truncation)
|
23
|
+
end
|
24
|
+
|
25
|
+
config.before(:each) do
|
26
|
+
DatabaseCleaner[:active_record].start
|
27
|
+
end
|
28
|
+
|
29
|
+
config.after(:each) do
|
30
|
+
DatabaseCleaner[:active_record].clean
|
31
|
+
end
|
32
|
+
|
33
|
+
config.infer_base_class_for_anonymous_controllers = true
|
34
|
+
end
|
35
|
+
|
36
|
+
module MultiTenantTest
|
37
|
+
class Application < Rails::Application; end
|
38
|
+
end
|
39
|
+
|
40
|
+
MultiTenantTest::Application.config.secret_token = 'x' * 40
|
41
|
+
MultiTenantTest::Application.config.secret_key_base = 'y' * 40
|
42
|
+
|
43
|
+
require 'schema'
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-multi-tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Citus Data
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: request_store
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.0.5
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.0.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,13 +100,38 @@ executables: []
|
|
86
100
|
extensions: []
|
87
101
|
extra_rdoc_files: []
|
88
102
|
files:
|
103
|
+
- ".gitignore"
|
104
|
+
- Appraisals
|
105
|
+
- CHANGELOG.md
|
106
|
+
- Gemfile
|
107
|
+
- Gemfile.lock
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- activerecord-multi-tenant.gemspec
|
111
|
+
- docker-compose.yml
|
112
|
+
- gemfiles/rails_3.2.gemfile
|
113
|
+
- gemfiles/rails_3.2.gemfile.lock
|
114
|
+
- gemfiles/rails_4.2.gemfile
|
115
|
+
- gemfiles/rails_4.2.gemfile.lock
|
116
|
+
- gemfiles/rails_5.0.gemfile
|
117
|
+
- gemfiles/rails_5.0.gemfile.lock
|
89
118
|
- lib/activerecord-multi-tenant.rb
|
119
|
+
- lib/activerecord-multi-tenant/controller_extensions.rb
|
90
120
|
- lib/activerecord-multi-tenant/copy_from_client.rb
|
91
121
|
- lib/activerecord-multi-tenant/default_scope.rb
|
92
122
|
- lib/activerecord-multi-tenant/migrations.rb
|
123
|
+
- lib/activerecord-multi-tenant/model_extensions.rb
|
93
124
|
- lib/activerecord-multi-tenant/multi_tenant.rb
|
94
125
|
- lib/activerecord-multi-tenant/referential_integrity.rb
|
95
126
|
- lib/activerecord-multi-tenant/version.rb
|
127
|
+
- spec/activerecord-multi-tenant/controller_extensions_spec.rb
|
128
|
+
- spec/activerecord-multi-tenant/model_extensions_spec.rb
|
129
|
+
- spec/activerecord-multi-tenant/record_callback_spec.rb
|
130
|
+
- spec/activerecord-multi-tenant/record_finding_spec.rb
|
131
|
+
- spec/activerecord-multi-tenant/record_modifications_spec.rb
|
132
|
+
- spec/database.yml
|
133
|
+
- spec/schema.rb
|
134
|
+
- spec/spec_helper.rb
|
96
135
|
homepage: https://github.com/citusdata/activerecord-multi-tenant
|
97
136
|
licenses:
|
98
137
|
- MIT
|