Mel Mel - 28 days ago 23
Javascript Question

Google maps - setting it up to work with Rails 5 - geocoder not geocoding

I have been trying for 4+ years to try and figure out a way to use google maps in my Rails app. I'm currently trying with Rails 5.

I recently posted this cry for help. The suggestions I received haven't worked for me, so I'm trying again now to see if someone else might know something.
Rails 5, Gmaps4Rails - setup

I have models for organisation and address.

The associations are:

Organisation

has_many :addresses, as: :addressable
accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true


Address

belongs_to :addressable, :polymorphic => true, optional: true


I'm no longer trying to figure this out with the gmaps4rails gem. I removed that from my gem file. I still have the geocoder gem though.

In my address controller, I have

def index
@addresses = Address.all
end

def show

end


In my organisation controller, I have

def index
@organisations = Organisation.all
authorize @organisations
end

def show
@addresses = @organisation.addresses.all

end


In my organisation view, I have

<%= render 'contacts/addresses/map' %>


In my address view, I have

<%= javascript_tag do %>
var addresses = <%= raw @addresses.to_json %>;
<% end %>


<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_MAPS_API_KEY"] %>&callback=initMap"
async defer></script>


In my address.js, I have

function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4
});
var bounds = new google.maps.LatLngBounds();

var n = addresses.length;
for (var i = 0; i < n; i++) {
var address = new google.maps.Marker({
position: {lat: parseFloat(addresses[i].latitude), lng: parseFloat(addresses[i].longitude)},
title: addresses[i].name,
map: map
});
bounds.extend(address.position);
}

map.fitBounds(bounds);
}


While the zoom setting doesnt seem to work, this will actually render a map provided that I set the latitude and longitude fields from the console.

If I don't set those fields manually in the console, I get an error that says:

js?key=.....&callback=initMap:42 Uncaught RangeError: Maximum call stack size exceeded


This post Uncaught RangeError: Maximum call stack size exceeded Google maps when I try to set bounds suggests that problem is caused by the lat/lng coordinates not being passed to the js. That explains my problem because my console shows those attributes are saved as nil.

In my address model I have:

class Address < ApplicationRecord

geocoded_by :full_address # can also be an IP address

# --------------- associations

belongs_to :addressable, :polymorphic => true, optional: true

# --------------- scopes

# --------------- validations


# --------------- class methods


def unit_line
if self.unit.present?
['Unit', unit.titlecase].join(' ')
end
end

def first_line
[street_number, street.titlecase].join(' ')
end

def middle_line
if self.building.present?
['Building', building.titlecase].join(' ')
end
end

def last_line
[city.titlecase, region.upcase, zip].join(' ')
end

# def country_name
# # self.country = ISO3166::Country[country]
# # country.translations[I18n.locale.to_s] || country.name

# # iso_country = ISO3166::Country.find_by_name(country) # `country` should be name like 'Australia'
# # iso_country.translations[I18n.locale.to_s] || iso_country.name
# end

# def country_name
# self.country = ISO3166::Country[country]
# country.translations[I18n.locale.to_s] || country.name
# end

def country_name
country = self.country
ISO3166::Country[country]
end



def full_address
[self.unit_line, first_line, middle_line, last_line, country_name].compact.join("<br>").html_safe
end


# --------------- callbacks

after_create :geocode#, if self.full_address.changed?


I have also tried:

after_validation :geocode



# --------------- instance methods

# --------------- private methods



end


My address table has decimal attributes for latitude and longitude. I am using the geocoder gem and expect the address to be geocoded after creation.

I have an initialiser for geocoder.rb with:

Geocoder.configure(
# Geocoding options
:timeout => 3, # geocoding service timeout (secs)
:lookup => :google, # name of geocoding service (symbol)
:language => :en, # ISO-639 language code
:use_https => true, # use HTTPS for lookup requests? (if supported)
# :http_proxy => nil, # HTTP proxy server (user:pass@host:port)
# :https_proxy => nil, # HTTPS proxy server (user:pass@host:port)
:api_key => nil, # API key for geocoding service
# :cache => nil, # cache object (must respond to #[], #[]=, and #keys)
# :cache_prefix => "geocoder:", # prefix (string) to use for all cache keys

# exceptions that should not be rescued by default
# (if you want to implement custom error handling);
# supports SocketError and TimeoutError
# :always_raise => [],

# calculation options
:units => :km, # :km for kilometers or :mi for miles
# :distances => :linear # :spherical or :linear
)


Can anyone see what I need to do to get this to work?

Despite having an after validation on my address.rb asking for geocode, my lat/lng attributes are not updating with coordinates.

Console output

a = Address.first
Address Load (30.2ms) SELECT "addresses".* FROM "addresses" ORDER BY "addresses"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Address id: 8, unit: "16", building: "asdf", street_number: "15", street: "Johnston Street", city: "Balmain East", region: "NSW", zip: "2041", country: "AU", time_zone: "Midway Island", addressable_id: 1, addressable_type: "Organisation", description: "registered_office", created_at: "2016-11-10 00:16:50", updated_at: "2016-11-10 00:16:50", latitude: nil, longitude: nil>


Eric's suggestion

I tried to take Eric's suggestion to trigger the geocode, but it doesnt update the lat/lng coordinates.

Address.where(latitude: nil).or(Address.where(longitude: nil)).each{|marker| marker.save}
Address Load (16.3ms) SELECT "addresses".* FROM "addresses" WHERE ("addresses"."latitude" IS NULL OR "addresses"."longitude" IS NULL)
(2.0ms) BEGIN
(0.8ms) COMMIT
=> [#<Address id: 8, unit: "1", building: "asdf", street_number: "10", street: "Darling Street", city: "Balmain", region: "NSW", zip: "2041", country: "AU", time_zone: "Midway Island", addressable_id: 1, addressable_type: "Organisation", description: "registered_office", created_at: "2016-11-10 00:16:50", updated_at: "2016-11-10 00:16:50", latitude: nil, longitude: nil>]

Answer

As usual, start simple and slowly add more code. I used the basic Rails app from my previous answer.

I added gem 'geocoder' to Gemfile.

After bundle install, I added

geocoded_by :name   # can also be an IP address
after_validation :geocode

to my Marker class, which already has latitude and longitude defined as :decimal attributes.

In rails c :

Marker.create(:name => 'New Dehli')
Marker.create(:name => 'New-York')

Done, they appear on the map!

NOTE: If for some reason some Marker doesn't have coordinates, the map will not be displayed, and you'll get a "too much recursion" Error in js console. To avoid this, you can make sure all your Markers have coordinates by enabling after_validation :geocode in your model, and launching this in in your console :

Marker.where(latitude: nil).or(Marker.where(longitude: nil)).each{|marker| marker.save}

Saving the marker triggers the validation, which triggers the geocoding.

If for some reason you still have non-geocoded Markers, change your js to :

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4
  });
  var bounds = new google.maps.LatLngBounds();

  var n = markers.length;
  for (var i = 0; i < n; i++) {
    var lat = markers[i].latitude;
    var lng = markers[i].longitude;
    if (lat == null || lng ==null){
      console.log(markers[i].name + " doesn't have coordinates");
    }else {
      var marker = new google.maps.Marker({
        position: {lat: parseFloat(lat), lng: parseFloat(lng)},
        title: markers[i].name,
        map: map
      });
      bounds.extend(marker.position);
    }
  }
  map.fitBounds(bounds);
}