lib/heimdallr/proxy/collection.rb in heimdallr-1.0.3 vs lib/heimdallr/proxy/collection.rb in heimdallr-1.0.4
- old
+ new
@@ -17,10 +17,11 @@
# @option options [Boolean] implicit proxy type
def initialize(context, scope, options={})
@context, @scope, @options = context, scope, options
@restrictions = @scope.restrictions(context)
+ @options[:eager_loaded] ||= {}
end
# Collections cannot be restricted with different context or options.
#
# @return self
@@ -38,11 +39,11 @@
# A proxy for +$1+ method which adds fixtures to the attribute list and
# returns a restricted record.
def self.delegate_as_constructor(name, method)
class_eval(<<-EOM, __FILE__, __LINE__)
def #{name}(attributes={})
- record = @restrictions.request_scope(:fetch).new.restrict(@context, @options)
+ record = @restrictions.request_scope(:fetch).new.restrict(@context, options_with_escape)
record.#{method}(attributes.merge(@restrictions.fixtures[:create]))
record
end
EOM
end
@@ -51,11 +52,11 @@
# @macro [attach] delegate_as_scope
# A proxy for +$1+ method which returns a restricted scope.
def self.delegate_as_scope(name)
class_eval(<<-EOM, __FILE__, __LINE__)
def #{name}(*args)
- Proxy::Collection.new(@context, @scope.#{name}(*args), @options)
+ Proxy::Collection.new(@context, @scope.#{name}(*args), options_with_escape)
end
EOM
end
# @private
@@ -73,11 +74,11 @@
# @macro [attach] delegate_as_record
# A proxy for +$1+ method which returns a restricted record.
def self.delegate_as_record(name)
class_eval(<<-EOM, __FILE__, __LINE__)
def #{name}(*args)
- @scope.#{name}(*args).restrict(@context, @options)
+ @scope.#{name}(*args).restrict(@context, options_with_eager_load)
end
EOM
end
# @private
@@ -85,11 +86,11 @@
# A proxy for +$1+ method which returns an array of restricted records.
def self.delegate_as_records(name)
class_eval(<<-EOM, __FILE__, __LINE__)
def #{name}(*args)
@scope.#{name}(*args).map do |element|
- element.restrict(@context, @options)
+ element.restrict(@context, options_with_eager_load)
end
end
EOM
end
@@ -111,13 +112,10 @@
delegate_as_scope :scoped
delegate_as_scope :uniq
delegate_as_scope :where
delegate_as_scope :joins
- delegate_as_scope :includes
- delegate_as_scope :eager_load
- delegate_as_scope :preload
delegate_as_scope :lock
delegate_as_scope :limit
delegate_as_scope :offset
delegate_as_scope :order
delegate_as_scope :reorder
@@ -152,45 +150,96 @@
delegate_as_records :all
delegate_as_records :to_a
delegate_as_records :to_ary
+ # A proxy for +includes+ which adds Heimdallr conditions for eager loaded
+ # associations.
+ def includes(*associations)
+ # Normalize association list to strict nested hash.
+ normalize = ->(list) {
+ if list.is_a? Array
+ list.map(&normalize).reduce(:merge)
+ elsif list.is_a? Symbol
+ { list => {} }
+ elsif list.is_a? Hash
+ hash = {}
+ list.each do |key, value|
+ hash[key] = normalize.(value)
+ end
+ hash
+ end
+ }
+ associations = normalize.(associations)
+
+ current_scope = @scope.includes(associations)
+
+ add_conditions = ->(associations, scope) {
+ associations.each do |association, nested|
+ reflection = scope.reflect_on_association(association)
+ if reflection && !reflection.options[:polymorphic]
+ associated_klass = reflection.klass
+
+ if associated_klass.respond_to? :restrict
+ nested_scope = associated_klass.restrictions(@context).request_scope(:fetch)
+
+ where_values = nested_scope.where_values
+ if where_values.any?
+ current_scope = current_scope.where(*where_values)
+ end
+
+ add_conditions.(nested, associated_klass)
+ end
+ end
+ end
+ }
+
+ unless Heimdallr.skip_eager_condition_injection
+ add_conditions.(associations, current_scope)
+ end
+
+ options = @options.merge(eager_loaded:
+ @options[:eager_loaded].merge(associations))
+
+ Proxy::Collection.new(@context, current_scope, options)
+ end
+
# A proxy for +find+ which restricts the returned record or records.
#
# @return [Proxy::Record, Array<Proxy::Record>]
def find(*args)
result = @scope.find(*args)
if result.is_a? Enumerable
result.map do |element|
- element.restrict(@context, @options)
+ element.restrict(@context, options_with_eager_load)
end
else
- result.restrict(@context, @options)
+ result.restrict(@context, options_with_eager_load)
end
end
# A proxy for +each+ which restricts the yielded records.
#
# @yield [record]
# @yieldparam [Proxy::Record] record
def each
@scope.each do |record|
- yield record.restrict(@context, @options)
+ yield record.restrict(@context, options_with_eager_load)
end
end
# Wraps a scope or a record in a corresponding proxy.
def method_missing(method, *args)
if method =~ /^find_all_by/
@scope.send(method, *args).map do |element|
- element.restrict(@context, @options)
+ element.restrict(@context, options_with_escape)
end
elsif method =~ /^find_by/
- @scope.send(method, *args).restrict(@context, @options)
+ @scope.send(method, *args).restrict(@context, options_with_escape)
elsif @scope.heimdallr_scopes && @scope.heimdallr_scopes.include?(method)
- Proxy::Collection.new(@context, @scope.send(method, *args), @options)
+ Proxy::Collection.new(@context, @scope.send(method, *args), options_with_escape)
elsif @scope.respond_to? method
raise InsecureOperationError,
"Potentially insecure method #{method} was called"
else
super
@@ -229,8 +278,21 @@
}.merge(@restrictions.reflection)
end
def creatable?
@restrictions.can? :create
+ end
+
+ private
+
+ # Return options hash to pass to children proxies.
+ # Currently this checks only eagerly loaded collections, which
+ # shouldn't be passed around blindly.
+ def options_with_escape
+ @options.reject { |k,v| k == :eager_loaded }
+ end
+
+ def options_with_eager_load
+ @options
end
end
end
\ No newline at end of file