mongodbruby-on-rails-4elasticsearchtire

*** NoMethodError Exception: undefined method `_id'


I am working on a RubyOnRails application. I am using Elasticsearch via Tire gem. Elasticsearch indexed a 3 models. these models are News, NewsTag and Categorization. News has a many-to-many relation with NewsTag, and NewsTag has a many-to-many relation with categorization. All of these models are mongodb.

mapping the index:-

    mapping do
        indexes :cover, analyzer: 'snowball'
        indexes :title_en
        indexes :title_ar
        indexes :content_brief_ar
        indexes :content_brief_en
        indexes :body_ar
        indexes :body_en
        indexes :likes, type: 'nested' do
        end
        indexes :comments, type: 'nested' do
        end

        indexes :news_tags, type: 'nested' do
          indexes :name_en, analyzer: 'snowball'
          indexes :name_ar, analyzer: 'snowball'
          indexes :categorizations, type: 'nested' do
            indexes :ar_name, analyzer: 'snowball'
            indexes :en_name, analyzer: 'snowball'
          end
        end
      end
      def to_indexed_json
    {
      cover: self.cover,
      title_ar: self.title_ar,
      title_en: self.title_en,
      content_brief_ar: self.content_brief_ar,
      content_brief_en: self.content_brief_en,
      body_ar: self.body_ar,
      body_en: self.body_en,
      news_tags: self.news_tags,
      likes: self.likes,
      comments: self.comments
    }.to_json
  end

How I searched

@news = News.tire.search do
        query do
          nested path: 'news_tags' do
            query do
              boolean do
                  should {terms 'news_tags.categorization_ids', categorization_ids }
              end
            end
          end
        end
        page = page_param.to_i
        search_size = per_page_param.to_i
        from (page - 1) * search_size
        size search_size
      end

That was working just fine till that I realized that the type of the retrieved result is not News, it is Item and there are some methods defined in the model (News) which are frequently called. So I searched for wrapping the Item to be News and I found something interesting.

add this line to config/intializers/tire.rb

Tire.configure do    
   wrapper ProxyObject
end

and this file app/models/proxy_object.rb

class ProxyObject < SimpleDelegator
  delegate :class, :is_a?, :to => :_proxied_object

  def initialize(attrs={})
    klass = attrs['_type'].camelize.classify.constantize
    @_proxied_object = klass.new
    _assign_attrs(attrs)
    super(_proxied_object)
  end

  private

  def _proxied_object
    @_proxied_object
  end

  def _assign_attrs(attrs={})
     attrs.each_pair do |key, value|
       unless _proxied_object.respond_to?("#{key}=".to_sym)
         _proxied_object.class.send(:attr_accessor, key.to_sym)
       end
       _proxied_object.send("#{key}=".to_sym, value)
     end
  end
end

This solution was very successful with a tutorial project on Elasticsearch. this solution has enabled me to call model methods. However, it doesn't work for my project. I noticed something which could be important. In the news index.

GET localhost:9200/news/news/58889bbb6f6d613fff050000

{
  "_index": "news",
  "_type": "news",
  "_id": "58889bbb6f6d613fff050000",
  "_version": 1,
  "found": true,
  "_source": {
    "cover": "new2",
    "title_ar": "new2",
    "title_en": "new2",
    "content_brief_ar": "new2",
    "content_brief_en": "new2",
    "body_ar": "<p>new2</p>\r\n",
    "body_en": "<p>new2</p>\r\n",
    "news_tags": [
      {
        "_id": "56dec06769702d1578000000",
        "categorization_ids": [
          "5888990f6f6d613fff000000"
        ],
        "created_at": "2016-03-08T14:07:03+02:00",
        "name_ar": "صحة الطفل",
        "name_en": "Baby Health",
        "news_ids": [
          "56dec03569702d156a010000",
          "5704f92769702d4c2b010000",
          "574efc7969702d370a130000",
          "578515d369702d4f11000000",
          "58889bbb6f6d613fff050000",
          "58889c3f6f6d613fff080000"
        ],
        "updated_at": "2016-03-08T14:07:03+02:00"
      }
    ],
    "likes": [],
    "comments": []
  }
}

As you notice, news_ids are embedded in news_tags, and news_tags is embedded in news. So, I feel that there are some sort of recursion.

My question is, when I apply any method on @news object, even count, retrieved from search I got the error message

(byebug) @news.count
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 1.9747ms
  MOPED: 127.0.0.1:27017 UPDATE       database=nabda-net_staging collection=news_tags selector={"$and"=>[{"_id"=>{"$in"=>[]}}]} update={"$pull"=>{"news_ids"=>BSON::ObjectId('5888aaba6f6d6154d0000000')}} flags=[:multi]
                         COMMAND      database=nabda-net_staging command={:getlasterror=>1, :w=>1} runtime: 0.6387ms
*** NoMethodError Exception: undefined method `_id' for #<Hash:0x00000004b78190>

Why am I get this error message? It is not even descriptive. I do not know where is the problem. So, can any one help me.


Solution

  • I found the solution. Adding load: true in the search query will fix the issue.

    @news = News.tire.search load: true do
            query do
              nested path: 'news_tags' do
                query do
                  boolean do
                      should {terms 'news_tags.categorization_ids', categorization_ids }
                  end
                end
              end
            end
            page = page_param.to_i
            search_size = per_page_param.to_i
            from (page - 1) * search_size
            size search_size
          end
    

    Thank you all.