class Chewy::Index::Witchcraft::Cauldron
Attributes
locals[R]
Public Class Methods
new(index, fields: [])
click to toggle source
@param index [Chewy::Index] index for composition @param fields [Array<Symbol>] restricts the fields for composition
# File lib/chewy/index/witchcraft.rb, line 48 def initialize(index, fields: []) @index = index @locals = [] @fields = fields end
Public Instance Methods
brew(object, crutches = nil)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 54 def brew(object, crutches = nil) alicorn.call(locals, object, crutches).as_json end
Private Instance Methods
alicorn()
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 60 def alicorn @alicorn ||= singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 -> (locals, object0, crutches) do #{composed_values(@index.root, 0)} end RUBY end
binding_variable_list(node)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 257 def binding_variable_list(node) return unless node.is_a?(Parser::AST::Node) if node.type == :send && node.children[0].nil? node.children[1] else node.children.map { |child| binding_variable_list(child) }.flatten.compact.uniq end end
composed_value(field, fetcher, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 77 def composed_value(field, fetcher, nesting) nesting = nesting.next if field.children.present? && !field.multi_field? <<-RUBY (result#{nesting} = #{fetcher} if result#{nesting}.nil? nil elsif result#{nesting}.respond_to?(:to_ary) result#{nesting}.map do |object#{nesting}| #{composed_values(field, nesting)} end else object#{nesting} = result#{nesting} #{composed_values(field, nesting)} end) RUBY else fetcher end end
composed_values(field, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 68 def composed_values(field, nesting) source = <<-RUBY non_proc_values#{nesting} = #{non_proc_values(field, nesting)} proc_values#{nesting} = #{proc_values(field, nesting)} non_proc_values#{nesting}.merge!(proc_values#{nesting}) RUBY source.gsub("\n,", ',') end
exctract_lambdas(node)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 195 def exctract_lambdas(node) return unless node.is_a?(Parser::AST::Node) if node.type == :block && node.children[0].type == :send && node.children[0].to_a == [nil, :lambda] [node.children[2]] else node.children.map { |child| exctract_lambdas(child) }.flatten.compact end end
non_proc_fields_for(parent, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 142 def non_proc_fields_for(parent, nesting) return [] unless parent fields = (parent.children || []).reject { |field| field.value.is_a?(Proc) } if nesting.zero? && @fields.present? fields.select { |f| @fields.include?(f.name) } else fields end end
non_proc_values(field, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 98 def non_proc_values(field, nesting) non_proc_fields = non_proc_fields_for(field, nesting) object = "object#{nesting}" if non_proc_fields.present? <<-RUBY (if #{object}.is_a?(Hash) { #{non_proc_fields.map do |f| key_name = f.value.is_a?(Symbol) || f.value.is_a?(String) ? f.value : f.name fetcher = "#{object}.has_key?(:#{key_name}) ? #{object}[:#{key_name}] : #{object}['#{key_name}']" "'#{f.name}'.freeze => #{composed_value(f, fetcher, nesting)}" end.join(', ')} } else { #{non_proc_fields.map do |f| method_name = f.value.is_a?(Symbol) || f.value.is_a?(String) ? f.value : f.name "'#{f.name}'.freeze => #{composed_value(f, "#{object}.#{method_name}", nesting)}" end.join(', ')} } end) RUBY else '{}' end end
proc_fields_for(parent, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 154 def proc_fields_for(parent, nesting) return [] unless parent fields = (parent.children || []).select { |field| field.value.is_a?(Proc) } if nesting.zero? && @fields.present? fields.select { |f| @fields.include?(f.name) } else fields end end
proc_values(field, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 126 def proc_values(field, nesting) proc_fields = proc_fields_for(field, nesting) if proc_fields.present? <<-RUBY { #{proc_fields.map do |f| "'#{f.name}'.freeze => (#{composed_value(f, source_for(f.value, nesting), nesting)})" end.join(', ')} } RUBY else '{}' end end
replace_local(node, variable, local_index)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 241 def replace_local(node, variable, local_index) if node.is_a?(Parser::AST::Node) if node.type == :send && node.children.to_a == [nil, variable] node.updated(nil, [ Parser::AST::Node.new(:lvar, [:locals]), :[], Parser::AST::Node.new(:int, [local_index]) ]) else node.updated(nil, node.children.map { |child| replace_local(child, variable, local_index) }) end else node end end
replace_lvar(node, old_variable, new_variable)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 205 def replace_lvar(node, old_variable, new_variable) if node.is_a?(Parser::AST::Node) if node.type == :lvar && node.children.to_a == [old_variable] node.updated(nil, [new_variable]) else node.updated(nil, node.children.map { |child| replace_lvar(child, old_variable, new_variable) }) end else node end end
replace_self(node, variable)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 229 def replace_self(node, variable) if node.is_a?(Parser::AST::Node) if node.type == :self Parser::AST::Node.new(:lvar, [variable]) else node.updated(nil, node.children.map { |child| replace_self(child, variable) }) end else node end end
replace_send(node, variable)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 217 def replace_send(node, variable) if node.is_a?(Parser::AST::Node) if node.type == :send && node.children[0].nil? node.updated(nil, [Parser::AST::Node.new(:lvar, [variable]), *node.children[1..]]) else node.updated(nil, node.children.map { |child| replace_send(child, variable) }) end else node end end
source_for(proc, nesting)
click to toggle source
# File lib/chewy/index/witchcraft.rb, line 166 def source_for(proc, nesting) ast = Parser::CurrentRuby.parse(proc.source) lambdas = exctract_lambdas(ast) raise "No lambdas found, try to reformat your code:\n`#{proc.source}`" unless lambdas source = lambdas.first proc_params = proc.parameters.map(&:second) if proc.arity.zero? source = replace_self(source, :"object#{nesting}") source = replace_send(source, :"object#{nesting}") elsif proc.arity.negative? raise "Splat arguments are unsupported by witchcraft:\n`#{proc.source}" else (nesting + 1).times do |n| source = replace_lvar(source, proc_params[n], :"object#{n}") if proc_params[n] end source = replace_lvar(source, proc_params[nesting + 1], :crutches) if proc_params[nesting + 1] binding_variable_list(source).each do |variable| locals.push(proc.binding.eval(variable.to_s)) source = replace_local(source, variable, locals.size - 1) end end Unparser.unparse(source) end