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.