activerecord-multi-tenant 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "4.2.7.1"
@@ -0,0 +1,110 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ actionmailer (4.2.7.1)
5
+ actionpack (= 4.2.7.1)
6
+ actionview (= 4.2.7.1)
7
+ activejob (= 4.2.7.1)
8
+ mail (~> 2.5, >= 2.5.4)
9
+ rails-dom-testing (~> 1.0, >= 1.0.5)
10
+ actionpack (4.2.7.1)
11
+ actionview (= 4.2.7.1)
12
+ activesupport (= 4.2.7.1)
13
+ rack (~> 1.6)
14
+ rack-test (~> 0.6.2)
15
+ rails-dom-testing (~> 1.0, >= 1.0.5)
16
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
17
+ actionview (4.2.7.1)
18
+ activesupport (= 4.2.7.1)
19
+ builder (~> 3.1)
20
+ erubis (~> 2.7.0)
21
+ rails-dom-testing (~> 1.0, >= 1.0.5)
22
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
23
+ activejob (4.2.7.1)
24
+ activesupport (= 4.2.7.1)
25
+ globalid (>= 0.3.0)
26
+ activemodel (4.2.7.1)
27
+ activesupport (= 4.2.7.1)
28
+ builder (~> 3.1)
29
+ activerecord (4.2.7.1)
30
+ activemodel (= 4.2.7.1)
31
+ activesupport (= 4.2.7.1)
32
+ arel (~> 6.0)
33
+ activesupport (4.2.7.1)
34
+ i18n (~> 0.7)
35
+ json (~> 1.7, >= 1.7.7)
36
+ minitest (~> 5.1)
37
+ thread_safe (~> 0.3, >= 0.3.4)
38
+ tzinfo (~> 1.1)
39
+ appraisal (2.1.0)
40
+ bundler
41
+ rake
42
+ thor (>= 0.14.0)
43
+ arel (6.0.4)
44
+ builder (3.2.2)
45
+ concurrent-ruby (1.0.4)
46
+ erubis (2.7.0)
47
+ globalid (0.3.7)
48
+ activesupport (>= 4.1.0)
49
+ i18n (0.7.0)
50
+ json (1.8.3)
51
+ loofah (2.0.3)
52
+ nokogiri (>= 1.5.9)
53
+ mail (2.6.4)
54
+ mime-types (>= 1.16, < 4)
55
+ mime-types (3.1)
56
+ mime-types-data (~> 3.2015)
57
+ mime-types-data (3.2016.0521)
58
+ mini_portile2 (2.1.0)
59
+ minitest (5.10.1)
60
+ nokogiri (1.7.0)
61
+ mini_portile2 (~> 2.1.0)
62
+ rack (1.6.5)
63
+ rack-test (0.6.3)
64
+ rack (>= 1.0)
65
+ rails (4.2.7.1)
66
+ actionmailer (= 4.2.7.1)
67
+ actionpack (= 4.2.7.1)
68
+ actionview (= 4.2.7.1)
69
+ activejob (= 4.2.7.1)
70
+ activemodel (= 4.2.7.1)
71
+ activerecord (= 4.2.7.1)
72
+ activesupport (= 4.2.7.1)
73
+ bundler (>= 1.3.0, < 2.0)
74
+ railties (= 4.2.7.1)
75
+ sprockets-rails
76
+ rails-deprecated_sanitizer (1.0.3)
77
+ activesupport (>= 4.2.0.alpha)
78
+ rails-dom-testing (1.0.8)
79
+ activesupport (>= 4.2.0.beta, < 5.0)
80
+ nokogiri (~> 1.6)
81
+ rails-deprecated_sanitizer (>= 1.0.1)
82
+ rails-html-sanitizer (1.0.3)
83
+ loofah (~> 2.0)
84
+ railties (4.2.7.1)
85
+ actionpack (= 4.2.7.1)
86
+ activesupport (= 4.2.7.1)
87
+ rake (>= 0.8.7)
88
+ thor (>= 0.18.1, < 2.0)
89
+ rake (12.0.0)
90
+ sprockets (3.7.1)
91
+ concurrent-ruby (~> 1.0)
92
+ rack (> 1, < 3)
93
+ sprockets-rails (3.2.0)
94
+ actionpack (>= 4.0)
95
+ activesupport (>= 4.0)
96
+ sprockets (>= 3.0.0)
97
+ thor (0.19.4)
98
+ thread_safe (0.3.5)
99
+ tzinfo (1.2.2)
100
+ thread_safe (~> 0.1)
101
+
102
+ PLATFORMS
103
+ ruby
104
+
105
+ DEPENDENCIES
106
+ appraisal
107
+ rails (= 4.2.7.1)
108
+
109
+ BUNDLED WITH
110
+ 1.11.2
@@ -0,0 +1,6 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "5.0.1"
@@ -0,0 +1,115 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ actioncable (5.0.1)
5
+ actionpack (= 5.0.1)
6
+ nio4r (~> 1.2)
7
+ websocket-driver (~> 0.6.1)
8
+ actionmailer (5.0.1)
9
+ actionpack (= 5.0.1)
10
+ actionview (= 5.0.1)
11
+ activejob (= 5.0.1)
12
+ mail (~> 2.5, >= 2.5.4)
13
+ rails-dom-testing (~> 2.0)
14
+ actionpack (5.0.1)
15
+ actionview (= 5.0.1)
16
+ activesupport (= 5.0.1)
17
+ rack (~> 2.0)
18
+ rack-test (~> 0.6.3)
19
+ rails-dom-testing (~> 2.0)
20
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
21
+ actionview (5.0.1)
22
+ activesupport (= 5.0.1)
23
+ builder (~> 3.1)
24
+ erubis (~> 2.7.0)
25
+ rails-dom-testing (~> 2.0)
26
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
27
+ activejob (5.0.1)
28
+ activesupport (= 5.0.1)
29
+ globalid (>= 0.3.6)
30
+ activemodel (5.0.1)
31
+ activesupport (= 5.0.1)
32
+ activerecord (5.0.1)
33
+ activemodel (= 5.0.1)
34
+ activesupport (= 5.0.1)
35
+ arel (~> 7.0)
36
+ activesupport (5.0.1)
37
+ concurrent-ruby (~> 1.0, >= 1.0.2)
38
+ i18n (~> 0.7)
39
+ minitest (~> 5.1)
40
+ tzinfo (~> 1.1)
41
+ appraisal (2.1.0)
42
+ bundler
43
+ rake
44
+ thor (>= 0.14.0)
45
+ arel (7.1.4)
46
+ builder (3.2.2)
47
+ concurrent-ruby (1.0.4)
48
+ erubis (2.7.0)
49
+ globalid (0.3.7)
50
+ activesupport (>= 4.1.0)
51
+ i18n (0.7.0)
52
+ loofah (2.0.3)
53
+ nokogiri (>= 1.5.9)
54
+ mail (2.6.4)
55
+ mime-types (>= 1.16, < 4)
56
+ method_source (0.8.2)
57
+ mime-types (3.1)
58
+ mime-types-data (~> 3.2015)
59
+ mime-types-data (3.2016.0521)
60
+ mini_portile2 (2.1.0)
61
+ minitest (5.10.1)
62
+ nio4r (1.2.1)
63
+ nokogiri (1.7.0)
64
+ mini_portile2 (~> 2.1.0)
65
+ rack (2.0.1)
66
+ rack-test (0.6.3)
67
+ rack (>= 1.0)
68
+ rails (5.0.1)
69
+ actioncable (= 5.0.1)
70
+ actionmailer (= 5.0.1)
71
+ actionpack (= 5.0.1)
72
+ actionview (= 5.0.1)
73
+ activejob (= 5.0.1)
74
+ activemodel (= 5.0.1)
75
+ activerecord (= 5.0.1)
76
+ activesupport (= 5.0.1)
77
+ bundler (>= 1.3.0, < 2.0)
78
+ railties (= 5.0.1)
79
+ sprockets-rails (>= 2.0.0)
80
+ rails-dom-testing (2.0.2)
81
+ activesupport (>= 4.2.0, < 6.0)
82
+ nokogiri (~> 1.6)
83
+ rails-html-sanitizer (1.0.3)
84
+ loofah (~> 2.0)
85
+ railties (5.0.1)
86
+ actionpack (= 5.0.1)
87
+ activesupport (= 5.0.1)
88
+ method_source
89
+ rake (>= 0.8.7)
90
+ thor (>= 0.18.1, < 2.0)
91
+ rake (12.0.0)
92
+ sprockets (3.7.1)
93
+ concurrent-ruby (~> 1.0)
94
+ rack (> 1, < 3)
95
+ sprockets-rails (3.2.0)
96
+ actionpack (>= 4.0)
97
+ activesupport (>= 4.0)
98
+ sprockets (>= 3.0.0)
99
+ thor (0.19.4)
100
+ thread_safe (0.3.5)
101
+ tzinfo (1.2.2)
102
+ thread_safe (~> 0.1)
103
+ websocket-driver (0.6.4)
104
+ websocket-extensions (>= 0.1.0)
105
+ websocket-extensions (0.1.2)
106
+
107
+ PLATFORMS
108
+ ruby
109
+
110
+ DEPENDENCIES
111
+ appraisal
112
+ rails (= 5.0.1)
113
+
114
+ BUNDLED WITH
115
+ 1.11.2
@@ -1,6 +1,8 @@
1
+ require 'activerecord-multi-tenant/controller_extensions'
1
2
  require 'activerecord-multi-tenant/copy_from_client'
2
3
  require 'activerecord-multi-tenant/default_scope'
3
4
  require 'activerecord-multi-tenant/migrations'
5
+ require 'activerecord-multi-tenant/model_extensions'
4
6
  require 'activerecord-multi-tenant/multi_tenant'
5
7
  require 'activerecord-multi-tenant/referential_integrity'
6
8
  require 'activerecord-multi-tenant/version'
@@ -0,0 +1,22 @@
1
+ module MultiTenant
2
+ module ControllerExtensions
3
+ def set_current_tenant_through_filter
4
+ self.class_eval do
5
+ helper_method :current_tenant
6
+
7
+ private
8
+ def set_current_tenant(current_tenant_object)
9
+ MultiTenant.current_tenant = current_tenant_object
10
+ end
11
+
12
+ def current_tenant
13
+ MultiTenant.current_tenant
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ if defined?(ActionController::Base)
21
+ ActionController::Base.extend MultiTenant::ControllerExtensions
22
+ end
@@ -3,7 +3,7 @@ class ActiveRecord::Base
3
3
  alias :unscoped_orig :unscoped
4
4
  def unscoped
5
5
  if respond_to?(:scoped_by_tenant?) && MultiTenant.current_tenant_id
6
- unscoped_orig.where(arel_table[MultiTenant.partition_key].eq(MultiTenant.current_tenant_id))
6
+ unscoped_orig.where(arel_table[self.partition_key].eq(MultiTenant.current_tenant_id))
7
7
  else
8
8
  unscoped_orig
9
9
  end
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  ret = orig_create_table(table_name, options.except(:partition_key), &block)
28
28
  if options[:partition_key] && options[:partition_key].to_s != 'id'
29
29
  execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{table_name}_pkey"
30
- execute "ALTER TABLE #{table_name} ADD PRIMARY KEY(id, #{options[:partition_key]})"
30
+ execute "ALTER TABLE #{table_name} ADD PRIMARY KEY(id, \"#{options[:partition_key]}\")"
31
31
  end
32
32
  ret
33
33
  end
@@ -0,0 +1,120 @@
1
+ module MultiTenant
2
+ module ModelExtensionsClassMethods
3
+ def multi_tenant(tenant, options = {})
4
+ # Workaround for https://github.com/citusdata/citus/issues/687
5
+ if to_s.underscore.to_sym == tenant
6
+ before_create -> { self.id ||= self.class.connection.select_value("SELECT nextval('" + [self.class.table_name, self.class.primary_key, 'seq'].join('_') + "'::regclass)") }
7
+ end
8
+
9
+ # Typically we don't need to run on the tenant model itself
10
+ if to_s.underscore.to_sym != tenant
11
+ # Provide fallback primary key setting to ease integration with the typical Rails app
12
+ self.primary_key = 'id' if primary_key.nil?
13
+
14
+ MultiTenant.set_tenant_klass(tenant)
15
+
16
+ class << self
17
+ def scoped_by_tenant?
18
+ true
19
+ end
20
+
21
+ def partition_key
22
+ @partition_key
23
+ end
24
+ end
25
+
26
+ @partition_key = options[:partition_key] || MultiTenant.partition_key
27
+ partition_key = @partition_key
28
+
29
+ # Create the association
30
+ belongs_to tenant, options.slice(:class_name, :inverse_of).merge(foreign_key: partition_key)
31
+
32
+ # Ensure all queries include the partition key
33
+ default_scope lambda {
34
+ if MultiTenant.current_tenant_id
35
+ where(partition_key.to_sym => MultiTenant.current_tenant_id)
36
+ else
37
+ Rails::VERSION::MAJOR < 4 ? scoped : all
38
+ end
39
+ }
40
+
41
+ # New instances should have the tenant set
42
+ before_validation Proc.new { |record|
43
+ if MultiTenant.current_tenant_id && record.public_send(partition_key.to_sym).nil?
44
+ record.public_send("#{partition_key}=".to_sym, MultiTenant.current_tenant_id)
45
+ end
46
+ }, on: :create
47
+
48
+ # Validate that associations belong to the tenant, currently only for belongs_to
49
+ polymorphic_foreign_keys = reflect_on_all_associations(:belongs_to).select do |a|
50
+ a.options[:polymorphic]
51
+ end.map { |a| a.foreign_key }
52
+
53
+ reflect_on_all_associations(:belongs_to).each do |a|
54
+ unless a == reflect_on_association(tenant) || polymorphic_foreign_keys.include?(a.foreign_key)
55
+ association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
56
+ validates_each a.foreign_key.to_sym do |record, attr, value|
57
+ primary_key = if association_class.respond_to?(:primary_key)
58
+ association_class.primary_key
59
+ else
60
+ a.primary_key
61
+ end.to_sym
62
+ record.errors.add attr, 'association is invalid [MultiTenant]' unless value.nil? || association_class.where(primary_key => value).any?
63
+ end
64
+ end
65
+ end
66
+
67
+ to_include = Module.new do
68
+ define_method "#{partition_key}=" do |integer|
69
+ write_attribute("#{partition_key}", integer)
70
+ raise MultiTenant::TenantIsImmutable if send("#{partition_key}_changed?") && persisted? && !send("#{partition_key}_was").nil?
71
+ integer
72
+ end
73
+
74
+ define_method "#{MultiTenant.tenant_klass.to_s}=" do |model|
75
+ super(model)
76
+ raise MultiTenant::TenantIsImmutable if send("#{partition_key}_changed?") && persisted? && !send("#{partition_key}_was").nil?
77
+ model
78
+ end
79
+
80
+ define_method "#{MultiTenant.tenant_klass.to_s}" do
81
+ if !MultiTenant.current_tenant.nil? && !MultiTenant.current_tenant.is_a?(MultiTenant::TenantIdWrapper) && public_send(partition_key) == MultiTenant.current_tenant.id
82
+ return MultiTenant.current_tenant
83
+ else
84
+ super()
85
+ end
86
+ end
87
+ end
88
+ include to_include
89
+
90
+ around_save -> (record, block) {
91
+ if persisted? && MultiTenant.current_tenant_id.nil?
92
+ MultiTenant.with_id(record.public_send(partition_key)) { block.call }
93
+ else
94
+ block.call
95
+ end
96
+ }
97
+
98
+ around_update -> (record, block) {
99
+ if MultiTenant.current_tenant_id.nil?
100
+ MultiTenant.with_id(record.public_send(partition_key)) { block.call }
101
+ else
102
+ block.call
103
+ end
104
+ }
105
+
106
+ around_destroy -> (record, block) {
107
+ if MultiTenant.current_tenant_id.nil?
108
+ MultiTenant.with_id(record.public_send(partition_key)) { block.call }
109
+ else
110
+ block.call
111
+ end
112
+ }
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ if defined?(ActiveRecord::Base)
119
+ ActiveRecord::Base.extend(MultiTenant::ModelExtensionsClassMethods)
120
+ end
@@ -1,30 +1,51 @@
1
- # Note that the actual details here are subject to change, and you should avoid
2
- # calling acts_as_tenant methods directly as a user of this library
3
- require 'acts_as_tenant'
1
+ require 'request_store'
4
2
 
5
3
  module MultiTenant
6
- def self.current_tenant
7
- ActsAsTenant.current_tenant
4
+ @@tenant_klass = nil
5
+
6
+ def self.set_tenant_klass(klass)
7
+ @@tenant_klass = klass
8
+ end
9
+
10
+ def self.tenant_klass
11
+ @@tenant_klass
12
+ end
13
+
14
+ def self.partition_key
15
+ "#{@@tenant_klass.to_s}_id"
8
16
  end
9
17
 
10
18
  def self.current_tenant=(tenant)
11
- ActsAsTenant.current_tenant = tenant
19
+ RequestStore.store[:current_tenant] = tenant
20
+ end
21
+
22
+ def self.current_tenant
23
+ RequestStore.store[:current_tenant]
12
24
  end
13
25
 
14
26
  def self.current_tenant_id
15
- ActsAsTenant.current_tenant.try(:id)
27
+ current_tenant.try(:id)
16
28
  end
17
29
 
18
30
  def self.with(tenant, &block)
19
- ActsAsTenant.with_tenant(tenant, &block)
20
- end
31
+ old_tenant = self.current_tenant
32
+ self.current_tenant = tenant
33
+ value = block.call
34
+ return value
21
35
 
22
- def self.partition_key
23
- ActsAsTenant.fkey
36
+ ensure
37
+ self.current_tenant = old_tenant
24
38
  end
25
39
 
26
40
  def self.with_id(tenant_id, &block)
27
- MultiTenant.with(TenantIdWrapper.new(id: tenant_id), &block)
41
+ if MultiTenant.current_tenant_id == tenant_id
42
+ block.call
43
+ else
44
+ MultiTenant.with(TenantIdWrapper.new(id: tenant_id), &block)
45
+ end
46
+ end
47
+
48
+ class TenantIsImmutable < StandardError
28
49
  end
29
50
 
30
51
  class TenantIdWrapper
@@ -37,36 +58,4 @@ module MultiTenant
37
58
  def new_record?; true; end
38
59
  def touch; nil; end
39
60
  end
40
-
41
- module ModelExtensions
42
- def self.included(base)
43
- base.extend(ClassMethods)
44
- end
45
-
46
- module ClassMethods
47
- def multi_tenant(tenant, options = {})
48
- # Provide fallback primary key setting to ease integration with the typical Rails app
49
- self.primary_key = 'id' if primary_key.nil?
50
-
51
- # Typically we don't need to run on the tenant model itself
52
- if to_s.underscore.to_sym != tenant
53
- belongs_to(tenant)
54
- acts_as_tenant(tenant, options)
55
-
56
- around_save -> (record, block) { persisted? ? MultiTenant.with_id(record.public_send(tenant.to_s + '_id')) { block.call } : block.call }
57
- around_update -> (record, block) { MultiTenant.with_id(record.public_send(tenant.to_s + '_id')) { block.call } }
58
- around_destroy -> (record, block) { MultiTenant.with_id(record.public_send(tenant.to_s + '_id')) { block.call } }
59
- end
60
-
61
- # Workaround for https://github.com/citusdata/citus/issues/687
62
- if to_s.underscore.to_sym == tenant
63
- before_create -> { self.id ||= self.class.connection.select_value("SELECT nextval('" + [self.class.table_name, self.class.primary_key, 'seq'].join('_') + "'::regclass)") }
64
- end
65
- end
66
- end
67
- end
68
- end
69
-
70
- if defined?(ActiveRecord::Base)
71
- ActiveRecord::Base.send(:include, MultiTenant::ModelExtensions)
72
61
  end