I'm going to use spatial indexes on points of longitude/latitude coordinates which I think is a likely implementation scenario. I could use an ActiveRecord hook to send a separate ST_GeomFromText('POINT(' || NEW.longitude || ' ' || NEW.latitude || ')', 4326) statements but that just seems silly. Instead I'm going to use a trigger. Note that I'm using a spatial system specific to the United States in this example.
First, add a geometry column named geom to the table resources as a migration:
#./db/migrate/TIMESTAMPSERIAL_add_geom_to_resources.rb
class AddGeomToResources < ActiveRecord::Migration
def up
begin
execute <<-ADD_GEOM
SELECT AddGeometryColumn('public'::varchar, 'resources'::varchar, 'geom'::varchar, 2163, 'POINT'::varchar, 2)
ADD_GEOM
rescue
# TODO figure out how to run migrations as a different user
puts 'You will need to "CREATE EXTENSION postgis;" with a super user to get this migration to work.'
puts 'And/or grant the user Rails is connecting with superuser privs until these migrations are complete.'
end
end
def down
execute "ALTER TABLE resources DROP COLUMN geom;"
end
end
Now the trigger:
#./db/migrate/TIMESTAMPSERIAL_add_calculate_geom_trigger_to_resources
class AddCalculateGeomTriggerToResources < ActiveRecord::Migration
def change
execute <<-TRIGGER
CREATE OR REPLACE FUNCTION trig_geom_from_lon_lat() RETURNS trigger AS
$$
BEGIN
NEW.geom = ST_Transform(
ST_GeomFromText('POINT(' || NEW.longitude || ' ' || NEW.latitude || ')', 4326),
2163);
RETURN NEW;
END;
$$
LANGUAGE plpgsql VOLATILE;
DROP TRIGGER IF EXISTS resources_calculate_geom ON resources;
CREATE TRIGGER resources_calculate_geom
BEFORE INSERT OR UPDATE OF latitude, longitude
ON resources
FOR EACH ROW
EXECUTE PROCEDURE trig_geom_from_lon_lat();
TRIGGER
end
end
Don't forget the spatial index!
# ./db/migrate/TIMESTAMPSERIAL_add_spatial_index_to_resources
class AddSpatialIndexToResources < ActiveRecord::Migration
def up
execute 'CREATE INDEX idx_resources_geom ON resources USING gist(geom);'
end
def down
execute 'DROP INDEX IF EXISTS idx_resources_geom'
end
end
How do I validate this in RSpec?
# ./spec/models/resource_spec.rb
require 'spec_helper'
describe Resource do
describe "#save" do
context "when new" do
subject { FactoryGirl.create(:valid_resource) }
its (:unique_id) { should_not be_nil }
it "has a non-null geom column" do
val = ActiveRecord::Base.connection.execute("SELECT geom FROM resources WHERE id = #{subject.id}").values.first.first
# TODO better (not just nil check) validation
val.should_not be_nil
end
end
end
end
And don't forget if you're using DatabaseCleaner that you could lose your spatialrefsys data if you fail to configure it properly.