module Protector module Adapters module ActiveRecord # Patches `ActiveRecord::Relation` module Relation extend ActiveSupport::Concern included do include Protector::DSL::Base alias_method_chain :exec_queries, :protector alias_method_chain :new, :protector # AR 3.2 workaround. Come on, guys... SQL parsing :( unless method_defined?(:references_values) def references_values tables_in_string(to_sql) end end unless method_defined?(:includes!) def includes!(*args) self.includes_values += args self end end end # Gets {Protector::DSL::Meta::Box} of this relation def protector_meta(subject=protector_subject) @klass.protector_meta.evaluate(subject) end # @note Unscoped relation drops properties and therefore should be re-restricted def unscoped return super unless protector_subject? super.restrict!(protector_subject) end def except(*args) return super unless protector_subject? super.restrict!(protector_subject) end def only(*args) return super unless protector_subject? super.restrict!(protector_subject) end # @note This is here cause `NullRelation` can return `nil` from `count` def count(*args) super || 0 end # @note This is here cause `NullRelation` can return `nil` from `sum` def sum(*args) super || 0 end # Merges current relation with restriction and calls real `calculate` def calculate(*args) return super unless protector_subject? merge(protector_meta.relation).unrestrict!.calculate *args end # Merges current relation with restriction and calls real `exists?` def exists?(*args) return super unless protector_subject? merge(protector_meta.relation).unrestrict!.exists? *args end # Forwards protection subject to the new instance def new_with_protector(*args, &block) return new_without_protector(*args, &block) unless protector_subject? # strong_parameters integration if Protector.config.strong_parameters? && args.first.respond_to?(:permit) Protector::ActiveRecord::StrongParameters::sanitize! args, true, protector_meta end new_without_protector(*args, &block).restrict!(protector_subject) end # Patches current relation to fulfill restriction and call real `exec_queries` # # Patching includes: # # * turning `includes` (that are not referenced for eager loading) into `preload` # * delaying built-in preloading to the stage where selection is restricted # * merging current relation with restriction (of self and every eager association) def exec_queries_with_protector(*args) return @records if loaded? return exec_queries_without_protector unless protector_subject? subject = protector_subject relation = merge(protector_meta.relation).unrestrict! relation = protector_substitute_includes(subject, relation) # Preserve associations from internal loading. We are going to handle that # ourselves respecting security scopes FTW! associations, relation.preload_values = relation.preload_values, [] @records = relation.send(:exec_queries).each{|record| record.restrict!(subject)} # Now we have @records restricted properly so let's preload associations! associations.each do |association| ::ActiveRecord::Associations::Preloader.new(@records, association).run end @loaded = true @records end # Swaps `includes` with `preload` if it's not referenced or merges # security scope of proper class otherwise def protector_substitute_includes(subject, relation) if eager_loading? protector_expand_inclusion(includes_values + eager_load_values).each do |klass, path| # AR drops default_scope for eagerly loadable associations # https://github.com/inossidabile/protector/issues/3 # and so should we meta = klass.protector_meta.evaluate(subject) if meta.scoped? unscoped = klass.unscoped # `unscoped` gets us a relation but Protector scope is supposed # to work with AR::Base. Some versions of AR have those uncompatible # so we have to workaround it :( unscoped.protector_mimic_base! # Finally we merge unscoped basic relation extended with protection scope relation = relation.merge meta.eval_scope_procs(unscoped) end end else relation.preload_values += includes_values relation.includes_values = [] end relation end # Makes instance of Relation duck-type compatible to AR::Base to allow proper # protection block execution with itself def protector_mimic_base! return unless Protector::Adapters::ActiveRecord.modern? class < a } } ] end end results << [model, base.inject(key){|a, n| { n => a } }] end end end end end end