Hello I've got this problem with the rails Elascticsearch range aggregations, it seems right as there's no error output but then again it also doesn't aggregate.
Heres my controller
def results
min_price = params[:min_price] if params[:min_price].present?
max_price = params[:max_price] if params[:max_price].present?
price_ranges = [{to: max_price}, {from: min_price, to: max_price}, {from: min_price}]
#results = Item.search(params[:q], aggs: {item_final_price: {ranges: price_ranges}}, page: params[:page], per_page: 10) if params[:q].present?
end
and my model
class Item < ApplicationRecord
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# Item.import
searchkick callbacks: :async, highlight: [:item_name]
def search_data
{
item_name: item_name,
item_details: item_details,
item_final_price: item_final_price,
item_av_rating: item_av_rating
}
end
end
and my views
<%= form_tag results_path, method: :get, enforce_utf8: false, id: "q_filter" do %>
<section class="widget widget-categories">
<%= hidden_field_tag :q, params[:q] %>
<h3 class="widget-title">Price Range</h3>
<div class="form-group">
<label>Price Between</label>
<%= number_field_tag :min_price, params[:min_price], class: "form-control form-control-sm", placeholder: "Min Price" %>
</div>
<div class="form-group">
<label>And</label>
<%= number_field_tag :max_price, params[:max_price], class: "form-control form-control-sm", placeholder: "Max Price" %>
</div>
<%= button_tag(type: "submit", name: nil, class: "btn btn-outline-primary btn-sm btn-block") do %>
Filter Search
<% end %>
</section>
<% #results.each do |item| %>
<%= item.item_name %>
<% end %>
Try to target this in your view
#results.response["aggregations"]["item_final_price"]["buckets"]
If you're running in development, can you throw a binding.pry and see what the result keys are? The data should be there if the query returns results.
Related
I have the following form:
<div class='panel' id='panel-advanced-search'>
<%= form_tag contacts_path, method: :get do %>
<div>
<div class='advanced_search'>
<div>
<%= label_tag "Contact Type" %>
<%= select_tag(:contact_type, options_for_select(Contact::TYPES, selected: params[:contact_type]), prompt: 'Any', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Prospect Strength" %>
<%= select_tag(:prospect_strength, options_for_select(Contact::PROSPECT_STRENGTHS, selected: params[:prospect_strength]), prompt: 'Any', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Customer" %>
<%= collection_select(:customer_account_id, :customer_account_id, Customer.order(:name), :id, :name, { prompt: '' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Supplier" %>
<%= collection_select(:supplier_account_id, :supplier_account_id, Supplier.order(:name), :id, :name, { prompt: '' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Company Name" %>
<%= text_field_tag :company_name %>
</div>
<div>
<%= label_tag "Name" %>
<%= text_field_tag :name %>
</div>
<div>
<%= label_tag "Job Title" %>
<%= text_field_tag :title %>
</div>
<div>
<%= label_tag "Postcode" %>
<%= text_field_tag :postcode %>
</div>
<div>
<%= label_tag :created_at, 'Created From' %>
<div class="date-picker">
<%= text_field_tag :created_at, nil, class: 'date-picker-select' %>
<span class="date-picker-btn">
<span class="icon-calendar" aria-hidden="true"></span>
</span>
</div>
</div>
<div>
<%= label_tag :updated_at, 'Created To' %>
<div class="date-picker">
<%= text_field_tag :updated_at, nil, class: 'date-picker-select' %>
<span class="date-picker-btn">
<span class="icon-calendar" aria-hidden="true"></span>
</span>
</div>
</div>
<div>
<%= label_tag "Tags" %>
<%= collection_select(:tag_list, :tag_list, #tags.order(:name), :name, :name, {}, { class: 'select2', multiple: true }) %>
</div>
<div>
<%= label_tag "Assignee" %>
<%= collection_select(:assigned_to, :assigned_to, User.all, :id, :name, { prompt: 'Any' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Obsolete?" %>
<%= select_tag(:obsolete, options_for_select(['Obsolete', 'All'], selected: params[:obsolete]), prompt: 'Not obsolete?', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Send Results To" %>
<%= select_tag(:subsequent_action, options_for_select([
['Report', 'report'],
['CSV Export', 'csv_export'],
['New Event', 'new_event']
]), prompt: 'None', class: 'customSelect') %>
</div>
</div>
<div class="advanced_search_btns">
<%= submit_tag submit_text %>
<%= link_to secondary_btn, contacts_path, class: 'btn-medium' %>
</div>
</div>
<% end %>
</div>
and the following method in the model
def self.advanced_search
Contact.where('
contact_type LIKE :search OR
prospect_strength LIKE :search OR
customer_account_id LIKE :search OR
supplier_account_id LIKE :search OR
company_name LIKE :search OR
name LIKE :search OR
title LIKE :search OR
postcode LIKE :search OR
created_at LIKE :search OR
updated_at LIKE :search OR
tag_list LIKE :search OR
assigned_to LIKE :search OR
obsolete LIKE :search
', search: "%#{search}%"
)
end
How do I go about using this method so the user can use this search form with multiple params? I already have the following in the index method for a basic search so I need to have both search forms
def index
#per_page = params[:per_page] || 20
#tags = ActsAsTaggableOn::Tag.all
if params[:search].present?
#contacts = Contact.search(params[:qs], params[:search]).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
else
#contacts = Contact.all.order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
end
end
Edit Updated the form above to the complete form, ideally, I would like the forms functionality to be entirely in the model.
Edit #2
This is the basic search:
model
QUICK_SEARCH_FIELDS = {
name: {
column_names: 'contacts.name'
},
customers: {
joins_table: :customer,
column_names: 'contacts.name',
filters: { contact_type: 'customer' }
},
suppliers: {
joins_table: :supplier,
column_names: 'contacts.name',
filters: { contact_type: 'supplier' }
},
tags: {
tagged_with: true
}
}.with_indifferent_access
def self.search(field, query)
field = QUICK_SEARCH_FIELDS[field]
contact = all
contact = contact.joins(field[:joins_table]) if field[:joins_table]
contact = contact.where(field[:filters]) if field[:filters]
contact = contact.where("#{field[:column_names]} LIKE ?", "%#{query}%") if field[:column_names]
contact = contact.tagged_with(query) if field[:tagged_with]
contact
end
form
<div class='panel' id='panel-search'>
<%= form_tag contacts_path, method: :get do %>
<div class='l-inline-row-block'>
<div class='l-inline-col width_120px'>
<%= select_tag(:qs, options_for_select(Contact::QUICK_SEARCH_FIELDS.keys(), selected: params[:qs]), class: 'customSelect') %>
</div>
<div class='l-inline-col'>
<%= search_field_tag :search, params[:search] %>
</div>
<div class='l-inline-col' style='width: 100px;'>
<%= submit_tag submit_text %>
</div>
</div>
<% end %>
</div>
you may need to make view adjustments and you cannot just use the search term/query for all fields:
1.make sure the fields inside the form in your view are all the same names of the columns in the Contact model that need to be searched
2.add a hidden field inside each form so you can determine which search this is (there is another option but this one is less work)
3.change the collection_select where user can only select one field to select_tag in order to not mess up query building
every field that is filled will pass a value- you can build a hash including column/form-field entered and their value e.g { contact_type: "value1", prospect_strength: "value2" }
after that, you would need to build a query using that hash and query the DB
this would look like so:
In your view:
add this in the advanced search form
<%= hidden_field_tag :search_type, :advanced %>
and in your normal search add this
<%= hidden_field_tag :search_type, :normal %>
In your model:
def self.advanced_search(values_by_column_name) # values_by_column_name is a hash passed from the controller
tag_list = values_by_column_name.delete("tag_list")
sql_query = values_by_column_name.keys.map { |column| "#{column} LIKE :#{column}" }
sql_query = sql_query.join(" OR ")
values_by_column_name = values_by_column_name.transform_values { |value| "%#{value}%" }.symbolize_keys
relation = Contact.where(sql_query, values_by_column_name)
relation = relation.tagged_with(tag_list) if tag_list
relation
end
in your controller:
ADVANCED_SEARCH_FIELDS = [
:contact_type,
:prospect_strength,
:customer_account_id,
:supplier_account_id,
:company_name,
:name,
:title,
:postcode,
:created_at,
:updated_at,
{tag_list: []},
:assigned_to,
:obsolete,
]
def index
#per_page = params[:per_page] || 20
#tags = ActsAsTaggableOn::Tag.all
if advanced_search?
#contacts = Contact.advanced_search(advanced_search_params)
elsif normal_search?
#contacts = Contact.search(params[:qs], params[:search])
else
#contacts = Contact.all
end
#contacts = #contacts.order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
end
def advanced_search?
params[:search_type] == "advanced" && advanced_search_params.any?
end
def normal_search?
params[:search_type] == "normal" && params[:search].present?
end
def advanced_search_params
#_advanced_search_params ||= params.permit(ADVANCED_SEARCH_FIELDS).select { |_, v| v.present? }.to_h
end
Hello I have an error about Rails Elasticsearch with searchkick gem
the error
Searchkick::InvalidQueryError ([400] {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"Expected numeric type on field [item_final_price], but got [keyword]"}],"type":"search_phase_execution_exception","reason":"all shards failed","phase":"query","grouped":true,"failed_shards":[{"shard":0,"index":"items_development_20180507023923980","node":"w7LqCILTTK-gndnoQB1cOQ","reason":{"type":"illegal_argument_exception","reason":"Expected numeric type on field [item_final_price], but got [keyword]"}}]},"status":400}):
Basically telling me the error is expecting numeric type but the type is keywords
heres my search params
in controller.rb
def results
price_ranges = [{to: params[:max_price]}, {from: params[:min_price], to: params[:max_price]}, {from: params[:min_price]}]
#results = Item.search(params[:q], aggs: {item_final_price: {ranges: price_ranges}}, page: params[:page], per_page: 10) if params[:q].present?
end
my views
<%= form_tag results_path, method: :get, enforce_utf8: false, id: "q_filter" do %>
<section class="widget widget-categories">
<%= hidden_field_tag :q, params[:q] %>
<h3 class="widget-title">Price Range</h3>
<div class="form-group">
<label>Price Between</label>
<%= number_field_tag :min_price, params[:min_price].to_f, class: "form-control form-control-sm", placeholder: "Min Price" %>
</div>
<div class="form-group">
<label>And</label>
<%= number_field_tag :max_price, params[:max_price].to_f, class: "form-control form-control-sm", placeholder: "Max Price" %>
</div>
<%= button_tag(type: "submit", name: nil, class: "btn btn-outline-primary btn-sm btn-block") do %>
Filter Search
<% end %>
</section>
and my model
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
Item.import
searchkick callbacks: :async
def search_data
{
item_name: item_name,
item_details: item_details,
item_final_price: item_final_price
}
end
I found my Answer. What I did is change the column datatype from string to float.
I have an application that has hundreds of products. I created a simple filter search bar and it works great for a single parameter, but I would like to add multiple parameters and return any product that contains one of the search parameters.
Here is my working code for a single parameter; I would show what I've tried to add multiple params, but I am a bit out of my depth.
products_controller.rb
def index
#products = Product.all.order('LOWER(name)')
if params[:q]
#products = Product.where('name ILIKE ?', "%#{params[:q]}%").all.order('LOWER(name)')
end
end
_searchbar.html.erb
<div class="form-inline">
<%= form_tag(products_path, :method => "get", id: 'search-form', :html => {class: 'form'}) do %>
<div class="form-group">
<%= text_field_tag :q, params[:q], placeholder: 'Product Name', class: 'form-control' %>
</div>
<%= submit_tag 'Search', class: 'btn btn-primary' %>
<% end %>
</div>
We should know the operation behind. The sql query must be like this:
select * from products where name ilike '%name1%' or name ilike '%name2%';
According you code:
select * from products where name ilike '%name1,name2%'
That's incorrect.
Try to the following:
# :query need to be an array
# Eg:
# "Product name 1, Product name 2, Product name 3" => params[:q].split(', ')
# => ["Product name 1", "Product name 3", "Product name 3"]
search_string = []
query.each { |q| search_string << "name ILIKE ?" }
search_string = search_string.join(' OR ')
Product.where(search_string, *query)
You can do that using following steps like if you need to use input field then form look like this
<div class="form-inline">
<%= form_tag(products_path, :method => "get", id: 'search-form', :html => {class: 'form'}) do %>
<div class="form-group">
<%= text_field_tag :x, params[:x], placeholder: 'Product Name1', class: 'form-control' %>
</div>
<div class="form-group">
<%= text_field_tag :y, params[:y], placeholder: 'Product Name2', class: 'form-control' %>
</div>
<div class="form-group">
<%= text_field_tag :z, params[:z], placeholder: 'Product Name3', class: 'form-control' %>
</div>
<%= submit_tag 'Search', class: 'btn btn-primary' %>
<% end %>
</div>
Or you need this filtering key is in the checkbox then form look like this
<%= form_tag(products_path, :method => "get", id: 'search-form', :html => {class: 'form'}) do %>
<%= check_box_tag("x[]", "x") %> Xname
<%= check_box_tag("y[]", "y") %> Yname
<%= check_box_tag("z[]", "z") %> Zname
<%= submit_tag 'Search', class: 'btn btn-primary' %>
<% end %>
Now update the controller look like this
if params[:x] || params[:y] || params[:z]
#products = Product.where('true')
#products = #products.where('name ILIKE ?', "%#{params[:x]}%") unless params[:x].blank?
#products = #products.where('name ILIKE ?', "%#{params[:y]}%") unless params[:y].blank?
#products = #products.where('name ILIKE ?', "%#{params[:z]}%") unless params[:z].blank?
#products = #products.order('LOWER(name)')
else
#products = Product.all.order('LOWER(name)')
end
Hope it helps
I am writing a custom FormBuilder in Rails and I get the above error at "around line 14" (<%= f.text_field :name, autofocus: true %>) of the view.
My FormBuilder:
class FoundationFormBuilder < ActionView::Helpers::FormBuilder
delegate :content_tag, to: :#template
delegate :label_tag, to: :#template
def text_field(method, options = {})
options[:label] ||= "#{method.to_s}".humanize
options[:class] ||= ""
field_errors = object.errors[method].join(', ') unless object.errors[method].blank?
error_class = "error" if field_errors
error_class ||= ""
label_tag("#{#object_name}[#{method}]", "#{options[:label]}", class: error_class) do
label << #template.send(text_field_tag("#{#object_name}[#{method}]", nil, class: "error_class #{options[:class]}"))
# label << (content_tag(:small, field_errors.humanize, class: error_class)) if field_errors
label.html_safe
end
end
end
In my view:
<%= form_for(#message, url: contact_us_path, builder: FoundationFormBuilder) do |f| %>
<!--<%= f.label :name %>-->
<%= f.text_field :name, autofocus: true %>
<!--<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :comment %>
<%= f.text_area :comment, rows: 10 %>
<%= f.submit "Send", class: "button round right" %>-->
<% end %>
Here is the application trace:
app/helpers/foundation_form_builder.rb:13:in `block in text_field'
app/helpers/foundation_form_builder.rb:3:in `label_tag'
app/helpers/foundation_form_builder.rb:12:in `text_field'
app/views/contact_us/new.html.erb:14:in `block in _app_views_contact_us_new_html_erb___968573049544088412_70132699754860'
app/views/contact_us/new.html.erb:12:in `_app_views_contact_us_new_html_erb___968573049544088412_70132699754860'
As far as I can tell I am passing the arguments to the label_tag since I presume this is what is causing the error. What am I actually doing wrong?
Update:
I think it has to do with the method beign passed as text_field to a label, but I'm not sure. Still scratching my head.
The issue was trying to append to label when I had not initialised it previously.
What I wanted to achieve was the following:
<form>
<div class="row">
<div class="large-6 columns">
<label class="error">Error
<input type="text" class="error" />
</label>
<small class="error">Invalid entry</small>
</div>
<div class="large-6 columns error">
<label>Another Error
<input type="text" />
</label>
<small class="error">Invalid entry</small>
</div>
</div>
<textarea class="error" placeholder="Message..."></textarea>
<small class="error">Invalid entry</small>
</form>
As seen here Zurb - Foundation 5 Forms
Which has been done as follows:
FormBuilder:
class FoundationFormBuilder < ActionView::Helpers::FormBuilder
delegate :content_tag, to: :#template
delegate :label_tag, to: :#template
def text_field(method, options = {})
options[:label] ||= "#{method.to_s}".humanize
options[:class] ||= ""
field_errors = object.errors[method].join(', ') unless object.errors[method].blank?
options[:class] << "error" if field_errors
options = objectify_options(options)
options.delete(:object)
label = lambda do
label_tag("#{#object_name}[#{method}]", "#{options[:label]}", class: "#{'error' if field_errors}") do
label = "#{options[:label]}"
label << #template.send('text_field_tag', "#{#object_name}[#{method}]", nil, options)
label.html_safe
end
end
error_messages = lambda do
content_tag(:small, field_errors.humanize, class: "error") if field_errors
end
"#{label.call} #{error_messages.call}".html_safe
end
def text_area(method, options = {})
options[:label] ||= "#{method.to_s}".humanize
options[:class] ||= ""
field_errors = object.errors[method].join(', ') unless object.errors[method].blank?
options[:class] << "error" if field_errors
options = objectify_options(options)
options.delete(:object)
label = lambda do
label_tag("#{#object_name}[#{method}]", "#{options[:label]}", class: "#{'error' if field_errors}") do
label = "#{options[:label]}"
label << #template.send('text_area_tag', "#{#object_name}[#{method}]", nil, options)
label.html_safe
end
end
error_messages = lambda do
content_tag(:small, field_errors.humanize, class: "error") if field_errors
end
"#{label.call} #{error_messages.call}".html_safe
end
end
View:
<%= form_for(#message, url: contact_us_path, builder: FoundationFormBuilder) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :email %>
<%= f.text_area :comment, rows: 10 %>
<%= f.submit "Send", class: "button round right" %>
<% end %>
It has achieved the result, but is this a safe way to achieve what I wanted?
I have been trying to setup pagination properly but I am having trouble following any tutorial and getting it to work properly.
What I basically want setup is that when a user chooses the number of companies they want to appear on a page, the following loop with paginate the given number of companies on each page. If the user doesn't choose a specific number of companies/page, there would be a default value that would be set.
If anyone could maybe give me a push in the right direction, that would be much appreciated.
View
<ul class="list-group">
<%= #comps.find_each do |comp| %>
<li class="list-group-item">
<div class="row">
<div class="span12">
<p style="font-size: 1.5em"><strong><%= link_to comp.name, comp %></strong></p>
<hr class="divider">
<div class="row">
<div class="span6">
<p><strong>Primary Field:</strong> <%= comp.primary_field %></p>
<p><strong>Description:</strong> <%= truncate(comp.description, length: 150) { link_to "Read More", comp, :target => "_blank"}%>
</p>
</div>
<div class="span5">
<% if signed_in? %>
<p><% if !(current_user.favorites.include? comp) %>
<%= link_to 'Favorite',
favorite_company_path(comp, type: "favorite"),
class: "btn btn-primary",
method: :put%>
<% else %>
<%= link_to 'Unfavorite',
favorite_company_path(comp, type: "unfavorite"),
class: "btn btn-danger",
method: :put%>
<% end %></p>
<% end %>
<p><strong>Website:</strong> <%= auto_link(comp.website, :html => { :target => '_blank' })%></p>
<p><strong>Location:</strong> <%= comp.address %></p>
</div>
</div>
</div>
</div>
</li>
<% end %>
<%= will_paginate #comps%>
</ul>
Controller (Where the list page is handled)
def filter
if ((params[:field] != "") and (params[:field] != "All Fields") and (params[:field] != nil))
# This will apply the filter
#comps = Company.where(primary_field: params[:field])
else
# This will return all records
#comps = Company.all
end
end
def list
#fields = ##primary_field
filter
#comps = #comps.paginate(:page => params[:page], :per_page => params[:number_of_companies] ||= 5)
end
Model for the list page
class DynamicPages < ActiveRecord::Base
self.per_page = 5
end
Since you are using will_paginate. You could do in this way.
Post.paginate(:page => params[:page], :per_page => params[:number_of_companies] ||= 10)
Here params[:number_of_companies] is the number of companies a user has selected and 10 is the
default page value.
class Post
self.per_page = 10
end
Also, your code snippet <%= #comps.find_each do |comp| %>, should be this <% %> instead of <%= %>