Ask A Question

Notifications

You’re not receiving notifications from this thread.

Searchkick: Reindex on model in multitenancy through default scope app

Mark Radford asked in General

That's interesting with regards to scopes. I think that doesn't have any bearing on your issue though, because the Apartment gem is not using scopes to separate out your tenants, it's using postgres schemas (you're using pg right?) which makes the records in the other schemas unqueryable entirely because they aren't even visible unless you're in the global tenant. Does that sound right?

Reply

My apologies Chris, I see I've created some confusion with my original post. When I said "I followed the suggested article in the readme", I was referring to the Searchkick readme where it says "Check out this great post on the Apartment gem. Follow a similar pattern if you use another gem". I never explicitly stated that I wasn't using the Apartment gem, which I am not.

I set up multitenancy in my app following this railscast, which uses scopes.

Reply

Ohhhh! My bad! Hahaha I was assuming you were using apartment. This all makes a ton more sense now!!

Reply

Yeah, my apologies for taking so long to clear this up. Are you familiar with that implementation? Unrelated to elasticsearch but I've read:

I don't like the default_scope for the reason that it is not threadsafe. The user id is stored in a class variable, which means that two or more concurrent users in your app will break this unless you use Unicorn or some other web server that makes sure no more than one single client connection will access the same thread.
http://stackoverflow.com/a/22534147/1299792

I've responded to that comment with:

In the railscast Ryan said: "We can find another potential issue in the Tenant model where we call cattr_accessor for the current_id attribute. While this is convenient it’s not really thread-safe so we might want to do something like this instead: Thread.current[:tenant_id] = id, Now we have getter and setter methods that use Thread.current to set the value which is more thread-safe". Do you still feel using default_scope with this implementation is not thread-safe?

Reply

Hi Chris,

Unrelated to all of the multitenancy and reindexing talk, how come you didn't need to use autocomplete: true in your search query and also set the autocomplete field in your search data? I've been struggling to get my search queries to match as I wanted and it only worked when I incorporated autocomplete. I previously tried this like word_start, word_end etc with no luck.

Thanks.

Reply

Searchkick's docs for autocomplete show using this which should tell it to index those with the word_start option, so I believe that's all I did.

class Book < ActiveRecord::Base
  searchkick word_start: [:title, :author]
end

https://github.com/ankane/searchkick/#instant-search--autocomplete

You doing something different?

Reply

Yeah, for me if I search for a Product for number "pm07" then I only want that product returned, I don't want "pm01" or "pm03" returned. I was only able to get this to work by using autocomplete:true but I can't figure out why.

If we look at what's created for word_start we find:
Mapping

"product_number" : {
  "type" : "keyword",
  "fields" : {
    "analyzed" : {
      "type" : "text"
    },
    "word_start" : {
      "type" : "text",
      "analyzer" : "searchkick_word_start_index"
    }
  },
  "ignore_above" : 256
},

Analyzer

searchkick_word_start_index: {
  type: "custom",
  tokenizer: "standard",
  filter: ["lowercase", "asciifolding", "searchkick_edge_ngram"]
},

searchkick_edge_ngram filter

searchkick_edge_ngram: {
  type: "edgeNGram",
  min_gram: 1,
  max_gram: 50
},

If we look at what's created for autocomplete we find:

Mapping

"product_number" : {
  "type" : "keyword",
  "fields" : {
    "analyzed" : {
      "type" : "text"
    },
    "autocomplete" : {
      "type" : "text",
      "analyzer" : "searchkick_autocomplete_index"
    }
  },
  "ignore_above" : 256
}

Analyzer

"searchkick_autocomplete_index" : {
  "filter" : ["lowercase","asciifolding"],
  "type" : "custom",
  "tokenizer" : "searchkick_autocomplete_ngram"
},

Tokenizer

tokenizer: {
  searchkick_autocomplete_ngram: {
    type: "edgeNGram",
    min_gram: 1,
    max_gram: 50
  }
}

So I think both word_start and autcomplete use lowercase, asciifolding and edgeNGram.

The difference I think comes in the search query and the use of autocomplete: true. So with word_start we can simply use:

Product.search "pm07"

whereas with autocomplete we have:

Product.search "pm07", autocomplete: true

which I think then uses the following code:

if options[:autocomplete]
  payload = {
    multi_match: {
      fields: fields,
      query: term,
      analyzer: "searchkick_autocomplete_search"
    }
  }
searchkick_autocomplete_search: {
  type: "custom",
  tokenizer: "keyword",
  filter: ["lowercase", "asciifolding"]
},

At this point in time I can't figure out what payload code is called/used for word_start and how it differs to that used by autocomplete

Reply

I posted the comment above in an existing Searchkick issue and the author responded with:

My guess is you need to use misspellings: false. Also, to help with debugging queries and mappings, you can use the recently added:

Product.search("something", debug: true)

Reply

Awesome to have the debugging option, and it makes sense that you don't want misspellings in that situation. Search is complex. Haha

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 82,329+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more.

    © 2024 GoRails, LLC. All rights reserved.