I have the following search query for products using searchkick:
def index
#filter = params[:filter].blank? ? nil : Search.find(params[:filter])
if #filter.present?
#products = #filter.products(set_order_column(#sort), #direction, params[:page], #per_page)
else
query = #query.presence || "*"
#products = Product.search(
query,
where: {
active: true
},
page: params[:page],
per_page: #per_page,
order: [{
"#{set_order_column(#sort)}" => "#{#direction}"
}],
misspellings: { distance: 2 },
fields: fields)
end
#filter_product_ids = #products.map(&:id)
end
There is the variable filter_product_ids where I need to store all results not filtered results by #per_page. Is it possible to do that? Right now, there are results just for results #per_page.
The goal to get all the results without pagination is to get uniq values for all products used to various product categorization for filtering on the website.
Thank you for any hints, Miroslav
My solution is to put this in it's own class (service class), then
class ProductFilter
attr_reader :search_params, :search_query, :pagination_params
def initialize(search_params, search_query, pagination_params)
#search_params = search_params
#search_query = search_query
#pagination_params = pagination_params
end
# Will return all records from searchkick, unpaginated
def filtered_products
Product.search(search_query, search_param_stuff)
end
# Will return all filtered_products & then paginate on that object
def paginated_products
filtered_products.records.paginate(pagination_params)
end
private
def search_param_stuff
search_params
end
end
In any case, searchkick's method for pagination actually works on the searchkick records object, so you can just pull the ES query (without passing in pagination params) in one method, then paginate on the filtered_products.records.
Just remove
params[:page], #per_page
and
page: params[:page],
per_page: #per_page,
from your code.
Related
Given that each project has_many :tasks, I hope to render the project.task within the json result.
However, the json output also include a list of individual tasks as part of the result. See below:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
#all = #tasks + #projects
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
per_page: 25 }
end
This means that if I simply include:
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
:include => [:tasks => {:only => :id}],
per_page: 25 }
end
Rails will throw an error of undefined method tasks for Task:0x007fa0ad8d3858 since tasks does not have a task method.
How can I have the project.tasks appear in a json result which also include individual tasks result? Thank you.
Consider using active_model_serializers gem. After installing you can define a serializer for Project model like so:
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :created_at, :tasks
def tasks
object.tasks.map(&:id)
end
end
Note: There might be any attributes you need. It's just an example.
Then you can do:
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
It will return you an array:
[{:id => 1, :created_at => "2017-07-13 08:13:20", tasks => [1, 2, 3, ...]}, ...]
Then for json response you can concat #tasks and serialized_projects:
all_for_json = #tasks + serialized_projects
And finally you can sort it like this:
all_for_json.sort_by { |record| record[:created_at] }.reverse
Note that you should do exactly record[:created_at], because projects are hashes, not active record models.
But I don't think this is a good idea to mix hashes and active record models in one array. So there is another solution.
You can also define a serializer for Task model:
class TaskSerializer < ActiveModel::Serializer
attributes :id, :created_at
end
Note: There might be any attributes you need. It's just an example.
And override code like this:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
respond_to do |format|
format.html do
#all = #tasks + #projects
end
format.json do
serialized_tasks = ActiveModelSerializers::SerializableResource.new(#tasks, each_serializer: TaskSerializer).as_json
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
all_serialized = serialized_tasks + serialized_projects
paginate json: all_serialized.sort_by { |record| record[:created_at] }.reverse, per_page: 25
end
end
To DRY your code, you can put
ActiveModelSerializers::SerializableResource.new(...).as_json
to separate method. For example:
def serialize_collection(collection, each_serializer)
ActiveModelSerializers::SerializableResource.new(collection, each_serializer: each_serializer).as_json
end
And do serializations like this:
serialized_tasks = serialize_collection(#tasks, TaskSerializer)
serialized_projects = serialize_collection(#projects, ProjectSerializer)
Profits of this solution:
You don't mix active record models and hashes in one array.
You can easily define via serializers which attributes and associations to include and set custom names for them.
I have a Rails 5 app and I'm trying to do an aggregations search for a has_and_belongs_to_many association.
Here is the code that I have so far:
event.rb:
class Event < ApplicationRecord
searchkick text_start: [:title]
has_and_belongs_to_many :services
has_and_belongs_to_many :sectors
def search_data
atributes.merge(
title: title,
description: description,
sector_name: sectors.map(&:name),
service_name: services.map(&:name)
)
end
end
events_controller.rb:
def index
query = params[:j].presence || "*"
conditions = {}
conditions[:sector] = params[:sector] if params[:sector].present?
conditions[:service] = params[:service] if params[:service].present?
conditions[:date] = params[:date] if params[:date].present?
#events = Event.search query, where: conditions, aggs: [:sector, :service, :date], order: {created_at: {order: "desc"}}, page: params[:page], per_page: 10
end
When I call Event.reindex in the console I was expecting to to show that the sectors and services had been indexed but it doesn't work.
To be honest I'm getting quite lost and going round in circles so any help would be much appreciated.
This is the code that ended up working for me:
event.rb:
def index
query = params[:j].presence || "*"
conditions = {start_date: {"gte": "now/d"}}
conditions[:sector_name] = params[:sector_name] if params[:sector_name].present?
conditions[:service_name] = params[:service_name] if params[:service_name].present?
conditions[:start_date] = params[:start_date] if params[:start_date].present?
#events = Event.search query, where: conditions, aggs: [:sector_name, :service_name], order: {start_date: {order: "asc", unmapped_type: "long"}}, page: params[:page], per_page: 10
end
events_controller.rb:
def search_data
{
title: title,
location: location,
description: description,
start_date: start_date,
sector_name: sectors.map(&:name),
service_name: services.map(&:name)
}
end
I have this query:
#cars = Car.paginate(page: params[:page], per_page: 5).order(created_at: :desc).group_by { |r| r.created_at.to_date }
and in the view:
= will_paginate #cars
But I am getting this error (rendered in site#index):
The #site variable appears to be empty. Did you forget to pass the collection object for will_paginate?
I've also tried to update the query this way:
#cars = Car.order(created_at: :desc).paginate(page: params[:page], per_page: 5).group_by { |r| r.created_at.to_date }
But the error is still the same.
How to fix this issue?
Thank you
You can do something like this in your controller:
#car_paginator = Car.paginate(page: params[:page], per_page: 5).order(created_at: :desc)
#cars = #car_paginator.group_by { |r| r.created_at.to_date }
Then change the line with the pagination in your view:
= will_paginate #car_paginator
The problem is: When you use the paginate method the results is not a normal array or database relation but an object that has some extra information like #size. If you run group_by on that you remove that extra attributes and return a plain hash.
i am trying to delete from list but when i am trying this it is getting deleted from database
#course = Course.find(params[:id])
#search = Lesson.search(params[:q])
#lessons = #search.result.paginate(:page => params[:page], :per_page => 10)
#search.build_condition if #search.conditions.empty?
#course.lessons.each do |lesson|
#lessons.each do |l|
if lesson.id == l.id
#lessons.delete(l)
end
end
end
I am getting this error: delete_all doesn't support limit scope
Thanking you
Delete is an ActiveRecord method. I assume you don't want to delete it from the database but from the result list. You can do it like this:
#course.lessons.each do |lesson|
#lesson.reject { |l| l.id == lesson.id }
end
I have a City model and in city's show action I want to render hotels nearby specific locations in the city. Cities has_many locations; hotels are being searched using Geocoder near method.
To add order functionality I've followed Ryan Bates screencasts #228, but this approach doesn't seem to work with arrays, giving error undefined method `order' for #< Array:0x007f960d003430>
cities_controller.rb
helper_method :sort_column, :sort_direction
def show
session[:search_radius] = 2 if session[:search_radius].blank?
#city = City.find(params[:id])
#locations = #city.locations
#hotels = []
#locations.each do |location|
unless location.longitude.blank? || location.latitude.blank?
center_point = [location.latitude, location.longitude]
box = Geocoder::Calculations.bounding_box(center_point, session[:search_radius])
thotels = Hotel.near(center_point, session[:search_radius]).within_bounding_box(box)
else
thotels = Hotel.near(center_point, session[:search_radius])
end
#hotels += thotels if thotels
#hotels = #hotels.uniq
end
#hotels = #hotels.order(sort_column + " " + sort_direction).paginate(:page => params[:page], :per_page => 5)
#json = #locations.to_gmaps4rails
respond_with #json, :location => city_url
end
private
def sort_column
Hotel.column_names.include?(params[:sort]) ? params[:sort] : "name"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
My question is: should I concentrate in converting an array into hash or should I initially create hash of hotels, or maybe find completely different approach to perform sorting?
order is a method used for sorting at the database level. since #hotels is an array, you won't be able to sort using order. Try the following (not tested and you may want to include array pagination if you haven't included it yet)
#hotels = #hotels.sort_by(&:"#{sort_column}")
#hotels = #hotels.reverse if sort_direction == 'DESC'
#hotels = #hotels.paginate(:page => params[:page], :per_page => 5)