multitenant-mysql 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  == Multitenant::Mysql
2
2
 
3
- Web app often face multitenancy problem and there are already few gems that help to solve this issue but this one uses different approach.
3
+ Web app often faces multitenancy problem and there are already few gems that help to solve this issue but this one uses different approach.
4
4
  Instead of default scopes or creating a separate database for every single tenant this gem uses mysql views and triggers.
5
5
  The idea is taken from http://blog.empowercampaigns.com/post/1044240481/multi-tenant-data-and-mysql
6
6
 
@@ -12,6 +12,7 @@ The advantages of such approach:
12
12
 
13
13
  == Code Status
14
14
 
15
+ * {<img src="https://fury-badge.herokuapp.com/rb/multitenant-mysql.png" alt="Gem Version" />}[http://badge.fury.io/rb/multitenant-mysql]
15
16
  * {<img src="https://travis-ci.org/eugenekorpan/multitenant-mysql.png?branch=master"/>}[http://travis-ci.org/eugenekorpan/multitenant-mysql]
16
17
  * {<img src="https://gemnasium.com/eugenekorpan/multitenant-mysql.png" alt="Dependency Status" />}[https://gemnasium.com/eugenekorpan/multitenant-mysql]
17
18
  * {<img src="https://codeclimate.com/github/eugenekorpan/multitenant-mysql.png" />}[https://codeclimate.com/github/eugenekorpan/multitenant-mysql]
@@ -70,7 +71,9 @@ E.g.
70
71
 
71
72
  if method used by `set_current_tenant` returns blank name then `root` account is used
72
73
 
73
- == Host It Works
74
+ Note: if you want to use subdomain as a tenant name then you can use `set_current_tenant_by_subdomain` method. Just add it into your application_controller. In this case `set_current_tenant` is not needed.
75
+
76
+ == How It Works
74
77
 
75
78
  About the main principles you can read here http://blog.empowercampaigns.com/post/1044240481/multi-tenant-data-and-mysql.
76
79
 
@@ -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 = Rails.root.to_s + "/config/#{CONFIG_FILE_NAME}"
10
+ dest = "config/#{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,6 +1,6 @@
1
1
  # simple example
2
2
  #
3
- # Multitenant::Mysql.active_record_configs = {
3
+ # Multitenant::Mysql.arc = {
4
4
  # models: ['Book', 'Task'],
5
5
  # tenant_model: { name: 'Subdomain', tenant_name_attr: name }
6
6
  # }
@@ -11,7 +11,7 @@
11
11
  # name - model name
12
12
  # tenant_name_attr - attribute used to fetch tenant name
13
13
 
14
- Multitenant::Mysql.active_record_configs = {
14
+ Multitenant::Mysql.arc = {
15
15
  models: [],
16
16
  tenant_model: { name: '' }
17
17
  }
@@ -13,7 +13,7 @@ module Multitenant
13
13
  "add_column :#{model.table_name}, :tenant, :string"
14
14
  }
15
15
 
16
- dest_path = Rails.root.to_s + "/db/migrate/#{migration_number}_#{MIGRATION_NAME}.rb"
16
+ dest_path = "db/migrate/#{migration_number}_#{MIGRATION_NAME}.rb"
17
17
  migration = File.new(dest_path, "w")
18
18
  migration.puts(migration_code(actions))
19
19
  migration.close
@@ -35,7 +35,7 @@ end)
35
35
  end
36
36
 
37
37
  def migration_exists?
38
- migrations = Dir.entries(Rails.root.to_s + "/db/migrate")
38
+ migrations = Dir.entries("db/migrate")
39
39
  migrations.any? { |m| m.include?(MIGRATION_NAME) }
40
40
  end
41
41
  end
@@ -1,58 +1,6 @@
1
1
  require 'multitenant-mysql/version'
2
- require 'multitenant-mysql/active_record_extension'
3
2
  require 'multitenant-mysql/action_controller_extension'
4
3
  require 'multitenant-mysql/conf_file'
4
+ require 'multitenant-mysql/arc'
5
5
 
6
- require_relative '../rails/init'
7
-
8
- module Multitenant
9
- module Mysql
10
-
11
- class << self
12
-
13
- DEFAULT_TENANT_NAME_ATTR = [:name, :title]
14
-
15
- def active_record_configs=(configs)
16
- @configs = configs
17
- end
18
-
19
- def active_record_configs
20
- @configs
21
- end
22
-
23
- alias_method :arc, :active_record_configs
24
-
25
- def tenant
26
- arc[:tenant_model][:name].constantize
27
- rescue
28
- if arc.blank? || arc[:tenant_model].blank? || arc[:tenant_model][:name].blank?
29
- raise "
30
- Multitenant::Mysql: You should specify model which stores info about tenants.
31
- E.g. in initializer:
32
- Multitenant::Mysql.arc = {
33
- tenant_model: { name: 'Subdomain' }
34
- }
35
- "
36
- else
37
- raise "Please check your multitenant-mysql configs. Seems like you are trying to use model which doesn't exist: #{arc[:tenant_model][:name]}"
38
- end
39
- end
40
-
41
- def models
42
- active_record_configs[:models]
43
- end
44
-
45
- def tenant_name_attr
46
- return active_record_configs[:tenant_model][:tenant_name_attr] if active_record_configs[:tenant_model][:tenant_name_attr]
47
-
48
- DEFAULT_TENANT_NAME_ATTR.each do |name|
49
- return name if tenant.column_names.include?(name.to_s)
50
- end
51
-
52
- raise 'You should specify tenant name attribute or use one default: name, title'
53
- end
54
-
55
- end
56
-
57
- end
58
- end
6
+ require 'multitenant-mysql/active_record_extension'
@@ -14,4 +14,15 @@ class ActionController::Base
14
14
  Multitenant::Mysql::ConnectionSwitcher.new(self, @@tenant_method).execute
15
15
  end
16
16
  end
17
+
18
+ def self.set_current_tenant_by_subdomain
19
+ require Multitenant::Mysql::ConfFile.path
20
+
21
+ before_filter :establish_tenant_connection_by_subdomain
22
+
23
+ def establish_tenant_connection_by_subdomain
24
+ tenant_name = request.subdomain
25
+ Multitenant::Mysql::ConnectionSwitcher.set_tenant(tenant_name)
26
+ end
27
+ end
17
28
  end
@@ -7,4 +7,35 @@ class ActiveRecord::Base
7
7
  ActiveRecord::Base.connection.execute "flush privileges;"
8
8
  end
9
9
  end
10
+
11
+ def self.inherited(child)
12
+ return unless FileTest.exist?(Multitenant::Mysql::ConfFile.full_path) # do nothing if no config file provided
13
+ require Multitenant::Mysql::ConfFile.path
14
+
15
+ model_name = child.to_s
16
+ if Multitenant::Mysql.models.include? model_name
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
23
+
24
+ self.original_table_name = self.table_name
25
+ self.table_name = view_name
26
+ self.primary_key = :id
27
+
28
+ def self.new(*args)
29
+ object = super(*args)
30
+ object.id = nil # workaround for https://github.com/rails/rails/issues/5982
31
+ object
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ if Multitenant::Mysql.tenant == child
38
+ child.send :acts_as_tenant
39
+ end
40
+ end
10
41
  end
@@ -0,0 +1,52 @@
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
@@ -2,6 +2,21 @@ module Multitenant
2
2
  module Mysql
3
3
  class NoTenantRegistratedError < StandardError; end;
4
4
 
5
+ class DB
6
+ class << self
7
+ def configs
8
+ Rails.configuration.database_configuration[Rails.env]
9
+ end
10
+
11
+ def establish_connection_for tenant_name
12
+ config = configs
13
+ config['username'] = tenant_name.blank? ? 'root' : tenant_name
14
+ ActiveRecord::Base.establish_connection(config)
15
+ true
16
+ end
17
+ end
18
+ end
19
+
5
20
  class Tenant
6
21
  def self.exists? tenant_name
7
22
  return true if tenant_name.blank?
@@ -22,25 +37,13 @@ module Multitenant
22
37
 
23
38
  def self.set_tenant(tenant_name)
24
39
  return unless Tenant.exists?(tenant_name)
25
-
26
- config = Rails.configuration.database_configuration[Rails.env]
27
- config['username'] = tenant_name.blank? ? 'root' : tenant_name
28
- ActiveRecord::Base.establish_connection(config)
40
+ DB.establish_connection_for tenant_name
29
41
  end
30
42
 
31
43
  def execute
32
- config = db_config
33
-
34
44
  tenant_name = action_controller.send(method)
35
45
  return unless Tenant.exists?(tenant_name)
36
-
37
- config['username'] = tenant_name.blank? ? 'root' : tenant_name
38
- ActiveRecord::Base.establish_connection(config)
39
- true
40
- end
41
-
42
- def db_config
43
- Rails.configuration.database_configuration[Rails.env]
46
+ DB.establish_connection_for tenant_name
44
47
  end
45
48
  end
46
49
 
@@ -1,5 +1,5 @@
1
1
  module Multitenant
2
2
  module Mysql
3
- VERSION = "1.0.1"
3
+ VERSION = "1.0.2"
4
4
  end
5
5
  end
data/rails/init.rb CHANGED
@@ -1,34 +1 @@
1
- ActiveRecord::Base.class_eval do
2
- def self.inherited(child)
3
-
4
- return unless FileTest.exist?(Multitenant::Mysql::ConfFile.full_path) # do nothing if no config file provided
5
-
6
- require Multitenant::Mysql::ConfFile.path
7
-
8
- model_name = child.to_s
9
- if Multitenant::Mysql.models.include? model_name
10
- view_name = model_name.to_s.downcase.pluralize + "_view"
11
-
12
- # check whether the view exists in db
13
- if ActiveRecord::Base.connection.table_exists? view_name
14
- child.class_eval do
15
- cattr_accessor :original_table_name
16
-
17
- self.original_table_name = self.table_name
18
- self.table_name = view_name
19
- self.primary_key = :id
20
-
21
- def self.new(*args)
22
- object = super(*args)
23
- object.id = nil # workaround for https://github.com/rails/rails/issues/5982
24
- object
25
- end
26
- end
27
- end
28
- end
29
-
30
- if Multitenant::Mysql.tenant == child
31
- child.send :acts_as_tenant
32
- end
33
- end
34
- end
1
+ require 'multitenant-mysql'
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ActionController::Base do
4
- subject { ActionController::Base }
5
4
 
6
5
  context '.set_current_tenant' do
6
+ subject { ActionController::Base }
7
+
7
8
  it 'should raise an error when no tenant method provided' do
8
9
  expect { subject.set_current_tenant }.to raise_error
9
10
  end
@@ -17,4 +18,19 @@ describe ActionController::Base do
17
18
  expect( subject.new.establish_tenant_connection ).to eql('ok')
18
19
  end
19
20
  end
21
+
22
+ context '.set_current_tenant_by_subdomain' do
23
+ subject { ActionController::Base.new }
24
+
25
+ before do
26
+ ActionController::Base.set_current_tenant_by_subdomain
27
+ end
28
+
29
+ it 'should set current tenant by subdomain' do
30
+ subject.stub_chain(:request, :subdomain).and_return('yahoo')
31
+ Multitenant::Mysql::ConnectionSwitcher.should_receive(:set_tenant).with('yahoo').and_return(true)
32
+
33
+ expect( subject.establish_tenant_connection_by_subdomain ).to be
34
+ end
35
+ end
20
36
  end
@@ -63,5 +63,17 @@ describe Multitenant::Mysql do
63
63
  end
64
64
  end
65
65
 
66
+ context 'alias methods' do
67
+ it 'should use aliased setter' do
68
+ subject.arc = 'ARC'
69
+ expect(subject.active_record_configs).to eql('ARC')
70
+ end
71
+
72
+ it 'should use aliased getter' do
73
+ subject.active_record_configs = 'ABC'
74
+ expect(subject.arc).to eql('ABC')
75
+ end
76
+ end
77
+
66
78
  end
67
79
  end
@@ -14,7 +14,7 @@ describe Multitenant::Mysql::ConnectionSwitcher do
14
14
  ac_mock = double('ActionController::Base')
15
15
  ac_mock.should_receive(:send).and_return('unexisting tenant')
16
16
  switcher = Multitenant::Mysql::ConnectionSwitcher.new(ac_mock, :tenant_method)
17
- switcher.stub(:db_config).and_return({ username: 'root' })
17
+ Multitenant::Mysql::DB.stub(:configs).and_return({ 'username' => 'root' })
18
18
 
19
19
  expect { switcher.execute }.to raise_error(Multitenant::Mysql::NoTenantRegistratedError)
20
20
  end
@@ -27,11 +27,23 @@ describe Multitenant::Mysql::ConnectionSwitcher do
27
27
  ac_mock = double('ActionController::Base')
28
28
  ac_mock.should_receive(:tenant_method)
29
29
  switcher = Multitenant::Mysql::ConnectionSwitcher.new(ac_mock, :tenant_method)
30
- switcher.stub(:db_config).and_return({ username: 'root' })
30
+ Multitenant::Mysql::DB.stub(:configs).and_return({ 'username' => 'root' })
31
31
 
32
32
  ActiveRecord::Base.should_receive(:establish_connection)
33
33
 
34
34
  expect( switcher.execute ).to be
35
35
  end
36
36
  end
37
+
38
+ context '.set_tenant' do
39
+ subject { Multitenant::Mysql::ConnectionSwitcher }
40
+
41
+ it 'should change db connection' do
42
+ Multitenant::Mysql::Tenant.stub(:exists?).and_return(true)
43
+ Multitenant::Mysql::DB.stub(:configs).and_return({ 'username' => 'root' })
44
+
45
+ ActiveRecord::Base.should_receive(:establish_connection).with({ 'username' => 'google'})
46
+ expect( subject.set_tenant('google') ).to be
47
+ end
48
+ end
37
49
  end
@@ -5,6 +5,7 @@ describe ActiveRecord::Base do
5
5
 
6
6
  before do
7
7
  Multitenant::Mysql::ConfFile.path = CONF_FILE_PATH
8
+ Multitenant::Mysql.arc = { models: ['Book', 'Task'] }
8
9
  end
9
10
 
10
11
  it 'should respond to acts_as_tenant' 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.0.1
4
+ version: 1.0.2
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-02-18 00:00:00.000000000 Z
12
+ date: 2013-03-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -51,15 +51,16 @@ files:
51
51
  - lib/multitenant-mysql.rb
52
52
  - lib/multitenant-mysql/action_controller_extension.rb
53
53
  - lib/multitenant-mysql/active_record_extension.rb
54
+ - lib/multitenant-mysql/arc.rb
54
55
  - lib/multitenant-mysql/conf_file.rb
55
56
  - lib/multitenant-mysql/connection_switcher.rb
56
57
  - lib/multitenant-mysql/version.rb
57
58
  - multitenant-mysql.gemspec
58
59
  - rails/init.rb
59
60
  - spec/action_controller_extension_spec.rb
61
+ - spec/arc_spec.rb
60
62
  - spec/conf_file_spec.rb
61
63
  - spec/connection_switcher_spec.rb
62
- - spec/multitenant_mysql_spec.rb
63
64
  - spec/rails/active_record_base_spec.rb
64
65
  - spec/spec_helper.rb
65
66
  - spec/support/multitenant_mysql_conf.rb
@@ -83,15 +84,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  version: '0'
84
85
  requirements: []
85
86
  rubyforge_project:
86
- rubygems_version: 1.8.24
87
+ rubygems_version: 1.8.21
87
88
  signing_key:
88
89
  specification_version: 3
89
90
  summary: Add multi-tenancy to Rails application using MySql views
90
91
  test_files:
91
92
  - spec/action_controller_extension_spec.rb
93
+ - spec/arc_spec.rb
92
94
  - spec/conf_file_spec.rb
93
95
  - spec/connection_switcher_spec.rb
94
- - spec/multitenant_mysql_spec.rb
95
96
  - spec/rails/active_record_base_spec.rb
96
97
  - spec/spec_helper.rb
97
98
  - spec/support/multitenant_mysql_conf.rb