multitenant-mysql 1.0.1 → 1.0.2

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/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