multitenant-mysql 1.1.1 → 1.2.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.
- data/CHANGELOG.rdoc +4 -0
- data/Gemfile +1 -0
- data/README.rdoc +9 -8
- data/lib/generators/multitenant/install/install_generator.rb +1 -1
- data/lib/generators/multitenant/install/templates/multitenant_mysql_conf.rb +14 -13
- data/lib/generators/multitenant/migrations/migration_builder.rb +1 -1
- data/lib/generators/multitenant/migrations/migrations_generator.rb +0 -1
- data/lib/generators/multitenant/triggers/create_generator.rb +0 -1
- data/lib/generators/multitenant/triggers/refresh_generator.rb +0 -1
- data/lib/generators/multitenant/triggers/sql/create.rb +1 -1
- data/lib/generators/multitenant/views/create_generator.rb +0 -1
- data/lib/generators/multitenant/views/refresh_generator.rb +0 -1
- data/lib/generators/multitenant/views/sql/create.rb +1 -1
- data/lib/generators/multitenant/views_and_triggers/create_generator.rb +0 -2
- data/lib/generators/multitenant/views_and_triggers/drop_generator.rb +20 -0
- data/lib/generators/multitenant/views_and_triggers/refresh_generator.rb +0 -2
- data/lib/multitenant-mysql.rb +2 -3
- data/lib/multitenant-mysql/action_controller_extension.rb +1 -8
- data/lib/multitenant-mysql/active_record_extension.rb +22 -22
- data/lib/multitenant-mysql/configs.rb +23 -0
- data/lib/multitenant-mysql/configs/base.rb +20 -0
- data/lib/multitenant-mysql/configs/bucket.rb +43 -0
- data/lib/multitenant-mysql/connection_switcher.rb +4 -4
- data/lib/multitenant-mysql/db.rb +0 -1
- data/lib/multitenant-mysql/errors.rb +9 -0
- data/lib/multitenant-mysql/version.rb +1 -1
- data/spec/configs_spec.rb +39 -0
- data/spec/connection_switcher_spec.rb +36 -28
- data/spec/db_spec.rb +35 -0
- data/spec/rails/active_record_base_spec.rb +6 -6
- data/spec/spec_helper.rb +10 -3
- metadata +12 -11
- data/lib/multitenant-mysql/arc.rb +0 -52
- data/lib/multitenant-mysql/conf_file.rb +0 -22
- data/spec/arc_spec.rb +0 -81
- data/spec/conf_file_spec.rb +0 -48
- data/spec/support/multitenant_mysql_conf.rb +0 -4
data/CHANGELOG.rdoc
CHANGED
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -16,7 +16,7 @@ The advantages of such approach:
|
|
16
16
|
* {<img src="https://travis-ci.org/eugenekorpan/multitenant-mysql.png?branch=master"/>}[http://travis-ci.org/eugenekorpan/multitenant-mysql]
|
17
17
|
* {<img src="https://gemnasium.com/eugenekorpan/multitenant-mysql.png" alt="Dependency Status" />}[https://gemnasium.com/eugenekorpan/multitenant-mysql]
|
18
18
|
* {<img src="https://codeclimate.com/github/eugenekorpan/multitenant-mysql.png" />}[https://codeclimate.com/github/eugenekorpan/multitenant-mysql]
|
19
|
-
|
19
|
+
* {<img src="https://coveralls.io/repos/eugenekorpan/multitenant-mysql/badge.png?branch=master" alt="Coverage Status" />}[https://coveralls.io/r/eugenekorpan/multitenant-mysql]
|
20
20
|
|
21
21
|
== Installation
|
22
22
|
|
@@ -33,14 +33,14 @@ The advantages of such approach:
|
|
33
33
|
1 run
|
34
34
|
rails g multitenant:install
|
35
35
|
|
36
|
-
|
37
|
-
This is the place where you list all your tenant dependent models and the model which contains all the tenants.
|
38
|
-
E.g:
|
36
|
+
This will create a sample of config file in "rails_root/config/initializers/multitenant_mysql_conf.rb". Update it according to your needs. E.g:
|
39
37
|
|
40
|
-
Multitenant::Mysql.
|
41
|
-
models
|
42
|
-
|
43
|
-
|
38
|
+
Multitenant::Mysql.configure do |conf|
|
39
|
+
conf.models = ['Book', 'Task']
|
40
|
+
conf.tenants_bucket 'Subdomain' do |tb|
|
41
|
+
tb.field = 'name'
|
42
|
+
end
|
43
|
+
end
|
44
44
|
|
45
45
|
Important: Before moving on you have to update this file as all further steps use those configs.
|
46
46
|
|
@@ -88,6 +88,7 @@ if method used by `set_current_tenant` returns blank name then `root` account is
|
|
88
88
|
multitenant:views:drop
|
89
89
|
multitenant:views:refresh
|
90
90
|
multitenant:views_and_triggers:create
|
91
|
+
multitenant:views_and_triggers:drop
|
91
92
|
multitenant:views_and_triggers:refresh
|
92
93
|
|
93
94
|
== How It Works
|
@@ -7,7 +7,7 @@ module Multitenant
|
|
7
7
|
CONFIG_FILE_NAME = 'multitenant_mysql_conf.rb'
|
8
8
|
|
9
9
|
def copy_conf_file_into_app
|
10
|
-
dest = "config/#{CONFIG_FILE_NAME}"
|
10
|
+
dest = "config/initializers/#{CONFIG_FILE_NAME}"
|
11
11
|
return if FileTest.exist?(dest) # to prevent overwritting of existing file
|
12
12
|
src = File.expand_path(File.dirname(__FILE__)) + "/templates/#{CONFIG_FILE_NAME}"
|
13
13
|
FileUtils.copy_file src, dest
|
@@ -1,17 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# Usage example:
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
3
|
+
# Multitenant::Mysql.configure do |conf|
|
4
|
+
# conf.models = ['Book', 'Task', 'Post']
|
5
|
+
# conf.tenants_bucket 'Subdomain' do |tb|
|
6
|
+
# tb.field = 'name'
|
7
|
+
# end
|
8
|
+
# end
|
7
9
|
#
|
8
10
|
# where:
|
9
|
-
# models - list of tenant
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# tenant_name_attr - attribute used to fetch tenant name
|
11
|
+
# models - list of tenant dependent models
|
12
|
+
# tenants_bucket - model which stores all the tenants, as an argument recives the name of the model
|
13
|
+
# field - attribute used to fetch tenant name (not required, default values are: name, title)
|
13
14
|
|
14
|
-
Multitenant::Mysql.
|
15
|
-
models
|
16
|
-
|
17
|
-
|
15
|
+
Multitenant::Mysql.configure do |conf|
|
16
|
+
conf.models = []
|
17
|
+
conf.tenants_bucket ''
|
18
|
+
end
|
@@ -8,7 +8,7 @@ module Multitenant
|
|
8
8
|
def run
|
9
9
|
return if migration_exists?
|
10
10
|
|
11
|
-
actions = Multitenant::Mysql.models.map { |model_name|
|
11
|
+
actions = Multitenant::Mysql.configs.models.map { |model_name|
|
12
12
|
model = model_name.constantize
|
13
13
|
"add_column :#{model.original_table_name}, :tenant, :string"
|
14
14
|
}
|
@@ -7,7 +7,7 @@ module Multitenant
|
|
7
7
|
class Create
|
8
8
|
|
9
9
|
def self.run
|
10
|
-
Multitenant::Mysql.models.each do |model_name|
|
10
|
+
Multitenant::Mysql.configs.models.each do |model_name|
|
11
11
|
model = model_name.constantize
|
12
12
|
columns = model.column_names.join(', ')
|
13
13
|
view_name = model_name.to_s.downcase.pluralize + "_view"
|
@@ -3,8 +3,6 @@ require 'rails/generators'
|
|
3
3
|
require_relative '../views/sql/create'
|
4
4
|
require_relative '../triggers/sql/create'
|
5
5
|
|
6
|
-
require Rails.root.to_s + '/config/multitenant_mysql_conf'
|
7
|
-
|
8
6
|
module Multitenant
|
9
7
|
module ViewsAndTriggers
|
10
8
|
class CreateGenerator < Rails::Generators::Base
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
require_relative '../views/sql/drop'
|
4
|
+
require_relative '../triggers/sql/drop'
|
5
|
+
|
6
|
+
module Multitenant
|
7
|
+
module ViewsAndTriggers
|
8
|
+
class DropGenerator < Rails::Generators::Base
|
9
|
+
desc "drops all views and triggers"
|
10
|
+
|
11
|
+
def generate_mysql_views
|
12
|
+
Multitenant::Views::SQL::Drop.run
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_mysql_triggers
|
16
|
+
Multitenant::Triggers::SQL::Drop.run
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,8 +5,6 @@ require_relative '../views/sql/drop'
|
|
5
5
|
require_relative '../triggers/sql/create'
|
6
6
|
require_relative '../triggers/sql/drop'
|
7
7
|
|
8
|
-
require Rails.root.to_s + '/config/multitenant_mysql_conf'
|
9
|
-
|
10
8
|
module Multitenant
|
11
9
|
module ViewsAndTriggers
|
12
10
|
class RefreshGenerator < Rails::Generators::Base
|
data/lib/multitenant-mysql.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'multitenant-mysql/version'
|
2
|
+
require 'multitenant-mysql/errors'
|
3
|
+
require 'multitenant-mysql/configs'
|
2
4
|
require 'multitenant-mysql/action_controller_extension'
|
3
|
-
require 'multitenant-mysql/conf_file'
|
4
|
-
require 'multitenant-mysql/arc'
|
5
|
-
|
6
5
|
require 'multitenant-mysql/active_record_extension'
|
@@ -2,24 +2,17 @@ require_relative './connection_switcher'
|
|
2
2
|
|
3
3
|
class ActionController::Base
|
4
4
|
def self.set_current_tenant(tenant_method)
|
5
|
-
|
6
|
-
|
7
|
-
raise "you should provide tenant method" unless tenant_method
|
8
|
-
|
5
|
+
raise InvalidTenantError.new('Multitenant::Mysql: you should provide tenant method') unless tenant_method
|
9
6
|
@@tenant_method = tenant_method
|
10
7
|
|
11
8
|
before_filter :establish_tenant_connection
|
12
|
-
|
13
9
|
def establish_tenant_connection
|
14
10
|
Multitenant::Mysql::ConnectionSwitcher.new(self, @@tenant_method).execute
|
15
11
|
end
|
16
12
|
end
|
17
13
|
|
18
14
|
def self.set_current_tenant_by_subdomain
|
19
|
-
require Multitenant::Mysql::ConfFile.path
|
20
|
-
|
21
15
|
before_filter :establish_tenant_connection_by_subdomain
|
22
|
-
|
23
16
|
def establish_tenant_connection_by_subdomain
|
24
17
|
tenant_name = request.subdomain
|
25
18
|
Multitenant::Mysql::ConnectionSwitcher.set_tenant(tenant_name)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class ActiveRecord::Base
|
2
|
-
def self.
|
2
|
+
def self.acts_as_tenants_bucket
|
3
3
|
after_create do
|
4
4
|
config = Rails.configuration.database_configuration
|
5
5
|
password = config[Rails.env]["password"]
|
@@ -8,34 +8,34 @@ class ActiveRecord::Base
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
view_name = model_name.to_s.downcase.pluralize + "_view"
|
18
|
-
|
19
|
-
# check whether the view exists in db
|
20
|
-
if ActiveRecord::Base.connection.table_exists? view_name
|
21
|
-
child.class_eval do
|
22
|
-
cattr_accessor :original_table_name
|
11
|
+
def self.acts_as_tenant
|
12
|
+
view_name = model_name.to_s.downcase.pluralize + "_view"
|
13
|
+
# check whether the view exists in db
|
14
|
+
if ActiveRecord::Base.connection.table_exists? view_name
|
15
|
+
self.class_eval do
|
16
|
+
cattr_accessor :original_table_name
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
self.original_table_name = self.table_name
|
19
|
+
self.table_name = view_name
|
20
|
+
self.primary_key = :id
|
27
21
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
22
|
+
def self.new(*args)
|
23
|
+
object = super(*args)
|
24
|
+
object.id = nil # workaround for https://github.com/rails/rails/issues/5982
|
25
|
+
object
|
33
26
|
end
|
34
27
|
end
|
35
28
|
end
|
29
|
+
end
|
36
30
|
|
37
|
-
|
31
|
+
def self.inherited(child)
|
32
|
+
model_name = child.to_s
|
33
|
+
if Multitenant::Mysql.configs.models.include? model_name
|
38
34
|
child.send :acts_as_tenant
|
39
35
|
end
|
36
|
+
|
37
|
+
if Multitenant::Mysql.configs.tenant == child
|
38
|
+
child.send :acts_as_tenants_bucket
|
39
|
+
end
|
40
40
|
end
|
41
41
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative './configs/bucket'
|
2
|
+
require_relative './configs/base'
|
3
|
+
|
4
|
+
module Multitenant
|
5
|
+
module Mysql
|
6
|
+
class << self
|
7
|
+
def configure &block
|
8
|
+
raise InvalidConfigsError.new('Multitenant::Mysql: No configs provided') unless block_given?
|
9
|
+
configs = Configs::Base.new
|
10
|
+
block.call(configs)
|
11
|
+
self.configs = configs
|
12
|
+
end
|
13
|
+
|
14
|
+
def configs=configs
|
15
|
+
@configs = configs
|
16
|
+
end
|
17
|
+
|
18
|
+
def configs
|
19
|
+
@configs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Multitenant
|
2
|
+
module Mysql
|
3
|
+
module Configs
|
4
|
+
class Base
|
5
|
+
attr_accessor :models, :bucket
|
6
|
+
|
7
|
+
def tenants_bucket(name)
|
8
|
+
raise InvalidBucketError.new('Multitenant::Mysql: invalid bucket') if name.blank?
|
9
|
+
|
10
|
+
@bucket = Bucket.new(name)
|
11
|
+
yield(@bucket) if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
def tenant
|
15
|
+
@bucket.model
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Multitenant
|
2
|
+
module Mysql
|
3
|
+
module Configs
|
4
|
+
class Bucket
|
5
|
+
DEFAULT_TENANT_NAME_ATTR = [:name, :title]
|
6
|
+
attr_accessor :name, :field
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def model
|
13
|
+
@name.constantize
|
14
|
+
rescue
|
15
|
+
if @name.blank?
|
16
|
+
raise InvalidBucketError.new('Multitenant::Mysql: You should specify model which stores info about tenants.')
|
17
|
+
else
|
18
|
+
raise InvalidBucketError.new("Please check your multitenant-mysql configs. Seems like you are trying to use model which doesn't exist: #{@name}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def field
|
23
|
+
@field ||= default_field
|
24
|
+
@field || raise(InvalidBucketFieldError.new('Multitenant::Mysql: You should specify tenants bucket field or use one default: name, title'))
|
25
|
+
raise InvalidBucketFieldError.new("Multitenant::Mysql: #{model} doesn't have '#{@field}' attribute") unless validate_field?
|
26
|
+
@field
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def default_field
|
32
|
+
DEFAULT_TENANT_NAME_ATTR.each do |n|
|
33
|
+
return n if model.column_names.include?(n.to_s)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_field?
|
38
|
+
@valid ||= model.column_names.include?(@field.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -2,13 +2,13 @@ require_relative './db'
|
|
2
2
|
|
3
3
|
module Multitenant
|
4
4
|
module Mysql
|
5
|
-
class NoTenantRegistratedError < StandardError; end;
|
6
|
-
|
7
5
|
class Tenant
|
8
6
|
def self.exists? tenant_name
|
9
7
|
return true if tenant_name.blank?
|
10
|
-
|
11
|
-
|
8
|
+
tenant = Multitenant::Mysql.configs.tenant
|
9
|
+
field = Multitenant::Mysql.configs.bucket.field
|
10
|
+
if tenant.where(field => tenant_name).blank?
|
11
|
+
raise NoTenantRegistratedError.new("No tenant registered: #{tenant_name}")
|
12
12
|
end
|
13
13
|
true
|
14
14
|
end
|
data/lib/multitenant-mysql/db.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Multitenant
|
2
|
+
module Mysql
|
3
|
+
class InvalidBucketError < StandardError; end;
|
4
|
+
class NoTenantRegistratedError < StandardError; end;
|
5
|
+
class InvalidConfigsError < StandardError; end;
|
6
|
+
class InvalidBucketFieldError < StandardError; end;
|
7
|
+
class InvalidTenantError < StandardError; end;
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Multitenant::Mysql do
|
4
|
+
subject { Multitenant::Mysql }
|
5
|
+
|
6
|
+
context '#configure' do
|
7
|
+
it 'should raise error with no block given' do
|
8
|
+
expect { subject.configure }.to raise_error(Multitenant::Mysql::InvalidConfigsError)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'with valid params' do
|
12
|
+
before do
|
13
|
+
class Subdomain; end;
|
14
|
+
subject.configure do |conf|
|
15
|
+
conf.models = ['Book']
|
16
|
+
conf.tenants_bucket 'Subdomain'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should be of valid type' do
|
21
|
+
expect(subject.configs).to be_kind_of Multitenant::Mysql::Configs::Base
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have valid values' do
|
25
|
+
expect(subject.configs.models).to eql(['Book'])
|
26
|
+
expect(subject.configs.tenant).to eql(Subdomain)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#configs=' do
|
32
|
+
let(:configs) { Multitenant::Mysql::Configs::Base.new }
|
33
|
+
|
34
|
+
it 'should set configs' do
|
35
|
+
subject.configs = configs
|
36
|
+
expect(subject.configs).to eql(configs)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,49 +1,57 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Multitenant::Mysql::
|
3
|
+
describe Multitenant::Mysql::Tenant do
|
4
|
+
subject { Multitenant::Mysql::Tenant }
|
5
|
+
|
4
6
|
before do
|
5
|
-
|
7
|
+
create_table('subdomains')
|
8
|
+
class Subdomain < ActiveRecord::Base; end;
|
6
9
|
end
|
7
10
|
|
8
|
-
context '#
|
9
|
-
it 'should
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
context '#exists?' do
|
12
|
+
it 'should return true for existing tenant' do
|
13
|
+
mock = double('Subdomain')
|
14
|
+
Subdomain.stub(:where).and_return([mock])
|
15
|
+
expect(subject.exists?('blade')).to be_true
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Multitenant::Mysql::DB.stub(:configs).and_return({ 'username' => 'root' })
|
18
|
+
it 'should return true for blank name' do
|
19
|
+
expect(subject.exists?('')).to be_true
|
20
|
+
end
|
18
21
|
|
19
|
-
|
22
|
+
it 'should raise an error for unexisting tenant' do
|
23
|
+
ActiveRecord::Base.stub(:where).and_return(nil)
|
24
|
+
expect { subject.exists?('invalid tenant') }.to raise_error(Multitenant::Mysql::NoTenantRegistratedError)
|
20
25
|
end
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
ar_mock = double('ActiveRecord::Relation')
|
24
|
-
ar_mock.stub(:where).and_return(:some_result)
|
25
|
-
Multitenant::Mysql.stub(:tenant).and_return(ar_mock)
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
Multitenant::Mysql::DB.stub(:configs).and_return({ 'username' => 'root' })
|
30
|
+
describe Multitenant::Mysql::ConnectionSwitcher do
|
31
|
+
context '#set_tenant' do
|
32
|
+
subject { Multitenant::Mysql::ConnectionSwitcher }
|
31
33
|
|
32
|
-
|
34
|
+
before do
|
35
|
+
Multitenant::Mysql::Tenant.stub(:exists?).and_return(true)
|
36
|
+
end
|
33
37
|
|
34
|
-
|
38
|
+
it 'should change db connection' do
|
39
|
+
Multitenant::Mysql::DB.should respond_to(:establish_connection_for)
|
40
|
+
Multitenant::Mysql::DB.should_receive(:establish_connection_for).with('tenant')
|
41
|
+
expect{ subject.set_tenant('tenant') }.to_not raise_error
|
35
42
|
end
|
36
43
|
end
|
37
44
|
|
38
|
-
context '.
|
39
|
-
|
45
|
+
context '.execute' do
|
46
|
+
let(:mock_ac) { double('ActionController::Base') }
|
47
|
+
|
48
|
+
subject { Multitenant::Mysql::ConnectionSwitcher.new(mock_ac, :tenant) }
|
40
49
|
|
41
50
|
it 'should change db connection' do
|
42
51
|
Multitenant::Mysql::Tenant.stub(:exists?).and_return(true)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
expect( subject.set_tenant('google') ).to be
|
52
|
+
mock_ac.should_receive(:send).with(:tenant).and_return('wallmart')
|
53
|
+
Multitenant::Mysql::DB.should_receive(:establish_connection_for).with('wallmart')
|
54
|
+
expect { subject.execute }.to_not raise_error
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
data/spec/db_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Multitenant::Mysql::DB do
|
4
|
+
subject { Multitenant::Mysql::DB }
|
5
|
+
let(:configs) { { 'username' => 'root', 'password' => 'password' } }
|
6
|
+
|
7
|
+
context '#configs' do
|
8
|
+
it 'should set configs' do
|
9
|
+
subject.configs = configs
|
10
|
+
expect(subject.configs).to eql(configs)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should get configs' do
|
14
|
+
class Rails; end;
|
15
|
+
Rails.stub_chain(:configuration, :database_configuration, :[]).and_return(configs)
|
16
|
+
|
17
|
+
expect(subject.configs['username']).to eql('root')
|
18
|
+
expect(subject.configs['password']).to eql('password')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context '#establish_connection_for' do
|
23
|
+
it 'should change db connection with root account' do
|
24
|
+
subject.configs = configs
|
25
|
+
ActiveRecord::Base.should_receive(:establish_connection).with({ 'username' => 'root', 'password' => 'password' })
|
26
|
+
expect { subject.establish_connection_for(nil) }.to_not raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should change db connection for particular tenant' do
|
30
|
+
subject.configs = configs
|
31
|
+
ActiveRecord::Base.should_receive(:establish_connection).with({ 'username' => 'wallmart', 'password' => 'password' })
|
32
|
+
expect { subject.establish_connection_for('wallmart') }.to_not raise_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,19 +2,19 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe ActiveRecord::Base do
|
4
4
|
subject { ActiveRecord::Base }
|
5
|
-
|
6
|
-
|
7
|
-
Multitenant::Mysql.arc = { models: ['Book', 'Task'] }
|
5
|
+
it 'should respond to acts_as_tenant' do
|
6
|
+
subject.should respond_to(:acts_as_tenant)
|
8
7
|
end
|
9
8
|
|
10
|
-
it 'should respond to
|
11
|
-
subject.should respond_to(:
|
9
|
+
it 'should respond to acts_as_tenants_bucket' do
|
10
|
+
subject.should respond_to(:acts_as_tenants_bucket)
|
12
11
|
end
|
13
12
|
|
14
13
|
it 'should redefine table name and primary key and keep original table name' do
|
15
14
|
ActiveRecord::Base.stub_chain(:connection, :table_exists?).and_return(true)
|
16
15
|
|
17
|
-
Multitenant::Mysql.
|
16
|
+
Multitenant::Mysql.stub_chain(:configs, :models).and_return(['Book'])
|
17
|
+
Multitenant::Mysql.stub_chain(:configs, :tenant).and_return(false)
|
18
18
|
|
19
19
|
class Book < ActiveRecord::Base
|
20
20
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,14 +5,13 @@ require 'active_record'
|
|
5
5
|
require 'action_controller'
|
6
6
|
require 'multitenant-mysql'
|
7
7
|
|
8
|
+
require 'coveralls'
|
9
|
+
Coveralls.wear!
|
8
10
|
|
9
11
|
GEM_ROOT_PATH = File.expand_path('../../', __FILE__)
|
10
|
-
CONF_FILE_PATH = GEM_ROOT_PATH + '/spec/support/multitenant_mysql_conf'
|
11
12
|
|
12
13
|
RSpec.configure do |config|
|
13
14
|
config.before do
|
14
|
-
Multitenant::Mysql::ConfFile.path = CONF_FILE_PATH
|
15
|
-
|
16
15
|
ActiveRecord::Base.establish_connection({
|
17
16
|
adapter: 'mysql2',
|
18
17
|
username: 'root',
|
@@ -21,6 +20,14 @@ RSpec.configure do |config|
|
|
21
20
|
ActiveRecord::Base.connection.execute('drop database if exists `tenant_test`;')
|
22
21
|
ActiveRecord::Base.connection.execute('create database `tenant_test`;')
|
23
22
|
ActiveRecord::Base.connection.execute('use `tenant_test`;')
|
23
|
+
|
24
|
+
|
25
|
+
Multitenant::Mysql.configure do |conf|
|
26
|
+
conf.models = ['Book']
|
27
|
+
conf.tenants_bucket 'Subdomain' do |tb|
|
28
|
+
tb.field = 'name'
|
29
|
+
end
|
30
|
+
end
|
24
31
|
end
|
25
32
|
|
26
33
|
config.after do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multitenant-mysql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -57,22 +57,25 @@ files:
|
|
57
57
|
- lib/generators/multitenant/views/sql/create.rb
|
58
58
|
- lib/generators/multitenant/views/sql/drop.rb
|
59
59
|
- lib/generators/multitenant/views_and_triggers/create_generator.rb
|
60
|
+
- lib/generators/multitenant/views_and_triggers/drop_generator.rb
|
60
61
|
- lib/generators/multitenant/views_and_triggers/list/list.rb
|
61
62
|
- lib/generators/multitenant/views_and_triggers/list/sql.rb
|
62
63
|
- lib/generators/multitenant/views_and_triggers/refresh_generator.rb
|
63
64
|
- lib/multitenant-mysql.rb
|
64
65
|
- lib/multitenant-mysql/action_controller_extension.rb
|
65
66
|
- lib/multitenant-mysql/active_record_extension.rb
|
66
|
-
- lib/multitenant-mysql/
|
67
|
-
- lib/multitenant-mysql/
|
67
|
+
- lib/multitenant-mysql/configs.rb
|
68
|
+
- lib/multitenant-mysql/configs/base.rb
|
69
|
+
- lib/multitenant-mysql/configs/bucket.rb
|
68
70
|
- lib/multitenant-mysql/connection_switcher.rb
|
69
71
|
- lib/multitenant-mysql/db.rb
|
72
|
+
- lib/multitenant-mysql/errors.rb
|
70
73
|
- lib/multitenant-mysql/version.rb
|
71
74
|
- multitenant-mysql.gemspec
|
72
75
|
- rails/init.rb
|
73
|
-
- spec/
|
74
|
-
- spec/conf_file_spec.rb
|
76
|
+
- spec/configs_spec.rb
|
75
77
|
- spec/connection_switcher_spec.rb
|
78
|
+
- spec/db_spec.rb
|
76
79
|
- spec/generators/list_spec.rb
|
77
80
|
- spec/generators/triggers/sql/create_spec.rb
|
78
81
|
- spec/generators/triggers/sql/drop_spec.rb
|
@@ -81,7 +84,6 @@ files:
|
|
81
84
|
- spec/rails/action_controller_extension_spec.rb
|
82
85
|
- spec/rails/active_record_base_spec.rb
|
83
86
|
- spec/spec_helper.rb
|
84
|
-
- spec/support/multitenant_mysql_conf.rb
|
85
87
|
homepage: https://github.com/eugenekorpan/multitenant-mysql
|
86
88
|
licenses: []
|
87
89
|
post_install_message:
|
@@ -102,14 +104,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
104
|
version: '0'
|
103
105
|
requirements: []
|
104
106
|
rubyforge_project:
|
105
|
-
rubygems_version: 1.8.
|
107
|
+
rubygems_version: 1.8.24
|
106
108
|
signing_key:
|
107
109
|
specification_version: 3
|
108
110
|
summary: Add multi-tenancy to Rails application using MySql views
|
109
111
|
test_files:
|
110
|
-
- spec/
|
111
|
-
- spec/conf_file_spec.rb
|
112
|
+
- spec/configs_spec.rb
|
112
113
|
- spec/connection_switcher_spec.rb
|
114
|
+
- spec/db_spec.rb
|
113
115
|
- spec/generators/list_spec.rb
|
114
116
|
- spec/generators/triggers/sql/create_spec.rb
|
115
117
|
- spec/generators/triggers/sql/drop_spec.rb
|
@@ -118,4 +120,3 @@ test_files:
|
|
118
120
|
- spec/rails/action_controller_extension_spec.rb
|
119
121
|
- spec/rails/active_record_base_spec.rb
|
120
122
|
- spec/spec_helper.rb
|
121
|
-
- spec/support/multitenant_mysql_conf.rb
|
@@ -1,52 +0,0 @@
|
|
1
|
-
module Multitenant
|
2
|
-
module Mysql
|
3
|
-
|
4
|
-
class << self
|
5
|
-
|
6
|
-
DEFAULT_TENANT_NAME_ATTR = [:name, :title]
|
7
|
-
|
8
|
-
def active_record_configs=(configs)
|
9
|
-
@configs = configs
|
10
|
-
end
|
11
|
-
|
12
|
-
def active_record_configs
|
13
|
-
@configs
|
14
|
-
end
|
15
|
-
|
16
|
-
alias_method :arc, :active_record_configs
|
17
|
-
alias_method :arc=, :active_record_configs=
|
18
|
-
|
19
|
-
def tenant
|
20
|
-
arc[:tenant_model][:name].constantize
|
21
|
-
rescue
|
22
|
-
if arc.blank? || arc[:tenant_model].blank? || arc[:tenant_model][:name].blank?
|
23
|
-
raise "
|
24
|
-
Multitenant::Mysql: You should specify model which stores info about tenants.
|
25
|
-
E.g. in initializer:
|
26
|
-
Multitenant::Mysql.arc = {
|
27
|
-
tenant_model: { name: 'Subdomain' }
|
28
|
-
}
|
29
|
-
"
|
30
|
-
else
|
31
|
-
raise "Please check your multitenant-mysql configs. Seems like you are trying to use model which doesn't exist: #{arc[:tenant_model][:name]}"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def models
|
36
|
-
active_record_configs[:models]
|
37
|
-
end
|
38
|
-
|
39
|
-
def tenant_name_attr
|
40
|
-
return active_record_configs[:tenant_model][:tenant_name_attr] if active_record_configs[:tenant_model][:tenant_name_attr]
|
41
|
-
|
42
|
-
DEFAULT_TENANT_NAME_ATTR.each do |name|
|
43
|
-
return name if tenant.column_names.include?(name.to_s)
|
44
|
-
end
|
45
|
-
|
46
|
-
raise 'You should specify tenant name attribute or use one default: name, title'
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Multitenant
|
2
|
-
module Mysql
|
3
|
-
class ConfFile
|
4
|
-
def self.path=(path)
|
5
|
-
@path = path
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.path
|
9
|
-
# workaround to reqire conf file in rails app
|
10
|
-
@path ||= default_path
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.full_path
|
14
|
-
"#{path}.rb"
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.default_path
|
18
|
-
Rails.root.to_s + '/config/multitenant_mysql_conf'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/spec/arc_spec.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Multitenant::Mysql do
|
4
|
-
subject { Multitenant::Mysql }
|
5
|
-
|
6
|
-
context 'active record configs' do
|
7
|
-
|
8
|
-
it 'should raise error if no tenant name provided' do
|
9
|
-
subject.active_record_configs = nil
|
10
|
-
expect {
|
11
|
-
subject.tenant_name_attr
|
12
|
-
}.to raise_error
|
13
|
-
end
|
14
|
-
|
15
|
-
context 'tenant name attribute' do
|
16
|
-
it 'should use name or title by default' do
|
17
|
-
mock = double()
|
18
|
-
mock.stub(:column_names).and_return(['name'])
|
19
|
-
subject.stub(:tenant).and_return(mock)
|
20
|
-
subject.stub(:active_record_configs).and_return({tenant_model: {}})
|
21
|
-
expect(subject.tenant_name_attr).to eql(:name)
|
22
|
-
|
23
|
-
mock.stub(:column_names).and_return(['title'])
|
24
|
-
expect(subject.tenant_name_attr).to eql(:title)
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'should use attribute from configs' do
|
28
|
-
subject.stub(:active_record_configs).and_return({tenant_model: {tenant_name_attr: 'subdomain'}})
|
29
|
-
expect(subject.tenant_name_attr).to eql('subdomain')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context '.tenant' do
|
34
|
-
before do
|
35
|
-
Subdomain = :constant
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'should find and return appropriate model' do
|
39
|
-
subject.active_record_configs = { tenant_model: { name: 'Subdomain' } }
|
40
|
-
expect(subject.tenant).to eq(Subdomain)
|
41
|
-
end
|
42
|
-
|
43
|
-
context 'invalid data' do
|
44
|
-
it 'should raise error if no data provided' do
|
45
|
-
subject.active_record_configs = {}
|
46
|
-
expect { subject.tenant }.to raise_error(RuntimeError)
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'should raise error if invalid data provided' do
|
50
|
-
subject.active_record_configs = { tenant_model: { name: nil } }
|
51
|
-
expect { subject.tenant }.to raise_error(RuntimeError)
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'should raise error if invalid model provided' do
|
55
|
-
subject.active_record_configs = { tenant_model: { name: 'UnexistingModelLa' } }
|
56
|
-
expect { subject.tenant }.to raise_error(RuntimeError)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context '.models' do
|
62
|
-
it 'should return listed models' do
|
63
|
-
subject.active_record_configs = { models: ['Book', 'Task'] }
|
64
|
-
expect(subject.models).to have(2).items
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
context 'alias methods' do
|
69
|
-
it 'should use aliased setter' do
|
70
|
-
subject.arc = 'ARC'
|
71
|
-
expect(subject.active_record_configs).to eql('ARC')
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'should use aliased getter' do
|
75
|
-
subject.active_record_configs = 'ABC'
|
76
|
-
expect(subject.arc).to eql('ABC')
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
data/spec/conf_file_spec.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Multitenant::Mysql::ConfFile do
|
4
|
-
subject { Multitenant::Mysql::ConfFile }
|
5
|
-
|
6
|
-
context '.path' do
|
7
|
-
it 'should return path' do
|
8
|
-
subject.path = '/conf/file'
|
9
|
-
expect(subject.path).to eql('/conf/file')
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'should return default path to conf file in rails app' do
|
13
|
-
path = 'Rails.root/config/multitenant_mysql_conf'
|
14
|
-
|
15
|
-
subject.stub(:default_path).and_return(path)
|
16
|
-
subject.path = nil
|
17
|
-
|
18
|
-
expect(subject.path).to eql(path)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context '.full_path' do
|
23
|
-
it 'should return path with extension of the file' do
|
24
|
-
subject.path = '/conf/file'
|
25
|
-
expect(subject.full_path).to eql('/conf/file.rb')
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'should return correct path no metter how many times we call it' do
|
29
|
-
subject.path = '/conf/file'
|
30
|
-
2.times do
|
31
|
-
expect(subject.full_path).to eql('/conf/file.rb')
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context '.default_path' do
|
37
|
-
it 'should return path to conf file in rails app' do
|
38
|
-
class Rails
|
39
|
-
def self.root
|
40
|
-
'Rails.root'
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
expect(subject.default_path).to eql('Rails.root/config/multitenant_mysql_conf')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|