I want to run multiple params of search with elasticsearch-model gem like below. Before i used gem tire to search records using multiple params of queries.
My model:
class Book < ApplicationRecord
include Tire::Model::Search
include Tire::Model::Callbacks
mapping do
indexes :id, type: 'integer'
indexes :title
indexes :author
indexes :category
indexes :price
end
def self.search(params)
tire.search(load: true) do
query do
boolean do
must { string params[:title] } if params[:title].present?
must { string params[:author] } if params[:author].present?
must { string params[:category] } if params[:category].present?
should { range :price, { gte: params[:price_range_min], lte:
params[:price_range_max] } } if
params[:price_range_min].present?
&& params[:price_range_max].present?
end
end
end
end
end
My controller:
class BookController < ApplicationController
def index
#books = Book.search(params)
#books =
Kaminari.paginate_array(#books).page(params[:page]).per(10)
end
end
I followed this documentation elasticsearch-model but i am getting results only for two params of field.
Any help?
Related
I'm using Sinatra with Mongoid and CarrierWave. I need to store document's attachments in /public/attachments/DOCUMENTS_ID.
Model of Mongo document:
class Dcmnt
include Mongoid::Document
store_in collection: 'dcmnts'
field :published, type: Boolean
field :name, type: String
field :description, type: String
field :additional, type: String
field :created_at, type: Date
mount_uploader :attachment, Uploader, type: String
end
And action's code:
post '/admin/create' do
params.delete 'submit'
d = Dcmnt.new(
:published => params[:published],
:name => params[:name],
:description => params[:description],
:additional => params[:additional],
:created_at => Time.now
)
d.attachment = params[:photos]
d.save
end
When I'm setting up unloader like this:
class Uploader < CarrierWave::Uploader::Base
storage :file
def store_dir
'public/attachments/' + d.id
end
end
It doesn't works for some amzaing reason. Can you help me implement this feature?
Accessing models attributes in CarrierWave is provided via model key word
class Uploader < CarrierWave::Uploader::Base
storage :file
def store_dir
'attachments/' + model.id
end
end
I'm having some difficulty with Elastic Search and Tire not returning any results. I'm using Ruby 1.9.3 and Rails 3.2.11.
In my controller I'm calling:
#location_id = 1
#listings = Listing.search(params.merge!(location_id: #location_id))
In my listing model I have
mapping do
indexes :id, type: 'integer'
...
indexes :author do
indexes :location_id, :type => 'integer', :index => :not_analyzed
...
end
def self.search(params={})
tire.search(load: true, page: params[:page], per_page: 20) do |search|
search.query { string params[:query], :default_operator => "AND" } if params[:query].present?
search.filter :range, posted_at: {lte: DateTime.now}
search.filter :term, "author.location_id" => params[:location_id]
end
I have 300 results which all have the location_id of 1 in the database so I can't seem to figure out why it's returning a nil set? If I comment out the author.location_id search filter line it returns all other results as expected?
There are several things which needs to be adressed in a situation like yours. Let's start with a fully working code:
require 'active_record'
require 'tire'
require 'logger'
# Tire.configure { logger STDERR }
# ActiveRecord::Base.logger = Logger.new(STDERR)
Tire.index('articles').delete
ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
ActiveRecord::Schema.define(version: 1) do
create_table :articles do |t|
t.string :title
t.integer :author_id
t.date :posted_at
t.timestamps
end
create_table :authors do |t|
t.string :name
t.integer :number, :location_id
t.timestamps
end
add_index(:articles, :author_id)
add_index(:authors, :location_id)
end
class Article < ActiveRecord::Base
belongs_to :author, touch: true
self.include_root_in_json = false
include Tire::Model::Search
include Tire::Model::Callbacks
mapping do
indexes :title
indexes :author do
indexes :location_id, type: 'integer'
end
end
def self.search(params={})
tire.search load: {include: 'author'} do |search|
search.query do |query|
query.filtered do |f|
f.query { params[:query].present? ? match([:title], params[:query], operator: 'and') : match_all }
f.filter :range, 'posted_at' => { lte: DateTime.now }
f.filter :term, 'author.location_id' => params[:location_id]
end
end
end
end
def to_indexed_json
to_json( only: ['title', 'posted_at'], include: { author: { only: [:location_id] } } )
end
end
class Author < ActiveRecord::Base
has_many :articles
after_touch do
articles.each { |a| a.tire.update_index }
end
end
# -----
Author.create id: 1, name: 'John', location_id: 1
Author.create id: 2, name: 'Mary', location_id: 1
Author.create id: 3, name: 'Abby', location_id: 2
Article.create title: 'Test A', author: Author.find(1), posted_at: 2.days.ago
Article.create title: 'Test B', author: Author.find(2), posted_at: 1.day.ago
Article.create title: 'Test C', author: Author.find(3), posted_at: 1.day.ago
Article.create title: 'Test D', author: Author.find(3), posted_at: 1.day.from_now
Article.index.refresh
# -----
articles = Article.search query: 'test', location_id: 1
puts "", "Documents with location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
articles = Article.search query: 'test', location_id: 2
puts "", "Documents with location:2", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
puts "(NOTE: 'D' is missing, because is not yet posted)"
articles = Article.search query: 'test b', location_id: 1
puts "", "Documents with query:B and location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
First, it's usually a good idea to create an isolated, extracted case like this.
In your example code, I assume you have a relationship Listing belongs_to :author. You need to properly define the mapping and serialization, which I again assume you did.
As for the query itself:
Unless you're using faceted navigation, use the filtered query, not top level filters, as in my example code.
Do not use the string query, unless you really want to expose all the power (and fragility!) of the Lucene query string query to your users.
Use the match query, as your "generic purpose" query -- Tire sprinkles some sugar on top of it, allowing to easily create multi_match queries, etc
The filter syntax in your example is correct. When the filter method is called multiple times in Tire, it creates and and filter.
Uncomment the Tire logging configuration (and possibly also the ActiveRecord logging), to see what the code is doing.
I have two simple classes
class Band
include Mongoid::Document
field :name, type:String
has_many :members
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
After I have created two object for test purposes
Band.create(title: 'New Band')
Band.members.create(name: 'New Member')
I got next db state:
> db.bands.find()
{ "_id" : ObjectId("..."), "title" : "New Band" }
> db.members.find()
{ "_id" : ObjectId("..."), "name" : "New Member", "band_id" : ObjectId("...") }
When I try to build json object of Band object I get data without children:
{"_id":"...","title":"New Band"}
But I need something like that:
{"_id":"...","title":"New Band", "members" : {"_id":"...","title":"New Member"}}
How to build json with children??
You can override serializable_hash:
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
def serializable_hash(options={})
{
id: id,
name: name
}
end
end
class Band
include Mongoid::Document
field :title, type: String
has_many :members
def serializable_hash(options={})
{
id: id,
title: title,
members: members.inject([]) { |acc, m| acc << m.serializable_hash; acc }
}
end
end
Suppose you have a band with a member:
band = Band.create(title: 'New Band')
band.members.create(name: 'New Member')
In that case band.to_json will return you something like that:
"{\"id\":...,\"title\":\"New Band\",\"members\":[{\"id\":...,\"name\":\"New Member\"}]}"
Try this:
a_band = Band.last
a_band.as_json(methods: [:members])
Mongoid auto-generates helper methods for your relations, and you can include these methods when you build your JSON object. You can use a_band.members to fetch the band's members out of the db, so you can include that method in your JSON object, like any other method on the model.
I've been hurting my head against what should be a simple query for quite a while now. I looked at all the documentation and examples, as well as most questions regarding Tire here on StackOverflow with no success.
Basically, I'm trying to filter my search results based on the IDs of some associated models.
Here's the model (note that I still use dynamic mapping at the moment):
class Location < ActiveRecord::Base
belongs_to :city
has_and_belongs_to_many :tags
# also has a string attribute named 'kind'
end
What I'm trying to do is to filter my search query by the city_id, by one tag_id and by kind.
I've tried building the query, but I only get errors because I can't seem to build it correctly. Here's what I have so far (not working):
Location.search do
query { string params[:query] } if params[:query].present?
filter :term, { city_id: params[:city_id] } if params[:city_id].present? # I'd like to use the ids filter, but have no idea of the syntax I'm supposed to use
filter :ids, { 'tag.id', values: [params[:tag_id]] } if params[:tag_id].present? # does not compile
filter :match, { kind: params[:kind] } if params[:kind].present? # does not compile either
end
Turns out the dynamic mapping doesn't cut it for this kind of scenario. I also had to define how my data was indexed.
Here's my mapping:
mapping do
indexes :id, index: :not_analyzed
indexes :kind, index: :not_analyzed
indexes :city_id, index: :not_analyzed
indexes :tags do
indexes :id, index: :not_analyzed
end
end
and my custom to_indexed_json:
def to_indexed_json
{
kind: kind,
city_id: city_id,
tags: tags.map do |t|
{
id: t.id
}
end
}.to_json
end
Finally, I can filter like so:
Location.search do
query { string params[:query] } if params[:query].present?
filter :term, { city_id: params[:city_id] } if params[:city_id].present?
filter :term, { "tags.id" => params[:tag_id] } if params[:tag_id].present?
filter :term, { kind: params[:kind] } if params[:kind].present?
end
The important part is the tags indexing which allows me to use "tags.id" in a filter.
I am using tire (https://github.com/karmi/tire) with mongoid. Here is my model definition:
class SomethingWithTag
include Mongoid::Document
include Mongoid::Timestamps
field :tags_array, type: Array
include Tire::Model::Search
include Tire::Model::Callbacks
mapping do
indexes :tags_array, type: :array, index: :not_analyzed
end
end
Say I have a document {tags_array: ["hello world"]}. Then the following queries work fine:
SomethingWithTag.tire.search { filter :terms, :tags_array => ["hello"] }
SomethingWithTag.tire.search { filter :terms, :tags_array => ["world"] }
SomethingWithTag.tire.search { filter :terms, :tags_array => ["hello", "world"] }
But the following doesn't return any results:
SomethingWithTag.tire.search { filter :terms, :tags_array => ["hello world"] }
What should I do to make it work?
Edit: here's a small piece of code to test: http://pastebin.com/n1rUtK3e
Issue solved at :
Use the keyword analyzer for the tags_array property:
class SomethingWithTag
# ...
mapping do
indexes :tags_array, analyzer: 'keyword'
end
end