multitenant-mysql 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|