I'm trying to index a model when I have a has_many, :through association, but no results are being displayed.
class Business < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
def self.search(params)
tire.search(load: true) do
query { string params[:q]} if params[:q].present?
end
end
mapping do
indexes :service_name
indexes :service_description
indexes :latitude
indexes :longitude
indexes :services do
indexes :service
indexes :description
end
end
def to_indexed_json #returns json data that should index (the model that should be searched)
to_json(methods: [:service_name, :service_description], include: { services: [:service, :description]})
end
def service_name
services.map(&:service)
end
def service_description
services.map(&:description)
end
has_many :professionals
has_many :services, :through => :professionals
end
Then this is Service model
class Service < ActiveRecord::Base
attr_accessible :service, :user_id, :description
belongs_to :professional
belongs_to :servicable, polymorphic: true
end
I've also reindex using this:
rake environment tire:import CLASS=Business FORCE=true
I can search for the items in Business, but when I tried to search something in Service, I get an empty result.
After struggling with mapping, I created a gem to make search a bit easier. https://github.com/ankane/searchkick
You can use the search_data method to accomplish this:
class Business < ActiveRecord::Base
searchkick
def search_data
{
service_name: services.map(&:name),
service_description: services.map(&:description)
}
end
end
I do not believe there is a way to do mapping on associations with Tire. What you will want to do instead is define easily searchable fields with the :as method and a proc. This way you can also get rid of the to_indexed_json method (you will actually need to)
mapping do
indexes :service_name
indexes :service_description
indexes :latitude
indexes :longitude
indexes :service_name, type: 'string', :as => proc{service_name}
indexes :service_description, type: 'string', :as => proc{service_description}
end
Tire can associate with associations, I've used it to index on has_many association but have not tried has_many, :through yet. Try index on object?
mapping do
indexes :service_name
indexes :service_description
indexes :latitude
indexes :longitude
indexes :services, type: 'object',
properties: {
service: {type: 'string'}
description: {type: 'string'}
}
end
Also, it might be good to have a touch method :
class Service < ActiveRecord::Base
attr_accessible :service, :user_id, :description
belongs_to :professional, touch: true
belongs_to :servicable, polymorphic: true
end
and after_touch callback to update the index.
Related
I have:
gem 'rails', '~> 5.1.1'
gem 'mongoid', '~> 6.1.0'
I have 3 models, related to this question.
class Book
include Mongoid::Document
field :name, type: String
field :author, type: String
field :description, type: String
field :status, type: String
field :image, type: String
has_many :histories, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
def ordered_histories
histories.order(taken_in: :desc)
end
def book_rating
rating_array = likes.map { |like| like.rate.to_i }
return 0 if rating_array.empty?
(rating_array.sum / rating_array.size).round
end
end
class Like
include Mongoid::Document
field :rate, type: Integer
belongs_to :book
belongs_to :user
end
class History
include Mongoid::Document
field :taken_in, type: DateTime
field :returned_in, type: DateTime
belongs_to :book
belongs_to :user
end
I need to get top 5 books which have the most count of likes and histories.
As I know - mongoid not support joins.
I thought about storeing likes_count and histories_count in Book model. But I think that it is not best way. Please help me to find better solution to get top 5 books from mongodb.
You should use the aggregation framework.
Try the following code to get the ids of the top 5 books with most histories count. The same approach can be used for the Like class.
History.collection.aggregate([
{"$group" => {
"_id" => "$book_id",
"histories" => {"$sum" => 1}
}},
{"$sort" => { "histories" => -1}},
{"$limit" => 5 }
])
I found another way to make it works.
I have add counter_cash to model Like and History:
class Like
include Mongoid::Document
field :rate, type: Integer
belongs_to :book, counter_cache: :likes_count
belongs_to :user
end
class History
include Mongoid::Document
field :taken_in, type: DateTime
field :returned_in, type: DateTime
belongs_to :book, counter_cache: :histories_count
belongs_to :user
end
After that I use one query to get top 5 books which have the most count of likes and histories:
#top_books = Book.all.order_by(likes_count: :desc, histories_count: :desc).limit(5)
I think it is one of possible way to do this. May be it help somebody. Tell me please, if I made any mistakes.
I develop engine for Ruby on Rails project. Here you can see how I solve the problem of eager loading associated objects with dynamic condition:
My models:
#version.rb
class Version < ActiveRecord::Base
class <<self
attr_accessor :type
attr_accessor :id
end
validates :name, :description, presence: true
belongs_to :package
has_many :clustervers,:dependent => :destroy
has_many :accesses,:dependent => :destroy
has_many :user_accesses,-> { where( "who_type= ? AND who_id=?",Version.type,Version.id) }, class_name: "Access"
end
#Access.rb
class Access < ActiveRecord::Base
enum status: [:request,:allowed,:denied]
validates :version_id, :user_id,presence: true
validates_uniqueness_of :user_id, :scope => [:version_id]
belongs_to :version
belongs_to :user
belongs_to :who, :polymorphic => true
end
Controller:
def show
Version.type="User"
Version.id=2
#package = Package.find(params[:id])
#versions = Version.page(params[:page]).per(2).includes({clustervers: :core_cluster},:user_accesses)
.where(package_id:params[:id])
end
View:
for version in #versions
tr
td #{version.name}
td #{version.description}
td #{version.r_up}
td #{version.r_down}
-if version.user_accesses.take
p show attributes
-else
p no access
td
Are there more convenient methods to do this? Is it possible to do this using raw SQL statments?
I need something to generate SQL condition like this:
LEFT OUTER JOIN ... ON a.id=b.id and CONDITION
I've a model that has a nested model of skills. Its a common has_many example. Elastic search is indexing the skills as an array of strings.
My question is, I am attempting to match on those skills by way of two different inputs.
Required skills and bonus skills.
So if I have two query terms one for required and one for bonus, I want to query the skills attribute with required input, if none found, query with the bonus input.
I'm using elasticsearch-rails gem. Didn't think I needed to post any code as this is more theory.
UPDATE
class Profile
has_many :skills
...
end
class Skill
belongs_to :profile
end
Mappings
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
...
mapping dynamic: 'false' do
indexes :skills, analyzer: 'keyword'
end
...
end
Overriden as_json
def as_indexed_json(options={})
hash = self.as_json(
include: {location: { methods: [:coordinates], only: [:coordinates] },
locations_of_interest: { methods: [:coordinates], only: [:coordinates]}
})
hash['skills'] = self.skills.map(&:name)
hash['interests'] = self.interests.map(&:name)
hash
end
I guess in essence i'm looking to perform the reverse of a multi_match on multiple fields and boosting one but instead searching one field with multiple inputs (required and bonus) and depending no the results of required search with bonus input. Does this makes things more clear?
This is my query so far, first attempt.
if options[:required_skills].present? && options[:bonus_skills].present?
bool do
must do
term skills: options[:required_skills]
end
should do
term skills: options[:bonus_skills]
end
end
end
class SkillContainer < ActiveRecord::Base
has_many :skill_links, dependent: :destroy, inverse_of: :skill_container
has_many :skills, through: :skill_links
has_many
end
##################################
create_table :skill_link do |t|
t.references :skill
t.references :skill_container
t.boolean :required
t.boolean :bonus
end
##################################
class SkillLink
belongs_to :skill_container
belongs_to :skill
scope :required, -> {
where(required: true)
}
scope :bonus, -> {
where(bonus: true)
}
end
class Skill < ActiveRecord::Base
has_many :skill_links, dependent: :destroy, inverse_of: :skill
has_many :skills, through: :skill_links
end
#required skills from any skill container
SkillContainer.last.skills.merge(SkillLink.required)
#bonus skills from any skill container
SkillContainer.last.skills.merge(SkillLink.bonus)
scopes can be combined with your elastic search
Im started to use Elasticsearh in my project, and have problem with result ordering.
In fact I need to sort my records by hstore record in connected (belongs_to) model.
More details:
So, I have a Model that I want to be searchable. This model have connections with another models, here the code:
class PropertyObject < ActiveRecord::Base
belongs_to :country, :counter_cache => true
belongs_to :region, :counter_cache => true
belongs_to :city, :counter_cache => true
belongs_to :property_object_type, :counter_cache => true
belongs_to :property_object_state, :counter_cache => true
has_one :property_object_parameter_pack, dependent: :destroy
has_one :property_object_feature_pack, dependent: :destroy
has_one :property_object_description, dependent: :destroy
has_one :property_object_seo_field, dependent: :destroy
end
I want to include to my search results next fields:
Model PropertyObject:
:code :string
Model Country
:title_translations :hstore
Model Region
:title_translations :hstore
Model City
:title_translations :hstore
Model PropertyObjectDescription
:title_translations :hstore
:main_text_translations :hstore
Model PropertyObjectParameterPack
:price :hstore (example: {min => 10, max=>100})
To make this work I had create concern Searchable and add it to my model PropertyObject.
Here the code of it:
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
mapping do
indexes :property_object_parameter_pack, type: 'nested' do
indexes :price do
indexes :min, type: :integer
end
end
end
# Customize the JSON serialization for Elasticsearch
def as_indexed_json(options={})
self.as_json(
include: {
country: {only: :title_translations},
region: {only: :title_translations},
city: {only: :title_translations},
property_object_description: {only: [:title_translations, :main_text_translations]},
property_object_parameter_pack: {only: [:price, :area, :rooms]}
})
end
end
end
Controller part where search is calling
def search
pagen = params[:page] || 1
#property_objects = PropertyObject.search(params[:q]).page(pagen).records
end
So now searching working and all seems good. But I need sort results of search by min price.
I had try order method that works in my another orders - but no luck.
As I understand I need to use Elasticsearch sorting , to get result already sorted - but spend a lot of hours trying to implement this and fail.
What you can suggest me?
UPDATE
Had try this code:
pagen = params[:page] || 1
query = params[:q]
params[:order] ||= 'asc'
property_objects = PropertyObject.search(query) do |s|
s.query do |q|
q.string query
end
s.sort { by :property_object_parameter_pack.price.min, params[:sort]}
end
#property_objects = property_objects.page(pagen).records
With different variants
s.sort by
by :price
by :price.min
by :price[:min]
by :property_object_parameter_pack.price.min
by :property_object_parameter_pack.price[:min]
and no luck with ordering.
In the end I decide to understand how Elasticsearch works, and start to read Elasticsearch: The Definitive Guide - where the answer was founded.
First off all I recommend to read this guide and install Marvel
. After this all becomes much more clearer then using CURL. In fact I discover my index structure with Marvel, and just implement it to search query of elasticsearch-rails gem.
Next thing that I did - I had rebuild price from hstore to separate integer columns : like price_min and price_max.
So in short the answer code is:
sq = {
"query": {
"multi_match": {
"query": "Prague",
"fields": [ "country.title_translations.en",
"region.title_translations.en",
"city.title_translations.en",
"property_object_description.main_text_translations.en",
"property_object_description.title_translations.en"
]
}
},
"track_scores": true,
"sort": {
"property_object_parameter_pack.price_min":
{ "order": "desc",
"missing" : "_last"
}
}} PropertyObject.search (sq)
In fact Im sure that it will work with hstore. Because I store translations in hstore and it indexing fine - just need to point right field (in this task Marvel helps with autocomplete).
Did you try this?
def search
options = { :page => (params[:page] || 1) }
#property_objects = PropertyObject.search, options do |f|
f.query { string params[:q] }
f.sort { by :price, 'desc' }
end
#property_objects.records
end
I have simple Rails app with a Post model that belongs to a creator (which is a User object) and I want to find posts that match the creator's first_name.
class Post < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
belongs_to :creator, polymorphic: true
mapping do
indexes :id, type: 'integer'
indexes :title, boost: 10
indexes :creator do
indexes :first_name
indexes :service_type
end
end
end
When I run search, there are no results:
Post.tire.search(load: true, page: params[:page], per_page: params[:per_page]) do
query do
match 'creator.first_name', 'Matt'
end
end
Even when I've run rake environment tire:import:all FORCE=true and verified that a Post exists with a creator that has a first name of 'Matt' in the database.
Is there something I am missing? Thanks!
UPDATE:
Adding the following to Post.rb did the trick:
def to_indexed_json
as_json.merge(
creator: creator.as_indexed_json
).to_json
end
Apparently, you need to specify the association in the to_indexed_json to make it work.