0

I'm attempting to use the Geocoder gem with a DataMapper model in a Sinatra application.

environment.rb:

require 'rubygems'
require 'bundler/setup'
require 'dm-core'
require 'dm-timestamps'
require 'dm-validations'
require 'dm-aggregates'
require 'dm-migrations'
require 'dm-types'
require 'geocoder'

require 'sinatra' unless defined?(Sinatra)

# load models
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
Dir.glob("#{File.dirname(__FILE__)}/lib/*.rb") { |lib| require File.basename(lib, '.*') }

DataMapper.setup(:default, (ENV["DATABASE_URL"] || "sqlite3:///#{File.expand_path(File.dirname(__FILE__))}/#{Sinatra::Base.environment}.db"))
DataMapper.finalize
DataMapper.auto_upgrade!

lib/location.rb:

  class Location
    include DataMapper::Resource
    include Geocoder::Model::Base

    property :id, Serial
    property :address, String, :required => true

    # geocoder gem
    geocoded_by :address, :latitude  => :lat, :longitude => :lng

    # geocoder
    after_validation :geocode, :if => :address_changed?

  end

When I attempt to start an IRB session, an exception is generated:

irb> require './environment'
NameError: uninitialized constant Geocoder::Model
...

What am I not understanding?

craig
  • 25,664
  • 27
  • 119
  • 205

1 Answers1

0

First up, it looks like the Geocode gem won't have direct support for Datamapper, as per this issue.

Second, when you include a module inside a class, the methods are available to the instance of the class, and not at the class level. For example:

module Name
  def name
    puts "Module"
  end
end

class SomeClass
  include Name
end

SomeClass.new.name # => "Module"

This works because when you include a module, that module gets added to the ancestor chain of that class. Any methods that get sent to the instance which are not available on the instance are forwarded to the ancestors. However, there's another method called extend which adds the methods at a class-level rather than at instance level:

# module definition same as before

class SomeClass
  extend Name

  name # works!
end

Inorder to get class-level inclusion, there is another way (which is what the Geocoder gem uses for supported models:

# module code same as before

module Name
  def name
    puts "Module"
  end

  def self.included(klass)
    klass.extend(self)
  end
end

The included hook is provided for models which can be overridden to do something when the include Name step executes. Since there's no Datamapper specific module that is not executing this step, you see that error.

craig
  • 25,664
  • 27
  • 119
  • 205
Kashyap
  • 4,696
  • 24
  • 26