Module: MultiTenant

Defined in:
lib/activerecord-multi-tenant/fast_truncate.rb,
lib/activerecord-multi-tenant/version.rb,
lib/activerecord-multi-tenant/migrations.rb,
lib/activerecord-multi-tenant/multi_tenant.rb,
lib/activerecord-multi-tenant/query_monitor.rb,
lib/activerecord-multi-tenant/query_rewriter.rb,
lib/activerecord-multi-tenant/copy_from_client.rb,
lib/activerecord-multi-tenant/model_extensions.rb,
lib/activerecord-multi-tenant/controller_extensions.rb,
lib/activerecord-multi-tenant/arel_visitors_depth_first.rb

Overview

Add generic warning when queries fail and there is no tenant set

Defined Under Namespace

Modules: ControllerExtensions, CopyFromClient, DatabaseStatements, FastTruncate, MigrationExtensions, ModelExtensionsClassMethods, TenantValueVisitor Classes: ArelTenantVisitor, ArelVisitorsDepthFirst, BaseTenantEnforcementClause, Context, CopyFromClientHelper, Current, QueryMonitor, Table, TenantEnforcementClause, TenantIsImmutable, TenantJoinEnforcementClause

Constant Summary collapse

VERSION =
'2.2.0'
@@enable_with_lock_workaround =

Workaroud to make “with_lock” work until github.com/citusdata/citus/issues/1236 is fixed

false
@@enable_query_monitor =

Option to enable query monitor

false

Class Method Summary collapse

Class Method Details

.current_tenantObject



63
64
65
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 63

def self.current_tenant
  Current.tenant
end

.current_tenant=(tenant) ⇒ Object



59
60
61
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 59

def self.current_tenant=(tenant)
  Current.tenant = tenant
end

.current_tenant_classObject



75
76
77
78
79
80
81
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 75

def self.current_tenant_class
  if current_tenant_is_id?
    MultiTenant.default_tenant_class || fail('Only have tenant id, and no default tenant class set')
  elsif current_tenant
    MultiTenant.current_tenant.class.name
  end
end

.current_tenant_idObject



67
68
69
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 67

def self.current_tenant_id
  current_tenant_is_id? ? current_tenant : current_tenant.try(:id)
end

.current_tenant_is_id?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 71

def self.current_tenant_is_id?
  current_tenant.is_a?(String) || current_tenant.is_a?(Integer)
end

.default_tenant_classObject



18
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 18

def self.default_tenant_class; @@default_tenant_class ||= nil; end

.default_tenant_class=(tenant_class) ⇒ Object

In some cases we only have an ID - if defined we’ll return the default tenant class in such cases



17
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 17

def self.default_tenant_class=(tenant_class); @@default_tenant_class = tenant_class; end

.enable_query_monitorObject



5
# File 'lib/activerecord-multi-tenant/query_monitor.rb', line 5

def self.enable_query_monitor; @@enable_query_monitor = true; end

.enable_with_lock_workaroundObject



27
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 27

def self.enable_with_lock_workaround; @@enable_with_lock_workaround = true; end

.enable_write_only_modeObject

Write-only Mode - this only adds the tenant_id to new records, but doesn’t require its presence for SELECTs/UPDATEs/DELETEs



22
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 22

def self.enable_write_only_mode; @@enable_write_only_mode = true; end

.load_current_tenant!Object



83
84
85
86
87
88
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 83

def self.load_current_tenant!
  return MultiTenant.current_tenant if MultiTenant.current_tenant && !current_tenant_is_id?
  raise 'MultiTenant.current_tenant must be set to load' if MultiTenant.current_tenant.nil?
  klass = MultiTenant.default_tenant_class || fail('Only have tenant id, and no default tenant class set')
  self.current_tenant = klass.find(MultiTenant.current_tenant_id)
end

.multi_tenant_model_for_arel(arel) ⇒ Object



50
51
52
53
54
55
56
57
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 50

def self.multi_tenant_model_for_arel(arel)
  return nil unless arel.respond_to?(:ast)
  if arel.ast.relation.is_a? Arel::Nodes::JoinSource
    MultiTenant.multi_tenant_model_for_table(arel.ast.relation.left.table_name)
  else
    MultiTenant.multi_tenant_model_for_table(arel.ast.relation.table_name)
  end
end

.multi_tenant_model_for_table(table_name) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 38

def self.multi_tenant_model_for_table(table_name)
  @@multi_tenant_models ||= []

  if !defined?(@@multi_tenant_model_table_names)
    @@multi_tenant_model_table_names = @@multi_tenant_models.map { |model|
      [model.table_name, model] if model.table_name
    }.compact.to_h
  end

  @@multi_tenant_model_table_names[table_name.to_s]
end

.partition_key(tenant_name) ⇒ Object



12
13
14
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 12

def self.partition_key(tenant_name)
  "#{tenant_name.to_s}_id"
end

.query_monitor_enabled?Boolean

Returns:

  • (Boolean)


6
# File 'lib/activerecord-multi-tenant/query_monitor.rb', line 6

def self.query_monitor_enabled?; @@enable_query_monitor; end

.register_multi_tenant_model(model_klass) ⇒ Object

Registry that maps table names to models (used by the query rewriter)



31
32
33
34
35
36
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 31

def self.register_multi_tenant_model(model_klass)
  @@multi_tenant_models ||= []
  @@multi_tenant_models.push(model_klass)

  remove_class_variable(:@@multi_tenant_model_table_names) if defined?(@@multi_tenant_model_table_names)
end

.tenant_klass_defined?(tenant_name) ⇒ Boolean

Returns:

  • (Boolean)


8
9
10
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 8

def self.tenant_klass_defined?(tenant_name)
  !!tenant_name.to_s.classify.safe_constantize
end

.with(tenant, &block) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 90

def self.with(tenant, &block)
  return block.call if self.current_tenant == tenant
  old_tenant = self.current_tenant
  begin
    self.current_tenant = tenant
    return block.call
  ensure
    self.current_tenant = old_tenant
  end
end

.with_lock_workaround_enabled?Boolean

Returns:

  • (Boolean)


28
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 28

def self.with_lock_workaround_enabled?; @@enable_with_lock_workaround; end

.with_write_only_mode_enabled?Boolean

Returns:

  • (Boolean)


23
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 23

def self.with_write_only_mode_enabled?; @@enable_write_only_mode ||= false; end

.without(&block) ⇒ Object



101
102
103
104
105
106
107
108
109
110
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 101

def self.without(&block)
  return block.call if self.current_tenant.nil?
  old_tenant = self.current_tenant
  begin
    self.current_tenant = nil
    return block.call
  ensure
    self.current_tenant = old_tenant
  end
end

.wrap_methods(klass, owner, *method_names) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 114

def self.wrap_methods(klass, owner, *method_names)
  method_names.each do |method_name|
    original_method_name = :"_mt_original_#{method_name}"
    klass.class_eval "alias_method :\#{original_method_name}, :\#{method_name}\ndef \#{method_name}(*args, &block)\nif MultiTenant.multi_tenant_model_for_table(\#{owner}.class.table_name).present? && \#{owner}.persisted? && MultiTenant.current_tenant_id.nil? && \#{owner}.class.respond_to?(:partition_key) && \#{owner}.attributes.include?(\#{owner}.class.partition_key)\nMultiTenant.with(\#{owner}.public_send(\#{owner}.class.partition_key)) { \#{original_method_name}(*args, &block) }\nelse\n\#{original_method_name}(*args, &block)\nend\nend\n", __FILE__, __LINE__ + 1
  end
end