Searchkick: How to get ids of results items?

Created on 20 Jun 2015  路  8Comments  路  Source: ankane/searchkick

Because this approach smells bad and I have to do two queries to the database.

ids = Model.search(where: where_params)
  .results.response['hits']['hits'].map{ |x| x['_id'].to_i }

Is there proper way for doing this?

By the way, I need items ids for "converting" Searchkick::Results class to ActiveRecord::Relation to use scopes and other class methods on it. Is there turnkey solution for these needs?

...

def self.filter(params)
  where_params = Search.attrs(params: params)

  if where_params.any?
    to_ar_relation(search(where: where_params))
  else
    where(nil)
  end
end

private

# converts searchkick results to ActiveRecord relation to use scopes on it
def self.to_ar_relation(results)
  where(id: results.response['hits']['hits'].map{ |x| x['_id'].to_i })
end

...

Most helpful comment

Perhaps #490 helps deal with this? You should be able to do:

Model.search(where: where_params).records

Which gives back an ActiveRecord::Relation, similar to what you're doing.

All 8 comments

I ran into the same problem with having to do this in order to get a named scope to work. Here's the way I'm getting the ids using load: false to prevent the initial SQL query / model load:

ids = Model.search(where: where_params, load: false).results.map(&:id)

Edit: Unfortunately .where(id: ids) and .find(ids) both do not preserve ordering (at least in Postgres), so I ended up having to use this gem as well. Feels pretty gross but works for now.

@abevoelker, thanks, dude!

but approach that used by this gem (order_as_specified) very very slow :-(

Perhaps #490 helps deal with this? You should be able to do:

Model.search(where: where_params).records

Which gives back an ActiveRecord::Relation, similar to what you're doing.

Had a chance to verify @Mavvie's suggestion tonight - seems to work great, thank you!

Unfortunately I spoke too soon, records does not in fact preserve ordering. Guess I must've gotten lucky the first time I tried it when I only had a handful of records to test.

search = Product.search("*", { price: {order: "desc"} })
# => [14, 22, 24, 26, 8, 12, 30, 20, 7, 16, 18, 28, 11]

search.records.map(&:id)
#   Product Load (0.4ms)  SELECT "products".* FROM "products" WHERE "products"."id" IN (14, 22, 24, 26, 8, 12, 30, 20, 7, 16, 18, 28, 11)
# => [7, 18, 11, 8, 14, 24, 20, 26, 16, 12, 22, 28, 30]

edit: just to make clear this is a Postgres thing and the types of "fixes" at the query level are complicated http://stackoverflow.com/a/35456954/215168

edit2: (of course if you just meant that the .where(id: ids) part could be replaced with .records you're totally right, but on Postgres hacks like the order_as_specified gem are still required with it)

Right. Forgot to mention that. There should be a way to preserve ordering inside searchkick, just converting it to an AR relation. I'll look closer.

Wow that would be awesome if possible. But even if not, .records is still cleaner than what I was doing before so thank you for that! :+1:

Adding ordering to the relation is possible, but messy. I can't see a better way to do it than order_as_specified. Here's how they do it

    order_by = params[:values].map do |value|
      "#{table}.#{attribute}='#{value}' DESC"
    end.join(", ")

Which was the most compatible way of doing it I saw mentioned in the stackoverflow answers. I don't want to add another dependency (esp. for an experimental feature), and fortunately it's simple enough there's no need to. I'll fork to add ordering and we can try it out/discuss it.

Was this page helpful?
0 / 5 - 0 ratings