[MUSIC]
In this lecture, we will discuss adding Geokit Rails gem queries to our images,
so that we can take advantage of queries like closest to origin, within a range of
an origin and ordered by distance from the at origin and add some query finishing,
so that our object gets augmented with its distance from a particular origin.
To get started with queries, we'll be adding the gem from Geokit Rails.
This gem specifically provides geolocation database based query functions
like within or by distance and we'll also integrate in with it's related
geokit gem that will be able to go out toward geocode provider, and get results.
But right now, we'll just worry about querying those values in the database and
not worry about where those values came from.
So as we pick up on our implementation, our image has properties of lat and long.
We have a point class that's going to encapsulate the values of lat and long for
our image, and we have defined that composition the active record.
And now that we've brought in GeoKit Rails, we would like to take our image
with lat and long and be able to find images that are closest to an origin,
within range of an origin and order them based upon their distance from an origin
and return to us with an annotation that says, how far they are from that origin.
And if we look closely at the results of finds closest,
our first hint of why the test is failing is there's no closest method on an image.
And so, how we pick up that method?
We define a Geokit Rails macro of acts as mappable.
So with the macro defined, our image now has the closest method to find.
So, let's define an origin.
A point at Johns Hopkins University and
let's build a new image with no image content at that particular origin.
So now if we query for closest, notice that it's doing some mathematical
calculations on our database values and it's coming back with
a query result that is still a collection, a collection of one, but
it has identified the image that is closest to the location that we expressed.
So defining the macro gave us the query method of closest and
it able to look in to the lat, and
long property that we passed in and implement our query.
Now one thing to note if we're going to execute the query against our point class,
the origin, we're going to get an error,
because it doesn't understand what point is.
It knows what an active model class is that has a lot and long property, but
it doesn't know what this non-active model class class is that has one.
We can somewhat solve that by giving it an array of latitude longitude and
we get our value.
Except, we would like to not have to worry about what it is that we're passing in.
We would like some of these types to be treated inherently as an origin.
So what I'm going to do in addition to defining the macro is on the image class,
I'm going to define a to_lat_lng.
This is a method that Geokit Rails looks for in what's passed in.
And if you supply that, it says, great.
Give it to me, because I'll expect this latlng object to come back.
Although at this point in time, it doesn't seem like we need it for this image class,
what we would like to do is the same for the point class.
We could go over to the point class and do exactly what we did with image, except,
well, this is a place to do trial and error.
Why don't we just show an example of us adding a method
to an existing class outside of that class.
So, let's leave our point class pristine.
It knows nothing of Geokit Rails and it just has its core data types.
And then since our image already knows about Geokit Rails,
it did act as mappable.
It defined as to_lat_lng and it says, you know what?
The point class as well should know about that.
Let me add that method to it and we can do it with this class_eval.
And it knows if I can pass in the result of latlng, which is an array and
put an asterisk in front of that which will turn the two
element array into two parameters into the new like we did here,
we will end up returning a Geokit: :LatLng.
Now, the only negative to doing it this way is this
method won't exist until you load in the image class.
So if you go straight to the point, the method won't exist.
If we go ahead and come in fresh and try to new up a point and
ask for its latlng, he's going to say, I don't know what a latlng is.
But if we new up an image, cause image to get uploaded and
then we go to a point he says, yeah, I know what that is.
That's the one downside of extending a class outside of the class itself.
That if we do it this way, we have to load an image in order to
have this property available to point.
This is probably overkill since we have the source code to both classes at our
fingertips, but I wanted to leave this in the baseline as an example of how we might
want to add methods to a class that we don't have the source code to.
So now that our point class knows about this particular special method we can just
use it straight up without having to convert it into lat long.
That'll be convenient.
And if we look back at the test that we're trying to pass,
we'll notice that it sets up by creating an image from 0 to
90 degrees latitude at 0 degrees longitude and
then locates a particular image in that set that's at 0 degrees latitude.
And then when we say, find closest, it says,
please give me who's closest to the origin, who's not of id origin.
And what we should find is that the closest should be
somebody who has a one degree latitude.
Now just absorb that for a second,
what we're adopting is the ability to augment our query context.
You saw all those mathematical equations and then we're paring it down.
We want to add in some additional criteria and the query just gets bigger.
So we come back over to the rails console and set of origin,
because it's not defined that way.
We just say, 27.
We say, find us the closest that's not this row homes,
because we want something else.
And now, we get the Taney Statue.
Well, give me something that's not the Taney Statue and
it's going back to the row homes.
Give me something that's not of those two.
We get the Bromo Tower.
And what we look at is yes, those mathematical calculations are in there,
but we've also added in images not in this id set.
So now when we run our query test just by the addition
of that macro all of our query methods are passing.
We've only looked at closest in moment,
we'll take a closer look at what all these other methods meant.
So if we take a look at our test, we know the details behind closest.
So, the test finds within range what it is invoking is within and
the within query scope will not reduce it down to the closest.
It will reduce it down to all are that within our criteria.
So if you remember our dataset, there were all at the same value of longitude, zero.
There like all like Greenwich mean time and
then they all were one degree apart from one another from 0 to 90 degrees.
Well, Googling how far is that distance of 1 degree latitude from the equator to
the North Pole, we come up with a value of about 69 miles equals 1 degree latitude.
And I'm just saying, give me everything within 13 times 69
miles of an origin that's at 0 latitude, but not the origin and
I would expect to get 13 answers back minus the one origin as my result.
And then I would expect that if I were to call another method added by
that macro distance from, each of the images returned will say that
they're less than 13 times 69 miles from the origin.
Make sense?
Now, we can apply that same query for when we say ordered.
We just don't want to know who's in that overall collection of within
13 times 69 miles, we would like them ordered by distance.
And if we grab the first one, it would either be the closest or
the farthest, depending on how we've ordered things.
So if we add in the query context of by distance and pass to it the origin,
realizes first queries go is receiving an origin and
it's constraining the results to be within 13 times 69 miles and
the second query scope is working on the order by and
it's doing another calculation that says, okay,
now let's order these results by their relative distance from an origin.
So in theory, these could be two different origins, but
that really wouldn't make sense to me.
And then a test goes on to verify that in the order given,
that each of them is one step further away than the other.
And of note, this by_distance, it also takes an optional
argument of reverse true, false and it defaults to false.
And so you can go from farthest to closest,
if you put in reverse versus closest to farthest.
If you put in nothing or you put in false.
So, lets take a look at this for a second.
So let's go ahead and build our origin again and let's paste in the within, and
by_distance.
If we go back to the query as I was saying earlier, the where clause is constraining
things that are within those limits using mathematical calculations.
And then the order by is doing the same thing,
except for the purposes of ordering them.
Two independent query scopes working together to give us the desired answer.
So in this last test, what we don't want is to have to look back and
ask distance from each time to find out where are objects are.
We want them already resolved.
We want them coming to us with a value.
So if you look on their website, you'll see some discussion of how they used to
have it actually baked in to the query and why that was a performance problem.
And so now instead, they provide a helper function that'll effectively do
what we're doing here of going through each element and
performing a distance from, they'll use meta programming in order to
add the value from this calculation on the object that gets returned.
So if you go to the Geokit Rails GiutHub website and search for
the DistanceCollection class, you'll notice a method of set
distance from that takes an origin and some options and extends array.
The options really are only just there to say, what property name are they
going to use and what they do is they use a metaprogramming to go ahead and
add the property to the overall class and then set that property in a for
loop calling distance to and then calculating it per origin.
For some reason, this does seem kind of unnecessary, because we already
did an order by and that order by required obviously a distance to but
this is how we need to do it.
And honestly, we don't really notice the performance.
It's pretty quick.
So what this last test verifies is that after coming out of DistanceCollection,
we walk through each of our objects and they all respond to distance and
they are all, of course, less than 13 times 69 miles.
So to demonstrate some of the capability that it's doing, just notice if we create
a new image and it asks for distance, it says, I don't know what a distance is.
But if we go ahead and get our origin and then if we go off and
get all the images that are within 13 times 69 miles of that origin,
the fact that we are going to order by distance is not important.
A result came back and then I'll take that collection,
and pass it through the distance collection helper method.
And if I look at the distance for a particular one, it's going to say,
two miles.
But in addition to that,
any new image that we create now understands what distance is.
So, this is what this method did.
It added this property to our overall class, so
that all new instances of that class would know about that property.
And if we take a look, this is logically what we did in the case of the point class
here, except we used a different syntax in order to achieve a similar behavior.
In summary, we added Geokit Rails query capability to our image and
used our point.
We integrated image, rather easily, because it already had lat and
long by adding the macro, acts_as_mappable.
And then we demonstrated a Ruby class capability where we extended the point
class to have an additional method required by Geokit Rails, but
we defined that extra method within the image class.
Pretty neat how that works.
We can keep our Geokit Rails stuff isolated and
not pollute some generic classes like point.
And then we took advantage of Geokit Rails queries like closest to an origin,
within a range of an origin and ordered by_distance from that origin and
then added some finishing of annotating the object.
How far it is from the origin when we return it back to our caller and
leveraging a helper class provided by Geokit Rails in order to do that.
What's next?
Associated Object Geolocation Queries.
Not every object within our applications is going to have lat and
long associated with it, but we would like to get some geolocation query
results from related objects to image and let's do that next.