OR query with Tire / Elastic - ruby-on-rails-3

This is probably very simple, but I can't work it out. I have this tire/elasicsearch query on Project class:
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 8) do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { term :status, "posted" }
must { term :budget, params[:budget] } if params[:budget].present?
end
end
end
end
I need the Status term to be searching on 'posted' OR 'closed' and can't work this out. I tried term :status, ["posted", "closed"] but this doesn't return results so probably doing an AND.
Be grateful for the right tire syntax here. (NB not looking for the jason/elasticsearch string here)
Many thanks!

What you need is a terms query, which does the OR between terms passed as an array:
must { terms :status, ["posted"] }
or something like this for dynamic values:
# params[:status] can be single value or array
must { terms :status, Array(params[:status]) }
See this part of Tire documentation for more info and context.

Related

ActiveRecord: How to order and retrieve records in a greatest-n-per-group situation

I'm stuck with a classic greatest-n-per-group problem, where a cat can have many kittens, but I'm usually just interested in the youngest.
I already do know how to build a scope and a has_one relation for the Cat.
My question: Is there a way to...
list all cats' names together with their youngest kittens' names...
while at the same time ordering them by their respective youngest kitten's name...
...using just a single SELECT under the hood?
What I got so far:
class Cat < ApplicationRecord
has_many :kittens
has_one :youngest_kitten, -> { merge(Kitten.youngest) }, foreign_key: :cat_id, class_name: :Kitten
scope :with_youngest_kittens, lambda {
joins(:kittens)
.joins(Kitten.younger_kittens_sql("cats.id"))
.where(younger_kittens: { id: nil })
}
end
class Kitten
belongs_to :cat
scope :youngest, lambda {
joins(Kitten.younger_kittens_sql("kittens.cat_id"))
.where(younger_kittens: { id: nil })
}
def self.younger_kittens_sql(cat_field_name)
%{
LEFT OUTER JOIN kittens AS younger_kittens
ON younger_kittens.cat_id = #{cat_field_name}
AND younger_kittens.created_at > kittens.created_at
}
end
end
When I run Cat.with_latest_kittens.order('kittens.name').map(&:name) everything looks fine: I get all the cats' names with just a single SELECT.
But when I run Cat.with_latest_kittens.order('kittens.name').map {|cat| cat.youngest_kitten.name}, I get the right result too, but a superfluous additional SELECT per cat is executed. Which is just logical, because the with_youngest_kittens doesn't know it should populate youngest_kitten. Is there a way to tell it or am I going about this all wrong?
I think adding an includes to your :with_youngest_kittens scope will fix the problem. Try changing the scope to
scope :with_youngest_kittens, lambda {
includes(:youngest_kitten)
.joins(:kittens)
.joins(Kitten.younger_kittens_sql("cats.id"))
.where(younger_kittens: { id: nil })
}
This should prevent Rails from making a separate database query for every kitten.
I found a solution that produces no extra SELECT, however it is quite ugly, so I'll actually go for localarrow's solution as it's more readable!
I thought I'd still post it for the sake of completeness (If someone needs the few ms extra performance):
First I add custom tailored select fields for each kitten column to the Cat.with_youngest_kitten scope:
scope :with_youngest_kittens, lambda {
kitten_columns = Kitten
.column_names
.map { |column_name| "kittens.#{column_name} AS `youngest_kittens.#{column_name}`" }
.join(', ')
joins(:kittens)
.joins(Kitten.latest_outer_join_sql("cats.id"))
.where(later_kittens: { id: nil })
.select("cats.*, #{kitten_columns}")
}
Then I override the has_one youngest_kitten relation with a method, that retrieves those custom selects and calls super if no data has been retrieved:
def youngest_kitten
return super if self[:'youngest_kittens.id'].nil?
kitten_hash = Hash[Kitten.column_names.collect { |column_name| [column_name, self[:"youngest_kittens.#{column_name}"]] }]
kitten_hash[:cat] = self
Kitten.new(kitten_hash)
end

Argument error in model scope

I'm trying to refactor the Companies_Controller#index method to encompass less logic by moving most of the query into a scope, company_search_params.
What is the best way to pass the param to the model scope? I'm getting an error thrown back, wrong number of arguments (given 0, expected 1). I'm relatively new to writing scopes and couldn't find much on passing arguments/conditions that was applicable in the Rails Guide.
Companies Controller
def index
params[:name_contains] ||= ''
Company.company_search_params(params[:name_contains])
#search = Company.company_search_params
#companies = #search.page(params[:page])
end
Company Model
scope :company_search_params, ->(name_contains){
where(
<<-SQL
"name LIKE :match OR subdomain LIKE :match", { match: "%#{name_contains}%" }
SQL
).where(is_archived: false).order(name: :asc)
}
Thanks for your help.
using named_scope sample and info
scope :named_scope, lambda {
|variable1, variable2|
where...
order...
}
#when you call it from your controller
Model.named_scope("value 1","value 2")
for your problem
in your company.rb
scope :company_search_params, lambda {
|name_contains|
where(
<<-SQL
"name LIKE :match OR subdomain LIKE :match", { match: "%#{name_contains}%" }
SQL
).where(is_archived: false).order(name: :asc)
}
company_controller.rb
def index
#search = Company.company_search_params(params[:name_contains])
#companies = #search.page(params[:page])
end

Rails: Search by custom instance method's value using tire gem & elasticsearch

For example, I have Article model like
class Article < ActiveRecord::Base
#Columns: id, title, status_number...etc
STATUSES = {1 => "SUCCESS", 2 => "REJECTED"}
include Tire::Model::Search
include Tire::Model::Callbacks
def display_status
STATUSES[status_number]
end
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 2) do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
end
end
end
end
how to include display_status as "SUCCESS" by default in search method?
I tried
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { term :display_status , "SUCCESS" }
end
end
But couldn't get result.
Please help to solve this problem. Thanks

Nested select in rails (SQL to Rails conversion)

I have this rails logic that uses partial SQL query code. I was wondering if there was a way a better way or a cleaner way to do the same thing (i.e. use rails's methods to replace the SQL code)?
#servers = Server
.select("*", "(SELECT AVG('reviews'.'average') FROM 'reviews' WHERE 'reviews'.'server_id' = 'servers'.'id') AS s_avg")
.order("s_avg DESC")
.paginate(:page => params[:page], :per_page => 25)
First good thing is to move that code from view or controller to model and wrap it in scope. Moreover, scopes can be chained.
class Server < ActiveRecord::Base
scope :averaged, -> { where(SQL CODE HERE) }
scope :expensive, -> { where('price > ?', price) }
scope :latest, -> { where('created_at > ?', Date.today - 3.days.ago) }
scope :active, -> { where(active: true) }
end
Only then you can pass and chain it in controller:
#servers = Server.latest.averaged
So, simply try to brake your SQL on several parts, move these parts to model and wrap them with scopes.
You can find a lot of useful examples of query methods without pure SQL here:
http://guides.rubyonrails.org/active_record_querying.html

Rails: finding matches that start with a string and do not end in another

I've got this little thing here:
def get_articles
#articles = []
Doc.column_names.each do |a|
if a.match(/^article/)
#articles << a
end
end
end
But it returns a lot of unwanted results. How would I go about discarding results it returns that end in a specific string (say, _body)?
Cheers!
How about:
if a.match(/^article/) and !a.match(/_body$/)
Incidentally, your method can be rewritten (more compactly) as:
def get_articles
#articles = Doc.column_names.select { |a| a.match(/^article/) && !a.match(/_body$/) }
end
You can also replace the dual match with a single match containing a zero-width negative look-behind assertion, but it is less readable for the majority of people (though about 2x as fast in a quick-and-dirty test):
def get_articles
#articles = Doc.column_names.select { |a| a.match(/^article.*(?<!_body)$/) }
end