Module: Sublet::Core
- Defined in:
- lib/sublet/core.rb
Class Method Summary collapse
-
.build_nested_joins(joins_path) ⇒ Object
Build nested joins hash from an array of association names [:account, :company] => { account: :company } [:department] => :department.
-
.extract_tenant_id(record) ⇒ Object
Extract tenant id from a record instance by walking to the last hop and checking a FK or assoc.
-
.scoped_relation_for(model, relation = model.all) ⇒ Object
Build a scoped relation that limits
relation(formodel) to the current tenant, if present.
Class Method Details
.build_nested_joins(joins_path) ⇒ Object
Build nested joins hash from an array of association names [:account, :company] => { account: :company } [:department] => :department
86 87 88 89 90 |
# File 'lib/sublet/core.rb', line 86 def build_nested_joins(joins_path) return nil if joins_path.nil? || joins_path.empty? joins_path.reverse.reduce(nil) { |acc, name| acc ? { name => acc } : name } end |
.extract_tenant_id(record) ⇒ Object
Extract tenant id from a record instance by walking to the last hop and checking a FK or assoc.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/sublet/core.rb', line 93 def extract_tenant_id(record) tenant = Sublet.current_tenant return nil unless tenant path = Sublet::PathResolver.resolve_path_to_tenant(record.class) return nil unless path obj = record path.each do |assoc| return nil unless obj.respond_to?(assoc) obj = obj.public_send(assoc) return nil if obj.nil? end if (bt = obj.class.reflect_on_all_associations(:belongs_to).find { |r| r.klass == tenant.class }) return obj.public_send(bt.foreign_key) end assoc_name_guess = tenant.class.name.demodulize.underscore if obj.respond_to?(assoc_name_guess) t = obj.public_send(assoc_name_guess) return t&.id end nil end |
.scoped_relation_for(model, relation = model.all) ⇒ Object
Build a scoped relation that limits relation (for model) to the current tenant, if present.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/sublet/core.rb', line 8 def scoped_relation_for(model, relation = model.all) tenant = Sublet.current_tenant return relation unless tenant path = Sublet::PathResolver.resolve_path_to_tenant(model) return relation unless path && !path.empty? # fail-open if no path or tenant root not found # Build deterministic INNER JOIN SQL across the whole chain to avoid AR 8 alias tracker issues rel = relation current_klass = model current_table = current_klass.table_name join_sql_parts = [] path.each do |assoc| reflection = current_klass.reflect_on_association(assoc) return relation unless reflection next_klass = reflection.klass next_table = next_klass.table_name case reflection.macro when :belongs_to # current_table.fk = next_table.pk fk = reflection.foreign_key pk = reflection.association_primary_key join_sql_parts << "INNER JOIN \"#{next_table}\" ON \"#{current_table}\".\"#{fk}\" = " \ "\"#{next_table}\".\"#{pk}\"" current_klass = next_klass current_table = next_table when :has_one, :has_many # next_table.fk = current_table.pk fk = reflection.foreign_key pk = reflection.active_record_primary_key join_sql_parts << "INNER JOIN \"#{next_table}\" ON \"#{next_table}\".\"#{fk}\" = " \ "\"#{current_table}\".\"#{pk}\"" current_klass = next_klass current_table = next_table else # unsupported macro; fail open return relation end end rel = rel.joins(join_sql_parts.join(' ')) if join_sql_parts.any? # Walk the path to discover the last klass we joined to last_klass = model path.each do |assoc| reflection = last_klass.reflect_on_association(assoc) return relation unless reflection last_klass = reflection.klass end tenant_table = tenant.class.table_name last_table = last_klass.table_name # Case 1: the last hop IS the tenant class (e.g., ... -> Company) return rel.where(tenant_table => { id: tenant.id }).distinct if last_klass == tenant.class # Case 2: the last hop has a direct belongs_to to the tenant class if (bt = last_klass.reflect_on_all_associations(:belongs_to).find { |r| r.klass == tenant.class }) return rel.where(last_table => { bt.foreign_key => tenant.id }).distinct end # Case 3: try a conventional association name matching the tenant class (e.g., :company) assoc_name_guess = tenant.class.name.demodulize.underscore.to_sym if last_klass.reflect_on_association(assoc_name_guess) return rel.joins(assoc_name_guess).where(tenant_table => { id: tenant.id }).distinct end # Could not determine how to constrain; fail open relation end |