Very weird result with geocoder has_many and belongs_to association

224 views Asked by At

I am using Geocoder Gem, it is showing wired behavior.

I have Location model

class Location < ActiveRecord::Base
has_many :events
geocoded_by :address
after_validation :geocode
def full_address
    "#{postal_code}, #{city}, #{street}"
  end
end

and Event model

class Event < ActiveRecord::Base
    belongs_to :location
    accepts_nested_attributes_for :location
end

I want to find all events nearby the current user's location.

I tried following steps to find nearby locations..

nearby_loc = current_user_location.nearby

it is returning all nearby "locations" of current users location

Then I tried

nearby_loc.events

but it is giving me error

NoMethodError: undefined method `events' for #<ActiveRecord::Relation::ActiveRecord_Relation_Location:0x0000000958c088>

Please help me ...

4

There are 4 answers

3
nathanvda On BEST ANSWER

The events is defined on a location, and nearby will give you a list of locations, so you will have to iterate over the list.

Simply put:

all_related_events  = []
nearby_locations.includes(:events).each do |location|
  all_related_events += location.events
end 

It also helps if your variable names more correctly reflect what they contain, so use nearby_locations instead of nearby_loc.

[UPDATE]

To minimise the number of queries, I added the .includes(:events) which will fetch all events in one query.

12
D-side On

I prefer to start with the query with a model I want to get. Instead of getting "a collection from collection of collections", you can...

Join a Location to all events so you can fetch all Events, whose Location matches certain conditions. Most of the time a scope is just a bunch of conditions, able to be merged.

Like so:

Event.joins(:location)                    # Results in `INNER JOIN`
     .merge(current_user_location.nearby) # Adds in the conditions for locations

But things are not that simple!

Geocoder does a very complicated select under the hood and adds some useful fields like distance that depend on the point that was fed into the scope. We can't just lose these, right? The query would stop making any sense.

An option is to do an INNER JOIN in a really weird way: by specifying the FROM-clause to fetch data from two tables (more on that later) and specifying the join condition in a WHERE-clause. We'll need a bit of Arel for that, so let's fetch tables in advance:

locations = Location.arel_table
events    = Event   .arel_table # Yeah, call me an indentation maniac

And now here's a catch: instead of locations table we'll use results of a subquery formed by current_user_location.nearby. How? We'll feed from with an array of things we want to use:

Event.from([current_user_location.nearby.as('locations'), events])
           # ^ an array, yeah!

What we have here is:

select events.* from (geocoder subquery) locations, events

Now what? A join condition. As I said, since we're making a weird join, we'll specify the join condition in where. We have to.

Event.from([current_user_location.nearby.as('locations'), events])
     .where(location_id: locations[:id])

...and that should probably work fine. Done completely by the database at least.

2
Sploadie On

In this case

nearby_loc = current_user_location.nearby

returns a list of locations, not a single location.

To iterate through them and find each location's events, you could use

nearby_events = nearby_loc.map {|loc| loc.events}

However, this is not efficient in terms of total queries.

0
Pavan On

NoMethodError: undefined method `events' for ActiveRecord::Relation::ActiveRecord_Relation_Location

Your nearby_loc is ActiveRecord::Relation, so nearby_loc.events results an error. You should iterate over nearby_loc to get it worked.

nearby_loc.each do |n|
n.events
end