Using SQL with elastic search and tire - ruby-on-rails-3

With my current setup I need to add some custom SQL statement that scopes some data with the Tire Gem.
I'm using Rails 3.2 with Ruby 1.9.3
In my Listing controller, I have the following:
#listings = Listing.search()
For my Listing.rb, I'm using the search methods with a number of filters such as:
def self.search(params={})
tire.search(load: true, page: params[:page], per_page: 50) do |search|
search.query { string params[:query], :default_operator => "AND" } if params[:query].present?
search.filter :range, posted_at: {lte: DateTime.now}
search.filter :term, "property.rooms_available" => params[:property_rooms_available] if params[:property_rooms_available].present?
search.filter :term, "property.user_state" => params[:property_user_state] if params[:property_user_state].present?
...
end
What I need to do is add this SQL statement into the search method so that it scopes by the lonitude and latitute. The 'coords' are passed in by parameters in the URL in the form
http://localhost:3000/listings?coords=51.0000,-01.0000 52.0000,-02.0000
(there is a white space between the -01.0000 and 52.0000.)
Currently I have:
sql = "SELECT title, properties.id, properties.lng,properties.lat from listings WHERE ST_Within(ST_GeometryFromText('POINT(' || lat || ' ' || lng || ')'),ST_GeometryFromText('POLYGON((#{coords}))'));"
I thought about trying to scope it within the controller by something like this?
def self.polyed(coords)
joins(:property).
where("ST_Within(ST_GeometryFromText('POINT(' || properties.lat || ' ' || properties.lng || ')'),ST_GeometryFromText('POLYGON((#{coords}))'))").
select("title, properties.id, properties.lng,properties.lat")
end
And this...
listings_controller.rb
def index
#listings = Listing.polyed(poly_coordinates).search()
end
It needs to return the results as #listings by HTML and the json format
http://localhost:3000/listings.json?
I'm already using RABL to automatically produce the json.
Is this possible?
Many thanks in advance.
Ryan

One possibility is to pass the options joins, where, etc. in the :load option.
But if you want to filter the returned results in the Rails code, a much better approach seem to be loading just record IDs with Tire (use the fields option to limit the fields returned), and then use them in your SQL query.

Related

Rails ActiveRecord where clause

I want to select Cars from database with where clause looking for best DRY approach for my issue.
for example I have this two parameters
params[:car_model_id] (int)
params[:transmission_id] (int)
params[:from_date]
params[:to_date]
but I dont know which one will be null
if params[:car_model_id].nil? && !params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:transmission_id => params[:transmission_id])
else
return Car.where(:transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
elseif !params[:car_model_id].nil? && params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:car_model_id=> params[:car_model_id])
else
return Car.where(:car_model_id=> params[:car_model_id], :date => params[:from_date]..params[:to_date])
end
else
return Car.where(:car_model_id=> params[:car_model_id], :transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
what is best approach to avoid such bad code and check if parameter is nil inline(in where)
You can do:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
and then:
Car.where(car_params)
Explanation: Since, you're checking if the particular key i.e.: :car_model_id and transmission_id exists in params. The above code would be something like this when you have just :transimission_id in params:
Car.where(:transmission_id => '1')
or this when you have :car_model_id in params:
Car.where(:car_model_id => '3')
or this when you'll have both:
Car.where(:transmission_id => '1', :car_model_id => '3')
NOTE: This will work only when you have params keys as the column names for which you're trying to run queries for. If you intend to have a different key in params which doesn't match with the column name then I'd suggest you change it's key to the column name in controller itself before slice.
UPDATE: Since, OP has edited his question and introduced more if.. else conditions now. One way to go about solving that and to always keep one thing in mind is to have your user_params correct values for which you want to run your queries on the model class, here it's Car. So, in this case:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
if params[:from_date].present? && params[:from_date].present?
car_params.merge!(date: params[:from_date]..params[:to_date])
end
and then:
Car.where(car_params)
what is best approach to avoid such bad code and check if parameter is
nil inline(in where)
Good Question !
I will make implementation with two extra boolean variables (transmission_id_is_valid and
car_model_id_is_valid)
transmission_id_is_valid = params[:car_model_id].nil? && !params[:transmission_id].nil?
car_model_id_is_valid = !params[:car_model_id].nil? && params[:transmission_id].nil?
if transmission_id_is_valid
return Car.where(:transmission_id => params[:transmission_id])
elseif car_model_id_is_valid
return Car.where(:car_model_id=> params[:car_model_id])
....
end
I think now is more human readable.
First, I would change this code to Car model, and I think there is no need to check if params doesn't exists.
# using Rails 4 methods
class Car < ActiveRecord::Base
def self.find_by_transmission_id_or_model_id(trasmission_id, model_id)
if transmission_id
find_by trasmission_id: trasmission_id
elsif model_id
find_by model_id: model_id
end
end
end
In controller:
def action
car = Car.find_by_transmission_id_or_model_id params[:trasmission_id], params[:car_model_id]
end
edit:
This code is fine while you have only two parameters. For many conditional parameters, look at ransack gem.

Tire gem: Use only to generate query instead of executing the query

I am using tire gem to query elasticsearch in my database. Now, I have a model ContentElastic which connects to index posts and mapping post. I am using elasticsearch as a single database and not as a search engine over another database. So, the model ContentElastic goes like this:
class ContentElastic
include Tire::Model::Persistence
include Tire::Model::Search
include Tire::Model::Callbacks
index_name 'posts'
document_type 'post'
# fields
property :id
property :type, :type => "string"
property :username
property :name
property :datasource
property :content
property :language_id, :type => "long"
property :geotag
property :geo_enabled, :type => "boolean"
property :language_confidence, :type => "long"
property :tweet_id
...
end
Now, I have written a query like this, to get 10,000 records from the elasticsearch database:
ContentElastic.search do
fields wanted_fields if wanted_fields.present?
query do
filtered do
query { string search_query } if search_query.present?
#### Filters
range_filters.to_a.each do |rf|
filter :range, rf if rf.present?
end
not_filters.to_a.each do |nf|
filter :not, nf if nf.present?
end
or_filters.to_a.each do |of|
filter :or, of if of.present?
end
and_filters.to_a.each do |af|
filter :and, af if af.present?
end
filter :script, {:script => script} if script.present?
terms_filters.to_a.each do |tf|
filter :terms, tf if tf.present?
end
end
end
size 10000
end
The query is working fine, and we are getting the results. The problem, is that, for 10,000 records, the conversion to ruby objects takes time. Is there a way to get the elasticsearch query directly from the tire declaration and call it separately via RestClient.
Is there a way to get the elasticsearch query without executing the query in Tire. I tried to_curl method in Tire immediately after the tire search block, but it seems the query is already executed and it says Undefined method 'to_curl' for <#Tire::Collection>.
I was able to find a workaround for this. Instead of calling ContentElastic.search directly, I was able to call Tire.search like this:
tire_block = Proc.new do
fields wanted_fields if wanted_fields.present?
query do
filtered do
query { string search_query } if search_query.present?
#### Filters
range_filters.to_a.each do |rf|
filter :range, rf if rf.present?
end
not_filters.to_a.each do |nf|
filter :not, nf if nf.present?
end
or_filters.to_a.each do |of|
filter :or, of if of.present?
end
and_filters.to_a.each do |af|
filter :and, af if af.present?
end
filter :script, {:script => script} if script.present?
terms_filters.to_a.each do |tf|
filter :terms, tf if tf.present?
end
end
end
size 10000
end
t = Tire.search(ContentElastic.index.name, :type => ContentElastic.document_type, :size => limit, :from => from, &tire_block)
This tire call was not executing the query, but was giving the tire search object. Then, I was able to execute the elastic query corresponding using RestClient like this:
post_url = t.url + (t.params.empty? ? '?' : t.params.to_s)
response = RestClient.post(post_url, t.to_json.gsub("'",'\u0027'))
The response will be the json string from elasticsearch and I was able to use this json_string by parsing using JSON.parse and taking the appropriate tags.

Ruby on Rails - search in database based on a query

I have a simple form, where I set up a query that I want to browse, for example panasonic viera.
This is on how I search the term in database:
Product.where("name ilike ?", "%#{params[:q]}%").order('price')
The query looks like %panasonic viera%, but I would need to search the query this way: %panasonic%viera% - I need to find all products, where is in the title the word panasonic or viera... but how to make this query?
One solution would be to break up your query into individual terms and build a set of database queries connected by OR.
terms = params[:q].split
query = terms.map { |term| "name like '%#{term}%'" }.join(" OR ")
Product.where(query).order('price')
If you're using PostgreSQL, you can use pg_search gem. It's support full text search, with option any_word:
Setting this attribute to true will perform a search which will return all models containing any word in the search terms.
Example from pg_search:
class Number < ActiveRecord::Base
include PgSearch
pg_search_scope :search_any_word,
:against => :text,
:using => {
:tsearch => {:any_word => true}
}
pg_search_scope :search_all_words,
:against => :text
end
one = Number.create! :text => 'one'
two = Number.create! :text => 'two'
three = Number.create! :text => 'three'
Number.search_any_word('one two three') # => [one, two, three]
Number.search_all_words('one two three') # => []
How about via ARel
def self.search(query)
words = query.split(/\s+/)
table = self.arel_table
predicates = []
words.each do |word|
predicates << table[:name].matches("%#{word}%")
end
if predicates.size > 1
first = predicates.shift
conditions = Arel::Nodes::Grouping.new(predicates.inject(first) {|memo, expr| Arel::Nodes::Or.new(memo, expr)})
else
conditions = predicates.first
end
where(conditions).to_a
end
This isn't working?
WHERE name LIKE "panasonic" OR name LIKE "viera"

Search multiple colums in model rails 3

I have a search form where I can search one column in my Recipe model using
#countrysearch = Recipe.where(:dish_name => params[:search]).all
So when i search for a dish say lasagne I get a result, however i would like to be able to search another 3 columns within the recipe model, country_of_origin, difficulty and preperation_time.
I have tried this
#countrysearch = Recipe.where({:dish_name => params[:search], :country_of_origin => params[:search], :difficulty => params[:search], :preperation_time => params[:search]}).all
but this does not seem to work either
Can anyone offer a suggestion?
Your code uses AND but you want OR I think:
#countrysearch = Recipe.where("dish_name = ? OR country_of_origin = ? OR difficulty = ? OR preperation_time = ?",
params[:search],
params[:search],
params[:search],
params[:search]
)
If you don't want to use an SQL string you can use the arel_table:
at = Recipe.arel_table
search = params[:search]
#countrysearch = Recipe.where(at[:dish_name].eq(search).or(at[:country_of_origin].eq(search)).or(at[:difficulty].eq(search)).or(at[:preperation_time].eq(search)))
But for the current version of Rails I would prefere the first method because this is better readable. In Rails 5 you will have better methods for this sort of queries. (I will update this post if this becomes available.)

a Facebook "like" or Twitter "follow" system - in Rails 3

Can anyone point me in the right direction to learn about how I could implement a system similar to facebook's "like" or Twitter's "Follow/Unfollow" system that I could create in Rails?
From what I understand I would need to use Unobtrusive Javascript.
I have a Thing model(has_many :likes) and a Like model (belongs_to :thing)
Any pointers?
You can do ajax call to a function and implement whatever functionality you like inside that function , (in this case "follow" ), you can do it with :
[link_to_function][1]
Incase , you are using rails 3.2.4 and it deprecated, you can use(This is from jeremy's comment.
https://gist.github.com/rails/rails/pull/5922#issuecomment-5770442 ):
module LinkToFunctionHelper
def link_to_function(name, *args, &block)
html_options = args.extract_options!.symbolize_keys
function = block_given? ? update_page(&block) : args[0] || ''
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
end