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.
@@ -1,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -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
@@ -0,0 +1,9 @@
1
+ test:
2
+ adapter: postgresql
3
+ username: postgres
4
+ database: postgres
5
+ host: localhost
6
+ port: 5600
7
+ pool: 5
8
+ timeout: 5000
9
+ prepared_statements: false
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
@@ -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.2.1
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-29 00:00:00.000000000 Z
11
+ date: 2016-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: acts_as_tenant
14
+ name: request_store
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
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: '0'
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