Dispatches from the frontlines of the business of making software.

Google Places Gem

Ruby Gem Adventures: Google Places

When you’re learning Ruby, your journey starts off with some simple course such as learning how Strings work, doing some simple multiplication and then writing a method or two.

Then, you might do a course such as the Rails Tutorials that introduces you to Rails, Git and such tools.

You might do a course on plain ol’ Ruby. For me I loved Sandy Metz’s Practical Object-Oriented Design in Ruby (POODR) and I also did Pragmatic Studio’s course on Ruby.

As I progressed, I started using more and more Gems in my Rails applications.

But you still might feel, like me, that you didn’t really understand how all these gem works.

For example, I’d find a Gem for my purposes – perhaps Google Places – and I’d read through the documentation of the API explaining how it worked.

  1. Create a client initialized with an API key
  2. Call methods on the client with arguments for different places I’m looking for.
  3. Receive back an object or a collection of objects.
  4. Profit!

The documentation provides a good overview of the API that the gem provides.

But if you want to start writing your own Gems,you need to understand how the Gem is built, the design choices made behind the scenes and tools used.

And if you want to contribute back to the Gem, you need to go even deeper to see where you can submit improvements or bug fixes.

You can read through the code, but Ruby is a very dynamic language, so what you read is not always what gets executed at run time.

I typically use a variety of tools to understand a Gem, and that’s what I want to do here.

The first thing we’ll do is get an Google API key to use with Google Places, so we can make calls via the Gem.

To be able to use this gem, you’ll need a Google Places API key. To request an API key, point your browser to code.google.com/apis/console and follow the instructions there. You’ll find your API key on the API Access tab under Simple API Access.

Then I’ll set up a simple script called google_places.rb that will initialize my client and do one call:

require 'google_places'

@client = GooglePlaces::Client.new('putyourAPIKeyinHere')
@client.spots(-33.8670522, 151.1957362).each do |spot|
  puts spot.name
end

For ease of getting started, you can install the google_places gem by running gem install google_places. We’ll also need pry_byebug gem, so if you don’t have it, you can install it via gem install pry-byebug.

Run the script via ruby google_places.rb and you should get something that looks like:

Dunmore East
Strand Inn Hotel Waterford
Dunmore East Golf Lodges
The Three Sisters Inn
Dunmore Adventure
etc....

So, if you see the output, we should have had some success. Excellent!

Now the first tool for understanding the code will be our debuggers – Pry and Byebug.

Pry is a powerful alternative to the more common IRB shell for Ruby.

We’ll see below how we can use these extra powers to better understand the google_places gem.

The first step is to find our install path of our gem: gem which google_places.

In my case, I get back the install path for the Gem: /Users/thomas/.rvm/gems/ruby-2.4.0/gems/google_places-1.1.0/lib/google_places.rb

It most likely will be different for you depending on, amongst other things, whether you’re using something like RVM or Rbenv.

Now, with this information, I can cd over to the folder and start dropping breakpoints where I want byebug to halt execution in the code.

For instance, I can run ls /Users/thomas/.rvm/gems/ruby-2.4.0/gems/google_places-1.1.0/lib/google_places to see the contents of the folder:

client.rb     error.rb      location.rb   photo.rb      prediction.rb rectangle.rb  request.rb    review.rb     spot.rb

These files make up the “concepts” of this Gem, or better said in this context, the “objects”.

Inside each of the files, we’ll find further information about the objects.

For example, one of the first lines of client.rb states:

This class acts as a proxy to the working classes when requesting data from the API.

So, we can see that the client.rb file contains the entry point to the rest of the objects. Let’s drop a debugger in this one.

Above we initialized the Client object with an API key and then we called the spots method on it with the map longitude and latitude as arguments.

Therefore, I’ll drop a binding.pry at the start of this spots method:

def spots(lat, lng, options = {})
 96       require 'pry-byebug'
 97    => binding.pry
 98       detail = @options.merge!(options).delete(:detail)
 99       collection_detail_level(
100         Spot.list(lat, lng, @api_key, @options),
101         detail
102       )
103     end

As you can see above, first we require ‘pry-byebug’ and then we add binding.pry.

When we run our script again, the Ruby interpreter will halt execution of the script at this point and open up a console in Pry where we can interrogate the program as to its current state and behavior at that point. We can also use keywords to “step” through each step of the progam.

Let’s get started! First, I run the program with ruby google_places.rb.

If all went well, you should see something like this:

rom: /Users/thomas/.rvm/gems/ruby-2.4.0/gems/google_places-1.1.0/lib/google_places/client.rb @ line 98 GooglePlaces::Client#spots:

     95: def spots(lat, lng, options = {})
     96:   require 'pry-byebug'
     97:   binding.pry
 =>  98:   detail = @options.merge!(options).delete(:detail)
     99:   collection_detail_level(
    100:     Spot.list(lat, lng, @api_key, @options),
    101:     detail
    102:   )
    103: end

[1] pry(#<GooglePlaces::Client>)>

Basically, at this point script execution has been frozen.

If we run ls, we’ll see:

[1] pry(#<GooglePlaces::Client>)> ls
GooglePlaces::Client#methods: api_key  options  predictions_by_input  spot  spots  spots_by_bounds  spots_by_pagetoken  spots_by_query  spots_by_radar
instance variables: @api_key  @options
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_  detail  lat  lng  options

This is really cool, because we can see the methods, instance variables and local variables at this point in the program.

We’re starting to see the outline of the API provided by the Client class: the methods spot, spots, spots_by_bounds, spots_by_pagetoken, spots_by_query, and spots_by_radar seem to be our core interfaces.

Let’s dig deeper by moving forward with execution.

Run step once or twice and you should find yourself in a new class: the Spot class.

Run ls and you’ll see much more methods, split up over three levels:

Object.methods: yaml_tag
GooglePlaces::Spot.methods: 
GooglePlaces::Spot#methods:

We’re starting to see more of the object orientated guts of Ruby: we have class-level methods defined on the Object object as well as the GooglePlaces::Spot object and instance methods on the GooglePlaces::Spot object, marked as instance methods by # character.

We also have a lot more instance-level methods. Many of these methods come in the double level of: lat and lat=. These are the getter and setter methods in Ruby that get defined when you use the attr_accessor: :lat in a Ruby class. While you don’t see these methods when you read the code, they get defined at run time using Ruby’s metaprogramming features, and pry lets you explore them.

But where were we, again?

Just type whereami and you’ll get back to the breakpoint in the code:

From: /Users/thomas/.rvm/gems/ruby-2.4.0/gems/google_places-1.1.0/lib/google_places/spot.rb @ line 50 GooglePlaces::Spot.list:

    45:     # @option options [Integer] :retry_options[:delay] (5) the delay between each retry in seconds
    46:     #
    47:     # @see http://spreadsheets.google.com/pub?key=p9pdwsai2hDMsLkXsoM05KQ&gid=1 List of supported languages
    48:     # @see https://developers.google.com/maps/documentation/places/supported_types List of supported types
    49:     def self.list(lat, lng, api_key, options = {})
 => 50:       location = Location.new(lat, lng)

A few more steps later and we see this line: location = Location.new(lat, lng).

If we hit step again, we’ll be in the initialize method of the Location class.

Small Classes that encapsulate behavior and state

Let’s have a quick look at this Location class (it’s only 12 lines after all):

module GooglePlaces
  2   class Location
  3     def initialize(lat, lng)
  4       @lat = ("%.8f"%lat)
  5       @lng = ("%.8f"%lng)
  6     end
  7
  8     def format
  9       [ @lat, @lng ].join(',')
 10     end
 11   end
 12 end

As you can see, this small class is initialized with two arguments: latitude and longitude. The arguments are standardized to 8 decimal places, and the class provides an instance method to get the lat/lon in a standardized format.

You’ll also have the benefit of being able to pass one object around your program, instead of two connected arguments.

As your program grows in complexity, you’ll be able to add additional methods on this class to fit your needs.

Most importantly, you’ve moved two strings into a coherent concept in the program: a location.

Back to Stepping

Keep stepping through each line of the program, seeing the location object initialized earlier used to return a formatted location in lon/lat.

After (quite) a few steps (which you can speed up by typing step 10 to step forward 10 steps at once), you’ll find yourself in a Request class.

A quick ls will reveal some interesting inheritance of this class: HTTParty::ClassMethods#methods, which are followed by a whole list of methods form the HTTParty gem.

At this point, I’d point out an official disclaimer from the HTTParty gem:

When you httparty you must party hard!

With that over with, we may continue on our adventure into the bowls of this gem.

The line is very simple: request = new(NEARBY_SEARCH_URL, options). We’re creating a new instance of self (the request class), passing in the URL from a constant and some options.

Because the class is inheriting from the HTTparty gem, this class is using the HTTPparty gem to do the heavy work of sending HTTP requests over the wire to the Google API and get back a response.

Moving forward, we actually find ourselves in the get method in the HTTParty::ClassMethods method, where we can see that HTTparty method is a wrapper around Net::HTTP, a low-level HTTP client api for Ruby that comes with Ruby Standard Library.

As we keep stepping through, it can get a little tedious if you’re going through a section with a lot of assigns. Using the finish commands means you can execute until the current stack frame returns.

Once we’ve done our requests, we get back to the request class method on the Spot class where we now have an array of results of our API call.

Each item on this array is a Spot object built out of the JSON the Google Places API sent back.

A recap of our journey into the heart and back of the Google_Places Gem

So, as you can see, this Gem stands on the shoulders of other gems such as HTTParty to provide an interface with the Google Places API that is not entirely unlike the query interface that ActiveRecord offers with the database.

You can find a bunch of spots by location, or keyword, or you can dig into a specific spot to get more information on it.

We’ve seen concepts of objective orientated programming as applied in Ruby:
1. classes that encapsulate state and behavior such as the Location class;
2. classes that inherit from other classes such as the Request method;
3. class-level methods versus instance-level methods.

Finally, we seen how we can use pry and byebug to show us more than just reading the written code itself would should us, so we can see just how methods such as attr_acessor in class work at run-time.

Leave a reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: