Module: SuperAuth::ActiveRecord::ByCurrentUser::ClassMethods

Defined in:
lib/super_auth/active_record/by_current_user.rb

Instance Method Summary collapse

Instance Method Details

#reset_column_informationObject

The preflight's answer is column information, so it is dropped with it.



157
158
159
160
# File 'lib/super_auth/active_record/by_current_user.rb', line 157

def reset_column_information
  @super_auth_preflight = nil
  super
end

#super_auth_effective_reachObject

The reach as this class queries it: the per-record step on its own name, since a subclass is its own resource type, then the parent steps it or its nearest declaring ancestor declared. Recomputed rather than stored because the stored map's :id names the declaring class.



98
99
100
# File 'lib/super_auth/active_record/by_current_user.rb', line 98

def super_auth_effective_reach
  { id: [name] }.merge(super_auth_parents)
end

#super_auth_explain(record_or_id) ⇒ Object

The compiled rows admitting one row for the current user, each tagged with the step it came through (:type_level, :id or the parent column), the rows the scope's subqueries match; the compiled table alone no longer answers who can see a row once a parent column takes part. Empty when nothing admits it; [{ step: :system }] under the system user, which bypasses the compiled table. The row is read unscoped, since the question is usually asked about one the user cannot see.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/super_auth/active_record/by_current_user.rb', line 109

def super_auth_explain(record_or_id)
  user = SuperAuth.current_user
  if user.blank?
    raise SuperAuth::Error, "SuperAuth.current_user not set" if SuperAuth.missing_user_behavior == :raise
    return []
  end
  return [{ step: :system }] if user.respond_to?(:system?) && user.system?

  record = unscoped.find(record_or_id.is_a?(::ActiveRecord::Base) ? record_or_id.id : record_or_id)
  held = SuperAuth::ActiveRecord::ByCurrentUser.held_by(user)
  tag = ->(step, rows) { rows.map { |row| { step: step, **row.attributes.symbolize_keys } } }

  rows = []
  rows.concat tag.(:type_level, held.where(resource_external_type: name, resource_external_id: nil)) if super_auth_wildcard
  super_auth_effective_reach.each do |column, types|
    value = record[column]
    # A NULL column is reached by nothing: "col = NULL" is never true.
    next if value.nil?
    rows.concat tag.(column, held.where(resource_external_type: types, resource_external_id: value))
  end
  rows
end

#super_auth_preflight!Object

Each parent column must exist on the table and share resource_external_id's type family. Checked on the first query rather than at declaration so a process can boot before its migrations run, and once per model, since the answer changes only with the schema. Postgres refuses a mismatched comparison; MySQL coerces it silently and admits whatever rows the cast happens to match, so the declaration is refused here, naming both sides.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/super_auth/active_record/by_current_user.rb', line 139

def super_auth_preflight!
  return if @super_auth_preflight

  expected = SuperAuth::ActiveRecord::Authorization.columns_hash.fetch("resource_external_id")
  super_auth_parents.each_key do |column|
    actual = columns_hash[column.to_s]
    unless actual
      raise SuperAuth::Error, "#{name} declares parent column #{column}, which table #{table_name} does not have"
    end
    unless SuperAuth::ActiveRecord::ByCurrentUser.type_family(actual) == SuperAuth::ActiveRecord::ByCurrentUser.type_family(expected)
      raise SuperAuth::Error, "#{name}.#{column} is #{actual.sql_type} but super_auth_authorizations.resource_external_id is #{expected.sql_type}; " \
                              "a parent column must have the type of SuperAuth.external_id_type, the type of the ids it holds"
    end
  end
  @super_auth_preflight = true
end