module Recurso module Queries class Relation def initialize(identity, resource, relation_name, all_columns: true, action: :view) @identity = identity @resource = resource @relation = resource.send(relation_name) @all_columns = all_columns @action = action end def resources @resources ||= join_permissions .select("#{@relation.table_name}.#{@all_columns ? :* : :id}") .where(coalesce(@action)) .distinct end private def join_permissions @relation.relevant_associations.reduce(@relation) do |result, assoc| result.joins(through_join_for(assoc)).joins(" LEFT OUTER JOIN #{permission_class.table_name} #{assoc.name}_permissions ON #{assoc.name}_permissions.resource_type = '#{assoc.class_name}' AND #{assoc.name}_permissions.resource_id = #{resource_id_for(assoc)} AND #{assoc.name}_permissions.#{identity_foreign_key} = #{@identity.id.to_i} ") end end def resource_id_for(assoc) "#{(assoc.through_reflection || assoc.active_record).table_name}.#{assoc.foreign_key}" end def through_join_for(assoc) return unless through = assoc.through_reflection " LEFT OUTER JOIN #{through.table_name} ON #{through.table_name}.#{through.association_primary_key} = #{resource_id_for(through)} " end def coalesce(action) "coalesce(#{level_columns}) IN (#{level_values(action)})" end def level_columns @relation.relevant_associations.map { |assoc| "#{assoc.name}_permissions.level" }.join(',') end def level_values(action) @resource.relevant_levels_for(action).map { |level| permission_class.levels[level] }.join(',') end def permission_class @permission_class ||= Recurso::Config.instance.permission_class_name_for(@identity.class).constantize end def identity_foreign_key @identity_foreign_key ||= Recurso::Config.instance.identity_foreign_key_for(@identity.class) end end end end