Skip to content

Instantly share code, notes, and snippets.

@sidot3291
Last active November 13, 2015 01:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sidot3291/77b999ffeaa5d1d290b2 to your computer and use it in GitHub Desktop.
Save sidot3291/77b999ffeaa5d1d290b2 to your computer and use it in GitHub Desktop.
Lambda Scopes
# Note: the lambda function's primary utility is scoping. We allow joins()
# in the lambda because it makes for readable nesting.
# Our "machine readable" tree takes this:
# joins({:assoc => -> { unscoped.joins(:nested_assoc) } })
# And effectively converts it to this:
# joins({:assoc => {:nested_assoc=>{}, __scope__ => ->{ unscoped } })
module ActiveRecord
class Relation
class HashMerger
def merge_joins
return if values[:joins].blank?
if other.klass == relation.klass
relation.joins!(*values[:joins])
else
joins_dependency, rest = values[:joins].partition do |join|
case join
when Hash, Symbol, Array
true
else
false
end
end
join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
joins_dependency,
[],
:joins)
relation.joins! rest
@relation = relation.joins join_dependency
end
end
end
end
end
module ActiveRecord
module FinderMethods
def construct_join_dependency(joins = [])
including = eager_load_values + includes_values
puts "using eager load_type"
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, :eager_load)
end
end
end
module ActiveRecord
module QueryMethods
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
when String
:string_join
when Hash, Symbol, Array
:association_join
when ActiveRecord::Associations::JoinDependency
:stashed_join
when Arel::Nodes::Join
:join_node
else
raise 'unknown class: %s' % join.class.name
end
end
association_joins = buckets[:association_join] || []
stashed_association_joins = buckets[:stashed_join] || []
join_nodes = (buckets[:join_node] || []).uniq
string_joins = (buckets[:string_join] || []).map(&:strip).uniq
join_list = join_nodes + custom_join_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
association_joins,
join_list,
:joins
)
joins = join_dependency.join_constraints stashed_association_joins
joins.each { |join| manager.from(join) }
manager.join_sources.concat(join_list)
manager
end
def collapse_wheres(arel, wheres)
predicates = wheres.map do |where|
next where if ::Arel::Nodes::Equality === where
next where if ::Arel::Nodes::NotEqual === where
where = Arel.sql(where) if String === where
Arel::Nodes::Grouping.new(where)
end
arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
end
end
end
module ActiveRecord
module Associations
class JoinDependency
class JoinDummy
attr_accessor :klass
def initialize(klass, table)
self.klass = klass
klass.columns_hash.each do |col_name, data|
define_singleton_method(col_name.to_sym) do
table[__method__]
end
end
end
end
def initialize(base, associations, joins, load_type)
@alias_tracker = AliasTracker.create(base.connection, joins)
@alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base, load_type)
@join_root.children.each { |child| construct_tables! @join_root, child }
end
#updated so the normalized tree's right hand side can
#either be an empty hash (old) OR a scoping lambda (new)
def self.walk_tree(associations, hash)
case associations
when Symbol, String
hash[associations.to_sym] ||= {}
when Array
associations.each do |assoc|
walk_tree assoc, hash
end
when Hash
associations.each do |k,v|
if v.is_a? Proc
hash[k] = v
else
cache = hash[k] ||= {}
walk_tree v, cache
end
end
else
raise ConfigurationError, associations.inspect
end
end
def build(associations, base_klass, load_type)
associations.map do |name, right|
reflection = find_reflection base_klass, name
reflection.check_validity!
if reflection.polymorphic?
raise EagerLoadPolymorphicError.new(reflection)
end
scoped_relation = reflection.klass.default_scoped
if right.is_a? Proc
scoped_relation = scoped_relation.instance_exec(&right)
additional_scope = right
else
scoped_relation = scoped_relation.send(load_type, right)
additional_scope = nil
end
scoped_relation_loads = scoped_relation.send(load_type.to_s+"_values")
if load_type == :eager_load
scoped_relation_loads.concat(scoped_relation.includes_values)
end
tree = self.class.make_tree scoped_relation_loads
children = build(tree, reflection.klass, load_type)
JoinAssociation.new reflection, additional_scope, children
end
end
end
end
end
require 'active_record/associations/join_dependency/join_part'
module ActiveRecord
module Associations
class JoinDependency
class JoinAssociation < JoinPart
attr_reader :additional_scope
def initialize(reflection, additional_scope, children)
super(reflection.klass, children)
@reflection = reflection
@additional_scope = additional_scope
@tables = nil
end
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
tables = tables.reverse
scope_chain_index = 0
scope_chain = scope_chain.reverse
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
chain.reverse_each do |reflection|
table = tables.shift
klass = reflection.klass
case reflection.source_macro
when :belongs_to
key = reflection.association_primary_key
foreign_key = reflection.foreign_key
else
key = reflection.foreign_key
foreign_key = reflection.active_record_primary_key
end
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
rel = klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))
if rel.nil?
rel = ActiveRecord::Relation.create(klass, table)
end
scope_chain[scope_chain_index].each do |item|
if item.is_a?(Relation)
rel = rel.where(item.arel.constraints)
else
dummy = JoinDummy.new(foreign_klass, foreign_table)
rel = rel.instance_exec(dummy, &item)
end
end
if !additional_scope.nil? and scope_chain_index==(scope_chain.length-1)
rel = rel.instance_exec(&additional_scope)
end
scope_chain_index += 1
if reflection.type
constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
end
if rel && !rel.arel.constraints.empty?
constraint = constraint.and rel.arel.constraints
end
joins << table.create_join(table, table.create_on(constraint), join_type)
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
end
joins
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment