How to chain these methods together (Geocoder + Pagination) - ruby-on-rails

I currently have an array of locations using the geocoder gem:
#locations = Location.near([#lat, #lon], 3)
which produces:
=> [#<Location id: 18, address: "555 N 7th Ave, Jamaica, CA 66666", phone: "(623) 555-5555", latitude: 66.666, longitude: -200.082, user_id: 1, created_at: "2015-03-11 15:06:57", updated_at: "2015-03-11 15:06:57">,
#<Location id: 46, address: "555 N 7th Ave, Jamaica, CA 66666", phone: "(623) 555-5555", latitude: 66.666, longitude: -200.082, user_id: 1, created_at: "2015-03-11 15:06:57", updated_at: "2015-03-11 15:06:57">]
I'm trying to add pagination to these results with
#locations = Location.near([#lat, #lon], 3).paginate(:page => params[:page], :per_page => 5)
and in the view (haml):
= will_paginate #locations
But I'm getting the following error:
ActionView::Template::Error (Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*, 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((33.457341299999996 - locations.lati' at line 1: SELECT COUNT(locations.*, 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((33.457341299999996 - locations.latitude) * PI() / 180 / 2), 2) + COS(33.457341299999996 * PI() / 180) * COS(locations.latitude * PI() / 180) * POWER(SIN((-112.07359500000001 - locations.longitude) * PI() / 180 / 2), 2))) AS distance, MOD(CAST((ATAN2( ((locations.longitude - -112.07359500000001) / 57.2957795), ((locations.latitude - 33.457341299999996) / 57.2957795)) * 57.2957795) + 360 AS decimal), 360) AS bearing) FROM `locations` WHERE (locations.latitude BETWEEN 33.41392176506674 AND 33.50076083493325 AND locations.longitude BETWEEN -112.12563828858949 AND -112.02155171141054 AND (3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((33.457341299999996 - locations.latitude) * PI() / 180 / 2), 2) + COS(33.457341299999996 * PI() / 180) * COS(locations.latitude * PI() / 180) * POWER(SIN((-112.07359500000001 - locations.longitude) * PI() / 180 / 2), 2)))) BETWEEN 0.0 AND 3)):
Any idea?

Have you tried rendering #locations separately in your template? This is the pattern I typically use:
= will_paginate
render #locations
= will_paginate
This should at least help you isolate the problem.

Related

Ruby on Rails Geocoder Gem. List results of nearby search returning confusing data

I am trying to build an app with Rails that allows users to search for job listings using geographic locations (addresses to be specific). I'm using the 'geocoder' gem.
gem 'geocoder'
I can successfully search using the rails console using
Job.near('sydney',10) and it returns a listing from my database in an array.
2.2.2 :001 > Job.near('sydney', 10 )
Job Load (97.5ms) SELECT jobs.*, 6371.0 * 2 * ASIN(SQRT(POWER(SIN((-33.8688197 - jobs.latitude) * PI() / 180 / 2), 2) + COS(-33.8688197 * PI() / 180) * COS(jobs.latitude * PI() / 180) * POWER(SIN((151.2092955 - jobs.longitude) * PI() / 180 / 2), 2))) AS distance, MOD(CAST((ATAN2( ((jobs.longitude - 151.2092955) / 57.2957795), ((jobs.latitude - -33.8688197) / 57.2957795)) * 57.2957795) + 360 AS decimal), 360) AS bearing FROM "jobs" WHERE (jobs.latitude BETWEEN -33.958751860591875 AND -33.77888753940813 AND jobs.longitude BETWEEN 151.10098469477293 AND 151.31760630522706 AND (6371.0 * 2 * ASIN(SQRT(POWER(SIN((-33.8688197 - jobs.latitude) * PI() / 180 / 2), 2) + COS(-33.8688197 * PI() / 180) * COS(jobs.latitude * PI() / 180) * POWER(SIN((151.2092955 - jobs.longitude) * PI() / 180 / 2), 2)))) BETWEEN 0.0 AND 10) ORDER BY distance ASC
=> #<ActiveRecord::Relation [#<Job id: 73, title: "Dog walkingn", description: "walk my dog everyday", price: "$25", user_id: "24", timeframe: "daily", created_at: "2016-07-29 05:50:00", updated_at: "2016-07-29 05:50:00", location: nil, job_type_id: "3", street_number: "100", street: "Pitt Street", suburb: "Sydney", state: "NSW", postcode: "2000", country: "Australia", longitude: 151.2092955, latitude: -33.8688197>]>
But when I do this in controller and try and send the data to the view (jobs.html.erb), all I get is :
#<Job::ActiveRecord_Relation:0x007fd66475abd8>
here is my controller code:
def location
if params[:location].present?
jobs = Job.near(params[:location],
params[:proximity])
#jobs = jobs
else
#noJobs = 'there is nothing nearby'
#jobs = nil
end
end
and my view only consists of: (jobs.html.erb)
<%= #jobs %>
if i run
<%=#jobs[0].title%>
in the view it just returns
ActiveRecord::StatementInvalid in Jobs#location
can anyone explain to me what is going on and how i can access the data?
and what the active record relation actually means?
I'm still pretty new to coding, and this has completely stumped me. I cant find the answer anywhere.
Really appreciate any help, Thanks!
As you can see, #jobs is a ActiveRecord::Relation object, which is essentially an Array of ActiveRecord objects. To display them in the view, you will have to use some sort of a loop, most likely an each:
<% #jobs.each do |job| %>
<%= job %>
<% end %>
Also, <%= job %> alone might not do the trick. It will return something like a object with it's id. You will need to call job.title or some other attribute/method on the object, so you get the actual value in the view:
<% #jobs.each do |job| %>
<%= job.title %>
<% end %>

How to combine postgres group by count and where

Using Rails 4.2 and Postgres I've created the following query to give me a uniq list of bird ids in the Sightings table ordered by the number of sightings for each bird. Also note I'm using the Kaminari gem for pagination.
Sighting.select("bird_id, COUNT(sightings.id) as sightings_count").group(:bird_id).order('sightings_count DESC').page(1)
This works great returning the ActiveRecordRelation intended. The problem arises when i try to combine it with the geocoder gems .near method
Sighting.near([-31.0, 151.0], 1000, units: :km).select("bird_id, COUNT(sightings.id) as sightings_count").group(:bird_id).order('sightings_count DESC').page(1)
This generates the query and error
SELECT sightings.*, 6371.0 * 2 * ASIN(SQRT(POWER(SIN((-31.0 - sightings.lat) * PI() / 180 / 2), 2) + COS(-31.0 * PI() / 180) * COS(sightings.lat * PI() / 180) * POWER(SIN((151.0 - sightings.lng) * PI() / 180 / 2), 2))) AS distance, MOD(CAST((ATAN2( ((sightings.lng - 151.0) / 57.2957795), ((sightings.lat - -31.0) / 57.2957795)) * 57.2957795) + 360 AS decimal), 360) AS bearing, bird_id, COUNT(sightings.id) as sightings_count FROM "sightings" WHERE (sightings.lat BETWEEN -39.993216059187304 AND -22.006783940812696 AND sightings.lng BETWEEN 140.50821379697885 AND 161.49178620302115 AND (6371.0 * 2 * ASIN(SQRT(POWER(SIN((-31.0 - sightings.lat) * PI() / 180 / 2), 2) + COS(-31.0 * PI() / 180) * COS(sightings.lat * PI() / 180) * POWER(SIN((151.0 - sightings.lng) * PI() / 180 / 2), 2)))) BETWEEN 0.0 AND 1000) GROUP BY bird_id ORDER BY distance ASC, sightings_count DESC LIMIT 25 OFFSET 0
PG::GroupingError: ERROR: column "sightings.id" must appear in the GROUP BY clause or be used in an aggregate function
Adding id to the group by means the bird counts aren't correct and as far as I understood COUNT within select was an aggregate function which does include sightings.id.
How can I successfully combine the two?
Note: I did try the following but this returns a Hash rather than AR Relation.
Sighting.near([#lat, #lng], #range, units: :km, order:nil).group(:bird_id).order('count_id DESC').page(#page).count(:id)
Thanks for any help!!
Creating a custom near scope was the easiest work around as I wasn't using the bearing or distance attributes that get added to each record and hence the whole select sql generated by near I didn't need. Instead of select(options[:select]) I replaced it with the select I wanted.
scope :birds_by_sighting, lambda{ |location, *args|
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
options = near_scope_options(latitude, longitude, *args)
select("bird_id, COUNT(sightings.id) as sightings_count").group(:bird_id).where(options[:conditions]).order('sightings_count DESC')
else
# If no lat/lon given we don't want any results, but we still
# need distance and bearing columns so you can add, for example:
# .order("distance")
select(select_clause(nil, null_value, null_value)).where(false_condition)
end
}

Ruby Geocoder nearbys is slow

Using Rails 3.2, Ruby 1.9, geocoder gem. The following query I have in my controller:
# 1500+ms to load
nearby_shops = (current_shop.nearbys(10, :order => 'overall_rating DESC')
.where(:shop_type => shop_type).includes(:photos, :active_property_list =>
:hotel_image_lists).limit(5))
# SQL
Shop Load (4045.6ms) SELECT shops.*, 3958.755864232 * 2 * ASIN(SQRT(POWER(
SIN((36.111927 - shops.lat) * PI() / 180 / 2), 2) + COS(36.111927 * PI()
/ 180) * COS(shops.lat * PI() / 180) * POWER(SIN((-115.171229 - shops.lng)
* PI() / 180 / 2), 2))) AS distance, CAST(DEGREES(ATAN2( RADIANS(shops.lng
- -115.171229), RADIANS(shops.lat - 36.111927))) + 360 AS decimal) % 360 AS
bearing FROM `shops` WHERE `shops`.`shop_type` = 'food' AND (shops.lat BETWEEN
35.96719521688915 AND 36.25665878311085 AND shops.lng BETWEEN
-115.3503819353204 AND -114.9920760646796 AND 3958.755864232 * 2 *
ASIN(SQRT(POWER(SIN((36.111927 - shops.lat) * PI() / 180 / 2), 2) +
COS(36.111927 * PI() / 180) * COS(shops.lat * PI() / 180) * POWER(SIN((
-115.171229 - shops.lng) * PI() / 180 / 2), 2))) <= 10 AND shops.id != 85155)
ORDER BY overall_rating DESC LIMIT 5
- shops.lat) * PI() / 180 / 2), 2) + COS(48.8582411618 * PI() / 180) *
COS(shops.lat * PI() / 180) * POWER(SIN((2.2945044899 - shops.lng) * PI() /
180 / 2), 2))) <= 100 AND shops.id != 517) ORDER BY distance ASC LIMIT 25
OFFSET 0
The problem lies in the nearbys which does calculation on the longitude and latitude. I have already added indexes to the longitude and latitude columns, but it doesn't improve anything.
How can I improve this?
P/S: I removed unrelated conditions which doesn't contribute to the speed of the query.
This might not be the most exact or elegant solution, but why not just fudge the math a little. You could write a select clause that did something like this:
.select(["id, name, 69.0975851*sqrt(POWER(shops.lat-?,2) + COS(?*PI()/180)*POWER(shops.lon-?,2)) AS DISTANCE", loc.lat, loc.lat, loc.lon])
You don't really need to use that hairy great circle formula if you're just dealing with small (< 500 mile) distances. You'll end up getting the same ~almost~ answers for a fraction of the computational cost.

What explicit type casts should I add to make a no operator method work

I'm trying to use the near method of the Ruby geocoder gem in a Rails application with a postgres database.
#users = Contact.near(params[:search], params[:distance])
That line produces the 'operator does not exist error' you see below. The search parameter will be an address and the dist
In the Contact.rb model I do this
geocoded_by :full_address
def full_address
self.address + self.city + self.province + self.postal
end
Can you tell me what it is that I might be doing wrong? The error says, 'You might need to add explicit type casts.'
Address, city, province and postal are all strings.
PG::Error: ERROR: operator does not exist: numeric - character varying LINE 1: ...8.755864232 * 2 * ASIN(SQRT(POWER(SIN((43.7058645 - contacts... ^ HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. : SELECT contacts.*, 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((43.7058645 - contacts.latitude) * PI() / 180 / 2), 2) + COS(43.7058645 * PI() / 180) * COS(contacts.latitude * PI() / 180) * POWER(SIN((-79.3687137 - contacts.longitude) * PI() / 180 / 2), 2))) AS distance, CAST(DEGREES(ATAN2( RADIANS(contacts.longitude - -79.3687137), RADIANS(contacts.latitude - 43.7058645))) + 360 AS decimal) % 360 AS bearing FROM "contacts" WHERE (contacts.latitude BETWEEN 43.676918143377826 AND 43.73481085662217 AND contacts.longitude BETWEEN -79.40875589075141 AND -79.3286715092486 AND 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((43.7058645 - contacts.latitude) * PI() / 180 / 2), 2) + COS(43.7058645 * PI() / 180) * COS(contacts.latitude * PI() / 180) * POWER(SIN((-79.3687137 - contacts.longitude) * PI() / 180 / 2), 2))) <= '2') ORDER BY distance ASC LIMIT 1

How to use scope in AR request with joins?

In my case I have a model Product has_one Location
I use a geocoder gem to search location near a distance.
The request Location.near([0, 0], 100) is look like this:
SELECT locations.*, 6371.0 * 2 * ASIN(SQRT(POWER(SIN((0 - locations.latitude) * PI() / 180 / 2), 2) + COS(0 * PI() / 180) * COS(locations.latitude * PI() / 180) * POWER(SIN((1 - locations.longitude) * PI() / 180 / 2), 2) )) AS distance, CAST(DEGREES(ATAN2( RADIANS(longitude - 1), RADIANS(latitude - 0))) + 360 AS decimal) % 360 AS bearing FROM \"locations\" WHERE (6371.0 * 2 * ASIN(SQRT(POWER(SIN((0 - locations.latitude) * PI() / 180 / 2), 2) + COS(0 * PI() / 180) * COS(locations.latitude * PI() / 180) * POWER(SIN((1 - locations.longitude) * PI() / 180 / 2), 2) )) <= 20) ORDER BY distance
I want to do something like this:
Product.where(...).joins(:location).dosomething
How can I do it?
Location#near is a named scope? If so you can merge it with your Product scope using the & operator. I think this should work:
class Product
scope :near, lambda { |coord, dist| joins(:location) & Location.near(coord, dist) }
...
end
Then you can just use it like so:
Product.near([0, 0], 100)
Another possibility, since merging the scopes doesn't seem to work:
class Product
scope :near, lambda { |coord, dist| where(:id => Location.near(coord, dist).all.map(&:locationable_id) }
...
end
And usage like in the other answer:
Product.near([0, 0], 100)

Resources