:include ?

I'd like some adviceif possile

i have 2 models Location and Listing Location has many Listings Listing
belongs to location. Using teh acts_as_geocodable plugin i have
geocoded the Location model.

this allows me to do a find like so

location = Location.find(:all, :origin => postal_code, :order =>
"distance asc")

i would like to include the child objects in the result so i can show
listings sorted by distance.

i posted at railsforum.com and Duplex suggested using :include as
follows :-

location = Location.find :all, :origin => postal_code,
                         :order => "distance asc",
                         :include => :listings #eager-loading for
performance
                         :conditions => "state = 'published'"

i get mysql error when i try that pasted here to save space
http://pastie.caboo.se/157933

This is my first project so I'm not sure but i think maybe its unable to
do the :include because distance is a generated field or maybe it's
because using :origin already is a type of JOIN query. any ideas? I'm
thinking maybe the way round this is to find a way to do the sql query
directly and bypass rails?

any suggestions welcome
cheers
Rigagoogoo

Hi Nathan,

The generated SQL does not contain a "distance" field yet you are
trying to order based on that field (:order => "distance asc"). Does
your Location model have this field? If not, then I would suggest you
remove the :order option and then try again. I don't understand why
this didn't fail in your first example without the :include option
though :frowning:

Regards,
Jabbslad

Hi Jabbslad

thanks for such a quick response :slight_smile:

Location model doesn't have distance field, it should be generated
by the inclusion of :origin in the find method indeed if i do the
following

location = Location.find :all, :origin => "some_postcode", :order =>
"distance asc"

it works fine and translates into an SQL query that gets mysql to
determine the distance between the Locations longitude and latitude
and the long/lat of the provided postcode - heres the SQL it creates
http://pastie.caboo.se/157973

it fails when i try to combine it with :include. so given :include
doesn't play ball with extended find acts_as_geocodable provides how
do i get to the endpoint i need

given Location has many Listings display Listings in order of
distance from users postcode my options as i see it are:-

1) Iterate through the results and create a new array but that hits
the database for each location.

location= Location.find :all, :origin => "some_postcode", :order =>
"distance asc"

2) maybe better to use
locations= Location.find :all, :origin => "some_postcode", :order =>
"distance asc"
listings = Listing.find :all
and some ruby to combine them into one array?

3) Use the locations result to build a new sql query?

4) fix the problem with :origin and include - probably not beginner
territory :wink:

frankly I don't know how to do any of the above so all of those
options are daunting :slight_smile: so any advice or examples would be great.

cheers
Rigagoogoo

Hi Jabbslad

thanks for such a quick response :slight_smile:

Location model doesn't have distance field, it should be generated
by the inclusion of :origin in the find method indeed if i do the
following

location = Location.find :all, :origin => "some_postcode", :order =>
"distance asc"

it works fine and translates into an SQL query that gets mysql to
determine the distance between the Locations longitude and latitude
and the long/lat of the provided postcode - heres the SQL it creates
http://pastie.caboo.se/157973

I imagine that origin's magic uses the :select option to include that
extra computed column. Unfortunately eager loading overwrites
that :select with its own and so that's not going o play ball.
Personally I'd go with a varian on 2:

locations= Location.find :all, :origin => "some_postcode", :order =>
"distance asc"
all_listings = Listing.find :all, :conditions => ['location_id in
(?)', locations] #pre rails 2 i think that needs to be changed to
locations.map(&:id)

listings_hash = all_listings.inject({}) do |memo, listing|
   listings_for_location = (memo[listing.location_id] ||= [])
   listings_for_location << listing
   memo
end

listings_hash is now a hash with all your listings, so if you want to
know all the listings for location[2], then you just do
locations[listings[2].id]

or cut out the middle man and do

location_map = {}
locations.each do |location|
   location.listings.loaded
   location_map[location.id] = location
end

all_listings.each do |listing|
   parent = location_map[listing.location_id]
   association_proxy = parent.listings
   association_proxy.loaded
   association_proxy.target.push(listing)
end

which sets up the association magic so that you can just do
location.listings

Fred

Hi fred thanks for the examples they've opened my eyes to the .include
() method after some playing with your exaple by using p to print out
the variables as i ent in console I think I understand it and will
probably be able to cobble together what i need using it but the
second example throws me with .loaded i've googled it but come up
with nothing? i've searched a rails project using textmate and not
found it as a method and searched the ruby docs. can you advise what
it does?

cheers
Nathan

Hi fred thanks for the examples they've opened my eyes to the .include
() method after some playing with your exaple by using p to print out
the variables as i ent in console I think I understand it and will
probably be able to cobble together what i need using it but the
second example throws me with .loaded i've googled it but come up
with nothing? i've searched a rails project using textmate and not
found it as a method and searched the ruby docs. can you advise what
it does?

The key bit of information here is that location.listings isn't an
array (even though it looks like one). It's an association proxy.
That's why you can do things like location.listings.find and so on.
When you try and do something to the association proxy that requires
the actual elements (eg if you try to access location.listings.first)
then the listings are actually loaded. What we're doing here is
prefetching all the listings and stuffing them into where the
association proxy stores the actual array (target). The call to loaded
means that the proxy will think that it has already loaded the array.
This sort of stuff isn't really documented much, but you can work it
out for yourself by reading the relevant bits of the rails source
(associations.rb, association_proxy.rb etc...)

Fred

Thanks Fred for the explanation i think i understand the intention
just not the how yet. i have only just learn't the meaning of
include and inject methods. :slight_smile:

i think i am spending too much time worrying about calls to my sql
there is no point spending days trying to understand this for my
first app. It could be years before i got enough traffic to it for
it to really make a difference. All I want is to create a view with
all Listings ordered by reletive distance from users postcode. The
method can be tuned at a later date if it ever needs to be I've
already spent 2 days trying to understand..

so what's easiest (not necessarily best) way for me to do this?

using acts_as_geocodable I can get relative distance to postcode in
the three following ways

locations = Locations.find(:all, :origin => postcode, :order =>
"distance")
locations[0].distance

location = Location.find(0, :origin => postcode)
location.distance

******the above .distance method is only available for the result set
not for the model in general also it can't be combined with an
include *******

Listing.find(1).location.distance_to postcode

ignoring how many sql queries are required how do i create a view
that works?

so something like :-

listings = Listing.find_all_by_state("published") # returns all my
published listings
for each listing in listings do
   puts listing.name &" "& listing.intro &" "&
listing.location.distance_to("postcode") &" "& "link to"listing.url
end

but ordered by distance and usable in a view not just the console???

thanks again
Nathan