activerecord-multi-tenant 0.2.1 → 0.3.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.
@@ -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