1

I'm using Toptal's Chewy gem to connect and query my Elasticsearch, just like an ODM.

I'm using Chewy along with Elasticsearch 6, Ruby on Rails 5.2 and Active Record.

I've defined my index just like this:

class OrdersIndex < Chewy::Index
  define_type Order.includes(:customer) do

    field :id, type: "keyword"

    field :customer do
      field :id, type: "keyword"
      field :name, type: "text"
      field :email, type: "keyword"
    end
  end
end

And my model:

class Order < ApplicationRecord
  belongs_to :customer
end

The problem here is that when I perform any query using Chewy, the customer data gets deserialized as a hash instead of an Object, and I can't use the dot notation to access the nested data.

results = OrdersIndex.query(query_string: { query: "test" })
results.first.id
# => "594d8e8b2cc640bb78bd115ae644637a1cc84dd460be6f69"

results.first.customer.name
# => NoMethodError: undefined method `name' for #<Hash:0x000000000931d928>

results.first.customer["name"]
# => "Frederique Schaefer"

How can I access the nested association using the dot notation (result.customer.name)? Or to deserialize the nested data inside an Object such as a Struct, that allows me to use the dot notation?

vinibrsl
  • 6,563
  • 4
  • 31
  • 44

2 Answers2

2

try to use

results = OrdersIndex.query(query_string: { query: "test" }).objects

It converts query result into active record Objects. so dot notation should work. If you want to load any extra association with the above result you can use .load method on Index.

If you want to convert existing ES nested object to accessible with dot notation try to reference this answer. Open Struct is best way to get things done in ruby.

Unable to use dot syntax for ruby hash

also, this one can help too

see this link if you need openStruct to work for nested object

Rahul Sharma
  • 1,393
  • 10
  • 19
0

Converting the just-deserialized results to JSON string and deserializing it again with OpenStruct as an object_class can be a bad idea and has a great CPU cost.

I've solved it differently, using recursion and the Ruby's native Struct, preserving the laziness of the Chewy gem.

def convert_to_object(keys, values)
  schema = Struct.new(*keys.map(&:to_sym))
  object = schema.new(*values)

  object.each_pair do |key, value|
    if value.is_a?(Hash)
      object.send("#{key}=", convert_to_object(value.keys, value.values))
    end
  end

  object
end

OrdersIndex.query(query_string: { query: "test" }).lazy.map do |item|
  convert_to_object(item.attributes.keys, item.attributes.values)
end

convert_to_object takes an array of keys and another one of values and creates a struct from it. Whenever the class of one of the array of values items is a Hash, then it converts to a struct, recursively, passing the hash keys and values.

To presence the laziness, that is the coolest part of Chewy, I've used Enumerator::Lazy and Enumerator#map. Mapping every value returned by the ES query into the convert_to_object function, makes every entry a complete struct.

The code is very generic and works to every index I've got.

vinibrsl
  • 6,563
  • 4
  • 31
  • 44