I have implemented ajax-datatables-rails for one table where I get raw records with query using group
class ListcontractorsDatatable < AjaxDatatablesRails::Base
def view_columns
# Declare strings in this format: ModelName.column_name
# or in aliased_join_table.column_name format
#view_columns ||= {
id: { source: "Contractor.id", cond: :eq },
name: { source: "Contractor.name" },
city: { source: "Contractor.city" },
ico: { source: "Contractor.ico" },
country: { source: "Contractor.country" },
count: {source: "Contract.count"}
}
end
def_delegators :#view, :link_to, :showcontractor_path, :content_tag
def data
records.map do |contractor|
{
# example:
id: contractor.id,
name: link_to(contractor.name, showcontractor_path(contractor.id)),
city: contractor.city,
ico: contractor.ico,
country: contractor.country,
count: contractor.count,
extlink: link_to(content_tag(:i,nil,class: 'fa fa-external-link'), 'https://www.somepage.com/'+contractor.ico.to_s)
}
end
end
private
def get_raw_records
Contractor.joins(:contracts).select("contractors.id,name,ico,city,country,count(resultinfo_id) as count")
.group("contractors.id,contractors.name,contractors.ico,contractors.city,contractors.country")
end
end
everything works (ordering, pagination, all columns are correct) except search. When I put anything into search I got error
PG::GroupingError: ERROR: aggregate functions are not allowed in
WHERE
I cannot find any working solution with group in query for branch 0.4.0. Could you please help me? thx
Related
Hi I am new to Ruby on Rails development. I have two queries with different model. My first_query is get from question model and second query is get from favourite model. I want to map with a column user_favourite from second query result to first query result.
this is my controller queries
def index
#first_query = Question.order('created_at DESC').page(params[:page]).per( (ENV['ILM_QUESTIONS_PER_PAGE'] || 5).to_i )
#second_query=Favourite.with_user_favourite(#user)
#combined_queries = #first_query + #second_query
end
favourite.rb
scope :with_user_favourite, -> (user) {
joins(:user).
where(favourites: {user_id: user})
}
index.json.builder
json.questions #combined_events
json for the result is
{
questions: [ #this is first query result
{
id: 88,
user_id: 28,
content: "test32",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
}
},
{
id: 87,
user_id: 18,
content: "testing riyas",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
}
},
{ #this is second query result
id: 1,
user_id: 2,
question_id: 84,
created_at: "2016-05-12T06:51:54.555-04:00",
updated_at: "2016-05-12T06:51:54.555-04:00"
},
{
id: 2,
user_id: 2,
question_id: 81,
created_at: "2016-05-12T07:23:47.770-04:00",
updated_at: "2016-05-12T07:23:47.770-04:00"
}
]
}
i want response like
{
questions: [
{ #first query result
id: 88,
user_id: 28,
content: "test32",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
},
user_favorite: { #corresponding result from second query result
id: 1,
user_id: 2,
question_id: 88
}
},
{ #first query result
id: 87,
user_id: 18,
content: "testing riyas",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
},
user_favorite: {} #corresponding result from second query result if there is no result for particular question in favourite table
},
]
}
The model relationships are:
class Question
belongs_to :user
has_many :favourite
end
class Favourite
belongs_to :user
belongs_to :question
end
class User
has_many :questions
has_many :favourite
end
You should modify your jBuilder template to support nesting.Since your model association is like one question has_many favorite so it will be an array and you can easily nest one object inside another.
json.array! #questions do |question|
json.user_id question.user_id
json.content question.content
json.user_favorites question.favorites do |json,favorite|
json.id question.favorite.id
json.user_id question.favorite.user.id
json.question_id question.id
end
end
Here is a link that you can refer to for more clarity.
Generate a nested JSON array in JBuilder
Using JBuilder to create nested JSON output in rails
Hope it helps!.
You can add an association between user_favourite and question so that you can select all user favourites on one question.
Question.rb:
has_many :user_favourites
UserFavourite.rb:
belongs_to :question
Then, as your web action:
def index
#questions = Question.all.order('created_at DESC').page(params[:page]).per((ENV['ILM_QUESTIONS_PER_PAGE'] || 5).to_i)
end
And finally, in index.json.builder:
json.questions #questions do |question|
json.user_favourites question.user_favourites
end
including whatever other fields you want.
After reading several sites (including elasticsearch's documentation) and experimenting around a lot, I'm having trouble getting highlights. I can do the basic keyword search, but it's clear I'm not grasping something. Here's my code.
Gems:
gem 'elasticsearch-model'
gem 'elasticsearch-rails'
Controller:
class TermsController < ApplicationController
def search
#terms = Term.search(params[:query]).results
end
end
Model:
require 'elasticsearch/model'
class Term < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
mappings dynamic: 'false' do
indexes :id, index: :not_analyzed
indexes :name, analyzer: 'spanish'
indexes :gender, index: :not_analyzed
indexes :part_of_speech, index: :not_analyzed
indexes :definition
indexes :etymology1
indexes :etymology2
indexes :uses
indexes :romance_cognates
indexes :notes1
indexes :notes2
indexes :quote1, analyzer: 'spanish'
indexes :quote2, analyzer: 'spanish'
end
end
def as_indexed_json(options = {})
as_json(
only: [:name, :gender, :part_of_speech, :definition, :etymology1, :etymology2, :uses, :romance_cognates, :notes1, :notes2, :quote1, :quote2]
)
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
tags_schema: 'styled',
fields: {
:'*' => {}
}
}
}
)
end
end
# Delete the previous terms index in Elasticsearch
Term.__elasticsearch__.client.indices.delete index: Term.index_name rescue nil
# Create the new index with the new mapping
Term.__elasticsearch__.client.indices.create \
index: Term.index_name,
body: { settings: Term.settings.to_hash, mappings: Term.mappings.to_hash }
# Index all term records from the db to Elasticsearch
Term.import(force: true)
I also tried:
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
fields: {
content: {'force_source': true}
}
}
}
and
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1^5', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
fields: {
content: {type: 'plain'}
}
}
}
and
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1^5', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
pre_tags: ['<tag1>']
post_tags: ['</tag1>']
fields: {
_all: {}
}
}
}
...Along with many other attempts I can't remember
It appears the key that I was missing as illustrated here is that I needed the try() method in my view template. I'm sure there's a more concise way of writing this, but a sample of my view syntax looks like this:
<%= term.try(:highlight).try(:definition) ? term.highlight.definition[0].html_safe : term.definition.html_safe %>
<%= term.try(:highlight).try(:etymology1) ? term.highlight.etymology1[0].html_safe : term.etymology1.html_safe %>
class Product < ActiveRecord::Base
belongs_to :city
has_and_belongs_to_many :categories
before_destroy { categories.clear }
searchkick locations: ["location"]
def search_data
{
istatus: i_status,
name: name,
price: price,
city_id: city_id,
value: value,
discount: discount,
expiry_date: expiry_date,
created_at: created_at,
products_sold: products_sold,
city: city.name,
deal_type: deal_type,
country: city.country.name,
category_id: categories.map(&:id),
location: [latitude, longitude]
}
end
def self.apply_filters(request)
# #product = Product.search "Tex-Mex", limit:10 #=>this works
#product = Product.search body: {match: {name: "Tex-Mex"}},limit: 10 #=>does not work, the limit part work
end
end
when i use advanced search using body.. it does not return the desired results although the limit:10 part us working as it does return 10 results only
I believe there is some missing information in the documentation.
Here's a reference to a body query that works based on the tests written in SearchKick:
https://github.com/ankane/searchkick/blob/c8f8dc65df2e85b97ea508e14ded299bb8111942/test/index_test.rb#L47
For advanced search to work, the way it should be written is:
#product = Product.search body: { query: { match: {name: "Tex-Mex"}}},limit: 10
You need a query key following the body.
// conditions = {}
query = Product.search params[:query], execute: false, where : conditions
query.body[:query] = { match: {name: "Tex-Mex"} }
query.body[:size] = 10
query.execute
You would need to build your query using the Elasticsearch DSL. Specifically, using size and match.
Product.search body: { query: { match: {name: "Tex-Mex"} }, size: 10 }
When using Advanced search, Searchkick ignores parameters outside the body hash. While the body hash allows you to use the full ES DSL.
I have a model named Movie that looks like this:
class Movie < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
has_many :actors, after_add: [ lambda {|a,c| a.__elasticsearch__.index_document}],
after_remove: [ lambda {|a,c| a.__elasticsearch__.index_document}]
settings index: {number_of_shards: 1} do
mappings dynamic: 'false' do
indexes :title, analyzer: 'snowball', boost: 100
indexes :actors
end
end
def as_indexed_json(options={})
self.as_json(
include: {
actors: { only: :name}
}
)
end
end
When i do Movie.first.as_indexed_json , I get:
{"id"=>6, "title"=>"Back to the Future ",
"created_at"=>Wed, 03 Dec 2014 22:21:24 UTC +00:00,
"updated_at"=>Fri, 12 Dec 2014 23:40:03 UTC +00:00,
"actors"=>[{"name"=>"Michael J Fox"}, {"name"=>"Christopher Lloyd"},
{"name"=>"Lea Thompson"}]}
but when i do Movie.search("Christopher Lloyd").records.first i get: => nil .
What changes can i make to the index to search movies associated with the searched actor?
I used filtering query to solve this, first I created an ActiveSupport::Concern called searchable.rb, the concern looks like this:
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name [Rails.application.engine_name, Rails.env].join('_')
settings index: { number_of_shards: 3, number_of_replicas: 0} do
mapping do
indexes :title, type: 'multi_field' do
indexes :title, analyzer: 'snowball'
indexes :tokenized, analyzer: 'simple'
end
indexes :actors, analyzer: 'keyword'
end
def as_indexed_json(options={})
hash = self.as_json()
hash['actors'] = self.actors.map(&:name)
hash
end
def self.search(query, options={})
__set_filters = lambda do |key, f|
#search_definition[:post_filter][:and] ||= []
#search_definition[:post_filter][:and] |= [f]
end
#search_definition = {
query: {},
highlight: {
pre_tags: ['<em class="label label-highlight">'],
post_tags: ['</em>'],
fields: {
title: {number_of_fragments: 0}
}
},
post_filter: {},
aggregations: {
actors: {
filter: {bool: {must: [match_all: {}]}},
aggregations: {actors: {terms: {field: 'actors'}}}
}
}
}
unless query.blank?
#search_definition[:query] = {
bool: {
should: [
{
multi_match: {
query: query,
fields: ['title^10'],
operator: 'and'
}
}
]
}
}
else
#search_definition[:query] = { match_all: {} }
#search_definition[:sort] = {created_at: 'desc'}
end
if options[:actor]
f = {term: { actors: options[:taxon]}}
end
if options[:sort]
#search_definition[:sort] = { options[:sort] => 'desc'}
#search_definition[:track_scores] = true
end
__elasticsearch__.search(#search_definition)
end
end
end
I have the above concern in the models/concerns directory.
In movies.rb I have:
class Movie < ActiveRecord::Base
include Searchable
end
In movies_controller.rb I am doing searching on the index action and the action looks like this:
def index
options = {
actor: params[:taxon],
sort: params[:sort]
}
#movies = Movie.search(params[q], options).records
end
Now when i go to http://localhost:3000/movies?q=future&actor=Christopher I get all records which have the word future on their title and has an actor with a name Christopher. You can have more than one filter as shown by the expert template of the example application templates found here .
You can try add method search to your model like this:
class Movie < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# ...
def self.search(query, options = {})
es_options =
{
query: {
query_string: {
query: query,
default_operator: 'AND',
}
},
sort: '_score',
}.merge!(options)
__elasticsearch__.search(es_options)
end
# ...
end
Here is some examples of method search: http://www.sitepoint.com/full-text-search-rails-elasticsearch/
And now you can search in all your indexed fields.
You need to specify the fields in the the search method, like:
def self.search query
__elasticsearch__.search(
query: {
multi_match: {
query: query,
fields: %w[title actor.name]
}
}
)
end
Try this
indexes :actors do
indexes :name, type: "string"
end
Im migrating from Tire to Flex
Basic search and index-sync works
I figured the flex.parent line in the model would auto-create the _parent mapping, but it crashes
I was not able to find any parent/child demo project.
Flex.yml:
settings:
number_of_shards: 5
number_of_replicas: 1
# analysis:
# analyzer:
# tokenizer:
mappings:
userprofile:
startdatef:
type: 'date'
format: 'dateOptionalTime'
fields:
index: 'not_analyzed'
untouched:
type: 'date'
index: 'not_analyzed'
orgunit:
org_name:
type: 'string'
index: 'analyzed'
search_analyzer: orgunit_name_search
index_analyzer: orgunit_name_index
untouched:
type: 'string'
index: 'not_analyzed'
Parent model:
class Userprofile < ActiveRecord::Base
include Flex::ModelIndexer
include Flex::Model
flex.sync self
has_many :assignments,
-> { order(startdate: :desc) }, dependent: :restrict_with_exception
module Search
include Flex::Scopes
flex.context = Userprofile
scope :alla, query([])
end
# rubocop:disable all
def flex_source
{
id: id,
fullname: fullname,
firstname: firstname,
lastname: lastname,
pnr: pnr,
gender: gender,
asscount: asscount,
created_at: created_at,
updated_at: updated_at,
user_id: user_id,
creator_id: creator_id,
}
end
# rubocop:enable all
end
Child model:
class Assignment < ActiveRecord::Base
include Flex::ModelIndexer
include Flex::Model
flex.parent :userprofile, 'userprofile' => 'assignment' # This makes indexing break
flex.sync self, :userprofile
belongs_to :userprofile, counter_cache: true, touch: true
module Search
include Flex::Scopes
flex.context = Assignment
scope :alla, query([])
end
def flex_source
{
# _parent_id: userprofile_id,
userprofile_id: userprofile_id,
created_at: created_at,
updated_at: updated_at
}
end
end
rake flex:import
Model Userprofile: Processing 37 documents in batches of 1000:
processing...: 100% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| Time: 0:00:01
Processed 37. Successful 37. Skipped 0. Failed 0.
Model Assignment: Processing 36 documents in batches of 1000:
rake aborted!: 0% | | ETA: --:--:--
activerecord-4.0.2/lib/active_record/relation/batches.rb:75:in find_in_batches'
400: {"error":"ElasticSearchIllegalArgumentException[Can't specify parent if no parent field has been configured]","status":400}
flex-1.0.6/lib/flex/template.rb:54:indo_render'
...
Tasks: TOP => flex:import
In your mapping you have to specify the "parent" field. Only then ES can link the ID in the "_parent" field you want to index to the corresponding index-type. Try this as a template (substitute the variables apropriate to your index). It just adds the infos to the mapping, not removing existing ones:
curl -XPUT 'http://localhost:9200/$yourIndex/$yourChildTypeName/_mapping' -d '
{
"$yourChildTypeName" : {
"_parent" : {
"type" : "$yourParentTypeName"
}
} '
Then try indexing again.