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

Extension to the controller to allow setting the current tenant set_current_tenant and current_tenant methods are introduced to set and get the current tenant in the controllers that uses the MultiTenant module.

Defined Under Namespace

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

Constant Summary collapse

VERSION =
'2.4.0'.freeze
@@enable_query_monitor =

rubocop:disable Style/ClassVars Option to enable query monitor

false

Class Method Summary collapse

Class Method Details

.current_tenantObject



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

def self.current_tenant
  Current.tenant
end

.current_tenant=(tenant) ⇒ Object



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

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

.current_tenant_classObject



88
89
90
91
92
93
94
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 88

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

.current_tenant_idObject



80
81
82
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 80

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

.current_tenant_is_id?Boolean

Returns:

  • (Boolean)


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

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

.default_tenant_classObject



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

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

.default_tenant_class=(tenant_class) ⇒ Object

rubocop:disable Style/ClassVars In some cases we only have an ID - if defined we’ll return the default tenant class in such cases



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

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

.enable_query_monitorObject



10
11
12
# File 'lib/activerecord-multi-tenant/query_monitor.rb', line 10

def self.enable_query_monitor
  @@enable_query_monitor = 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



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

def self.enable_write_only_mode
  @@enable_write_only_mode = true
end

.load_current_tenant!Object



96
97
98
99
100
101
102
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 96

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 || raise('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



62
63
64
65
66
67
68
69
70
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 62

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(TableNode.table_name(arel.ast.relation.left))
  else
    MultiTenant.multi_tenant_model_for_table(TableNode.table_name(arel.ast.relation))
  end
end

.multi_tenant_model_for_table(table_name) ⇒ Object



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

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

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

  @@multi_tenant_model_table_names[table_name.to_s]
  # rubocop:enable Style/ClassVars
end

.partition_key(tenant_name) ⇒ Object



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

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

.query_monitor_enabled?Boolean

Returns:

  • (Boolean)


14
15
16
# File 'lib/activerecord-multi-tenant/query_monitor.rb', line 14

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)



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

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, options = {}) ⇒ Boolean

Returns:

  • (Boolean)


8
9
10
11
12
13
14
15
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 8

def self.tenant_klass_defined?(tenant_name, options = {})
  class_name = if options[:class_name].present?
                 options[:class_name]
               else
                 tenant_name.to_s.classify
               end
  !!class_name.safe_constantize
end

.with(tenant, &block) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 104

def self.with(tenant, &block)
  return block.call if current_tenant == tenant

  old_tenant = current_tenant
  begin
    self.current_tenant = tenant
    block.call
  ensure
    self.current_tenant = old_tenant
  end
end

.with_write_only_mode_enabled?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 37

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

.without(&block) ⇒ Object



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

def self.without(&block)
  return block.call if current_tenant.nil?

  old_tenant = current_tenant
  begin
    self.current_tenant = nil
    block.call
  ensure
    self.current_tenant = old_tenant
  end
end

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



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/activerecord-multi-tenant/multi_tenant.rb', line 134

def self.wrap_methods(klass, owner, *method_names)
  method_names.each do |method_name|
    original_method_name = :"_mt_original_#{method_name}"
    klass.class_eval <<-CODE, __FILE__, __LINE__ + 1
      alias_method :#{original_method_name}, :#{method_name}
      def #{method_name}(*args, &block)
        if 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)
          MultiTenant.with(#{owner}.public_send(#{owner}.class.partition_key)) { #{original_method_name}(*args, &block) }
        else
          #{original_method_name}(*args, &block)
        end
      end
    CODE
  end
end