Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### New Features

* [#996](https://github.com/toptal/chewy/pull/996): Add `context:` option to `import`/`import!` for passing custom data to crutch blocks and field value procs without redundant DB queries.

### Changes
* [#977](https://github.com/toptal/chewy/pull/977): Fewer files on gem installation [@ericproulx](https://github.com/ericproulx).

Expand Down
6 changes: 3 additions & 3 deletions lib/chewy/fields/root.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def compose_id(object)
# @param fields [Array<Symbol>] a list of fields to compose, every field will be composed if empty
# @return [Hash] JSON-ready hash with stringified keys
#
def compose(object, crutches = nil, fields: [])
result = evaluate([object, crutches])
def compose(object, crutches = nil, fields: [], context: {})
result = evaluate([object, crutches, context])

if children.present?
child_fields = if fields.present?
Expand All @@ -72,7 +72,7 @@ def compose(object, crutches = nil, fields: [])
end

child_fields.each_with_object({}) do |field, memo|
memo.merge!(field.compose(result, crutches) || {})
memo.merge!(field.compose(result, crutches, context) || {})
end.as_json
elsif fields.present?
result.as_json(only: fields, root: false)
Expand Down
14 changes: 12 additions & 2 deletions lib/chewy/index/crutch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ module Crutch
end

class Crutches
def initialize(index, collection)
attr_reader :context

def initialize(index, collection, context = {})
@index = index
@collection = collection
@context = context
@crutches_instances = {}
end

Expand All @@ -26,7 +29,14 @@ def respond_to_missing?(name, include_private = false)
end

def [](name)
@crutches_instances[name] ||= @index._crutches[:"#{name}"].call(@collection)
@crutches_instances[name] ||= begin
block = @index._crutches[:"#{name}"]
if block.arity > 1 || block.arity < -1
block.call(@collection, @context)
else
block.call(@collection)
end
end
end
end

Expand Down
8 changes: 4 additions & 4 deletions lib/chewy/index/import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ def bulk(**options)
# @param crutches [Object] optional crutches object; if omitted - a crutch for the single passed object is created as a fallback
# @param fields [Array<Symbol>] and array of fields to restrict the generated document
# @return [Hash] a JSON-ready hash
def compose(object, crutches = nil, fields: [])
crutches ||= Chewy::Index::Crutch::Crutches.new self, [object]
def compose(object, crutches = nil, fields: [], context: {})
crutches ||= Chewy::Index::Crutch::Crutches.new self, [object], context

if witchcraft? && root.children.present?
cauldron(fields: fields).brew(object, crutches)
cauldron(fields: fields).brew(object, crutches, context)
else
root.compose(object, crutches, fields: fields)
root.compose(object, crutches, fields: fields, context: context)
end
end

Expand Down
7 changes: 4 additions & 3 deletions lib/chewy/index/import/bulk_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ class BulkBuilder
# @param to_index [Array<Object>] objects to index
# @param delete [Array<Object>] objects or ids to delete
# @param fields [Array<Symbol, String>] and array of fields for documents update
def initialize(index, to_index: [], delete: [], fields: [])
def initialize(index, to_index: [], delete: [], fields: [], context: {})
@index = index
@to_index = to_index
@delete = delete
@fields = fields.map!(&:to_sym)
@context = context
end

# Returns ES API-ready bulk requiest body.
Expand All @@ -42,7 +43,7 @@ def index_objects_by_id
private

def crutches_for_index
@crutches_for_index ||= Chewy::Index::Crutch::Crutches.new @index, @to_index
@crutches_for_index ||= Chewy::Index::Crutch::Crutches.new @index, @to_index, @context
end

def index_entry(object)
Expand Down Expand Up @@ -257,7 +258,7 @@ def join_field?
end

def data_for(object, fields: [], crutches: crutches_for_index)
@index.compose(object, crutches, fields: fields)
@index.compose(object, crutches, fields: fields, context: @context)
end

def parent_changed?(data, old_parent)
Expand Down
3 changes: 2 additions & 1 deletion lib/chewy/index/import/routine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def initialize(index, **options)
{}
end
end
@context = @options.delete(:context) || {}
@errors = []
@stats = {}
@leftovers = []
Expand All @@ -78,7 +79,7 @@ def create_indexes!
# @param delete [Array<Object>] any acceptable objects for deleting
# @return [true, false] the result of the request, true if no errors
def process(index: [], delete: [])
bulk_builder = BulkBuilder.new(@index, to_index: index, delete: delete, fields: @options[:update_fields])
bulk_builder = BulkBuilder.new(@index, to_index: index, delete: delete, fields: @options[:update_fields], context: @context)
bulk_body = bulk_builder.bulk_body

if @options[:journal]
Expand Down
7 changes: 4 additions & 3 deletions lib/chewy/index/witchcraft.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ def initialize(index, fields: [])
@fields = fields
end

def brew(object, crutches = nil)
alicorn.call(locals, object, crutches).as_json
def brew(object, crutches = nil, context = {})
alicorn.call(locals, object, crutches, context).as_json
end

private

def alicorn
@alicorn ||= singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
-> (locals, object0, crutches) do
-> (locals, object0, crutches, context) do
#{composed_values(@index.root, 0)}
end
RUBY
Expand Down Expand Up @@ -182,6 +182,7 @@ def source_for(proc, nesting)
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]
source = replace_lvar(source, proc_params[nesting + 2], :context) if proc_params[nesting + 2]

binding_variable_list(source).each do |variable|
locals.push(proc.binding.eval(variable.to_s))
Expand Down
84 changes: 84 additions & 0 deletions spec/chewy/index/import/bulk_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,90 @@ def derived
end
end

context 'context' do
before do
stub_index(:cities) do
crutch :names do |collection, context|
context[:names] || collection.to_h { |item| [item.id, "Name#{item.id}"] }
end

field :name, value: ->(o, c) { c.names[o.id] }
end
end

let(:to_index) { [double(id: 42)] }

context 'without context' do
specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => 'Name42'}}}
])
end
end

context 'with context' do
subject { described_class.new(index, to_index: to_index, delete: delete, fields: fields, context: {names: {42 => 'ContextName42'}}) }

specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => 'ContextName42'}}}
])
end
end

context 'with context passed to field value' do
before do
stub_index(:cities) do
crutch :names do |collection|
collection.to_h { |item| [item.id, "Name#{item.id}"] }
end

field :name, value: ->(o, crutches, context) { context[:prefix].to_s + crutches.names[o.id] }
end
end

context 'without context' do
specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => 'Name42'}}}
])
end
end

context 'with context' do
subject { described_class.new(index, to_index: to_index, delete: delete, fields: fields, context: {prefix: '[ctx] '}) }

specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => '[ctx] Name42'}}}
])
end
end
end

context 'witchcraft' do
before { CitiesIndex.witchcraft! }

context 'without context' do
specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => 'Name42'}}}
])
end
end

context 'with context' do
subject { described_class.new(index, to_index: to_index, delete: delete, fields: fields, context: {names: {42 => 'ContextName42'}}) }

specify do
expect(subject.bulk_body).to eq([
{index: {_id: 42, data: {'name' => 'ContextName42'}}}
])
end
end
end
end

context 'empty ids' do
before do
stub_index(:cities) do
Expand Down
13 changes: 13 additions & 0 deletions spec/chewy/index/witchcraft_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ def self.mapping(&block)
end
end

context 'context' do
mapping do
field :name, value: ->(_o, _c, ctx) { ctx[:name] }
end

context do
let(:object) { double(name: 'Name') }
let(:crutches) { double }
let(:context) { {name: 'FromContext'} }
specify { expect(index.cauldron.brew(object, crutches, context)).to eq({name: 'FromContext'}.as_json) }
end
end

context 'nesting' do
context do
mapping do
Expand Down