searchkick requests together with active record requests rails - ruby-on-rails

I have a trouble. For example I have a model Article, and my search_data method below
def search_data
title: title,
body: body
end
And I need to receive some records from my controller according to some attributes, for example:
def index
#articles = Article.where(user_id: user_id).search(query)
end
But this approach received all data whos according to query in search method ignoring where method, while I need to search by search method among data received by where method. How to resolve this?
Update
Desirely to use where before search method, not the inside it

It appears SearchKick doesn't allow you to chain a search onto an ActiveRecord Relation like that. It uses ElasticSearch to search instead of your database so if you did that you would be querying two databases for the information. However, it does provide a where option for the search method:
Article.search(query, where: {user_id: user_id})
If you absolutely have to make the ActiveRecord query first, you could get the IDs from the first query and provide them to the second.
ids = Article.where(user_id: user_id)
Article.search(query, where: {id: ids})

The key here tis to make sure that every attribute in your where query also exist in your search_data method. Example:
Message.search search_term, where: {or: [[{user_id: current_user.id}, {recipient_id: current_user.id}]]}
I am looking to find messages with the search_term where the current user is either sender or recipient.
Therefore in my search_data method (in model.rb) I should have:
def search_data
{ user_id: user_id,
recipient_id: recipient_id,
title: title,
description: description,
}
And this works well. this is using Searchkick gem.

Related

Searchkick joins between models

Using searchkick gem how would I translate this to the .search way? Or is it not doable?
#projects = Project.joins(:proj_status)
Just wasn't sure how to do joins.
eager loading example from gem reference
Product.search "milk", includes: [:brand, :stores]
for your code above
#projects = Product.search "milk", includes: [:proj_status]
If you want to search for projects with status then you might need to do in the following way
in your project model where searchkick included try this
this is for indexing data
I assume Project has name and id and its associated model proj_status has a title.You may change the values depend on your model attributes
def search_data
id: id,
name: name,
status: proj_status.title
end
After Project.reindex
Then you can query Project model with status like,
#projects = Product.search "you query if any", includes: [:proj_status], where: {status: 'open'}

Activeadmin: how to filter for strings that match two or more search terms

Let's say I've got User class with an :email field. And let's say I'm using activeadmin to manage Users.
Making a filter that returns emails that match one string, e.g. "smith", is very simple. In admin/user.rb, I just include the line
filter :email
This gives me a filter widget that does the job.
However, this filter doesn't let me search for the intersection of multiple terms. I can search for emails containing "smith", but not for emails containing both "smith" AND ".edu".
Google tells me that activerecord uses Ransack under the hood, and the Ransack demo has an 'advanced' mode that permits multiple term searches.
What's the easiest way to get a multiple term search widget into activeadmin?
Ideally, I'd like a widget that would allow me to enter smith .edu or smith AND .edu to filter for emails containing both terms.
there is simple solution using ranasckable scopes
So put something like this in your model
class User < ActiveRecord::Base
....
scope :email_includes, ->(search) {
current_scope = self
search.split.uniq.each do |word|
current_scope = current_scope.where('user.email ILIKE ?', "%#{word}%")
end
current_scope
}
def self.ransackable_scopes(auth_object = nil)
[ :email_includes]
end
end
After this you can add filter with AA DSL
Like
filter :email_includes, as: :string, label: "Email"
UPD
should work if change email_contains_any to email_includes
I've figured out a solution but it's not pretty.
The good news is that Ransack has no trouble with multiple terms searches. These searches use the 'predicate' cont_all. The following line works for finding emails containing 'smith' and '.edu'.
User.ransack(email_cont_all: ['smith','.edu'] ).result
Since these searches are easy in Ransack, they're probably straightforward in Activeadmin, right? Wrong! To get them working, I needed to do three things.
I put a custom ransack method (a.k.a. ransacker) into User.rb. I named the ransacker email_multiple_terms.
class User < ActiveRecord::Base
# ...
ransacker :email_multiple_terms do |parent|
parent.table[:path]
end
I declared a filter in my activeadmin dashboard, and associated it with the ransacker. Note that the search predicate cont_all is appended to the ransacker name.
admin/User.rb:
ActiveAdmin.register User do
# ...
filter :email_multiple_terms_cont_all, label: "Email", as: :string
This line creates the filter widget in Activeadmin. We're nearly there. One problem left: Activeadmin sends search queries to ransack as a single string (e.g. "smith .edu"), whereas our ransacker wants the search terms as an array. Somewhere, we need to convert the single string into an array of search terms.
I modified activeadmin to split the search string under certain conditions. The logic is in a method that I added to lib/active_admin/resource_controller/data_access.rb.
def split_search_params(params)
params.keys.each do |key|
if key.ends_with? "_any" or key.ends_with? "_all"
params[key] = params[key].split # turn into array
end
end
params
end
I then called this method inside apply_filtering.
def apply_filtering(chain)
#search = chain.ransack split_search_params clean_search_params params[:q]
#search.result
end
This code is live in my own fork of activeadmin, here: https://github.com/d-H-/activeadmin
So, to get multiple term search working, follow steps 1 and 2 above, and include my fork of A.A. in your Gemfile:
gem 'activeadmin', :git => 'git://github.com/d-H-/activeadmin.git'
HTH.
If anyone's got a simpler method, please share!
Just add three filters to your model:
filter :email_cont
filter :email_start
filter :email_end
It gives you a flexible way to manage your search.
This filter executes next sql code:
SELECT "admin_users".* FROM "admin_users"
WHERE ("admin_users"."email" ILIKE '%smith%' AND
"admin_users"."email" ILIKE '%\.edu')
ORDER BY "admin_users"."id" desc LIMIT 30 OFFSET 0
I expect that exactly what you're looking for.

active_admin config.sort for two columns

I have an active_admin table called shows that acts of a list of rsvps for bike riders and bike shows that the riders will compete in. The following code correctly sorts the table alphabetically by rider_last_name:
config.sort_order = 'rider_last_name_asc'
Now when a rider is attending multiple bike shows, I want the table to first sort by rider_last_name and then within that rider sort by an attribute of shows called start_time. start_time is a DateTime. According to this stackoverflow article, the following should work:
config.sort_order = 'rider_last_name_asc, start_time_asc'
but it doesn't. In fact, it undoes the sorting by rider_last_name. How do I sort by both columns?
You may try refine apply_sorting method for collections, like this:
controller do
def apply_sorting(chain)
params[:order] ? chain : chain.reorder(rider_last_name: :asc, start_time: :asc)
end
end
ActiveAdmin suggests overwrite the find_collection method, which returns an ActiveRecord::Relation. But I prefer add the order to it's output.
ActiveAdmin.register Show do
controller do
def find_collection(options = {})
super.reorder(rider_last_name: :asc, start_time: :asc)
end
end
...
end
Although it works, this overwrite the user option of click on a column.
Alternative
You can do the same with scoped_collection, which is called at the start of find_collection, but it does not work unless you add active_admin_config.sort_order = '' to the controller. This way:
controller do
active_admin_config.sort_order = ''
def scoped_collection
super.reorder(rider_last_name: :asc, start_time: :asc)
end
end
Now, if we want to reorder before and, after that take care of the user params (and do not overwrite them). This is the way.
Note: I did use active_admin_config.sort_order and not config.sort_order.
Also, the sort_order option, ends as a param of OrderClause.new call, which expect for only one sort field, see here and here.
you can try to pass an array to it as such:
config.sort_order = [:rider_last_name_asc, :start_time_asc]

ElasticSearch rails : order results by object's associated data

I have implemented Searchkick in a rails app and it is working fine. I want to order my results based on the number of associated objects.
For Eg. If I am searching for users by their name, then I want to order the results by the number of followers. (User having most followers should come first)
#users = User.search "2% #{query}", include: [:followers], fields: [:name]
Thanks for your help in advance
I think you need to add counter cache (http://guides.rubyonrails.org/association_basics.html 4.1.2.3) and use followers_count column on User model to order it.
User.search "2% #{query}", include: [:followers],
fields: [:name], order: followers_count

Ruby Gem ActiveRecord find method with multiple conditions

I'm building a Sinatra application using three database's tables: user, post and like.
I'd want to run a query that will find an entry in the like table like so:
FIND in like WHERE user_id == params[:user_id] AND post_id == params[:post_id]
(for one condition I'll be using: Like.find_by_user_id(params[:user_id]))
My question is:
How to run a find query with multiple conditions using the ActiveRecord Gem?
Use where:
Like.where('user_id = ? AND post_id = ?', params[:user_id], params[:post_id])
or
Like.where('user_id = :user_id AND post_id = :post_id', params)
Is important to keep in mind that the paremeters of the where need to be converted to the expected type for example params[:post_id].to_i
Similar to find_by_user_id for user_id column you can combine multiple column names and get a dynamic finder find_by_user_id_and_post_id:
Like.find_by_user_id_and_post_id(params[:user_id], params[:post_id])
When there are more than "bearable" columns in the find_by_ finder, you could use where and supply the condition as follows:
Like.where(user_id: params[:user_id], post_id: params[:post_id])
Like.find_by_user_id(params[:user_id]) - this syntax is deprecated in ActiveRecord 4.
Instead try using where method of ActiveRecord query interface, to pass array conditions. Example:
Like.where("user_id = ? AND post_id = ?", params[:user_id], params[:post_id])
If you are expecting one record to be the result:
To replace find_by_whatever you can use find_by(whatever) for example User.find_by(username:"UsernameIsMyUsername",password:"Mypassword"). You should use find_by if there is only one record that you expect to match your search.
If you are expecting more than one record to be the result:
If you expect more than one you should use where with where(username:"MyUsername",password:"Password"). This will return all the resulting records in an array.

Resources