diff --git a/.circleci/config.yml b/.circleci/config.yml index 08415ea..c0b1336 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: PGHOST: localhost PGUSER: open311status RAILS_ENV: test - - image: postgres:10 + - image: circleci/postgres:10-postgis environment: POSTGRES_USER: open311status POSTGRES_DB: open311status_test diff --git a/.gitignore b/.gitignore index 73ec241..7150920 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # Ignore all logfiles and tempfiles. /log/*.log /tmp +.byebug_history # Ignore foreman environment .env diff --git a/Gemfile b/Gemfile index e7df414..c184c96 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' ruby File.read(File.join(File.dirname(__FILE__), '.ruby-version')).strip git_source(:github) { |repo| "https://github.com/#{repo}.git" } +gem 'activerecord-postgis-adapter' gem 'autoprefixer-rails' gem 'aws-sdk-s3' gem 'babosa' diff --git a/Gemfile.lock b/Gemfile.lock index b308821..1aed859 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,6 +49,9 @@ GEM activemodel (= 5.2.1) activesupport (= 5.2.1) arel (>= 9.0) + activerecord-postgis-adapter (5.2.1) + activerecord (~> 5.1) + rgeo-activerecord (~> 6.0) activestorage (5.2.1) actionpack (= 5.2.1) activerecord (= 5.2.1) @@ -260,6 +263,10 @@ GEM redcarpet (3.4.0) request_store (1.4.1) rack (>= 1.4) + rgeo (1.1.1) + rgeo-activerecord (6.0.0) + activerecord (~> 5.0) + rgeo (~> 1.0) rspec-core (3.8.0) rspec-support (~> 3.8.0) rspec-expectations (3.8.1) @@ -356,6 +363,7 @@ PLATFORMS ruby DEPENDENCIES + activerecord-postgis-adapter annotate autoprefixer-rails aws-sdk-s3 diff --git a/README.md b/README.md index 41cee65..7101e70 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,27 @@ bruhl: jurisdiction: 'bruehl.de' ``` +### Loading real data + +By default, running `db:setup` will load cities and generate fake service +requests. To load cities, run `rake cities:load`. And to load service requests, +`rake cities:service_requests` + ### Application Dependencies 1. Install Ruby with your ruby version manager of choice, like [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://github.com/codeforamerica/howto/blob/master/Ruby.md) 2. Check the ruby version in `.ruby-version` and ensure you have it installed locally e.g. `rbenv install 2.5.1` 3. Install [bundler](https://bundler.io/) (the latest Heroku-compatible version): `gem install bundler` 4. [Install Postgres](https://github.com/codeforamerica/howto/blob/master/PostgreSQL.md). If setting up Postgres.app, you will also need to add the binary to your path. e.g. Add to your `~/.bashrc`: -`export PATH="$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin"` +`export PATH="$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin"`. +5. [Install PostGIS](https://postgis.net/install/), the Postgres geospatial extension, if it's not included in your distribution. Postgres.app comes with postgis. ### Application Setup 1. Install ruby gem dependencies: `bundle install` -2. Install node dependencies: `yarn install` -3. Create the databases and load schema and seeds: `bin/rails db:setup` -4. Run the tests: `bin/rspec` -5. Run the server: `bin/rails server`, and visit the web-browser: [`http://localhost:3000`](http://localhost:3000) +2. Create the databases and load schema and seeds: `bin/rails db:setup` +3. Run the tests: `bin/rspec` +4. Run the server: `bin/rails server`, and visit the web-browser: [`http://localhost:3000`](http://localhost:3000) + +#### Migration guide + +You may need to run `rake db:gis:setup` to enable PostGIS on your database. diff --git a/app/models/service_request.rb b/app/models/service_request.rb index 267b3bf..ba77f8c 100644 --- a/app/models/service_request.rb +++ b/app/models/service_request.rb @@ -3,6 +3,7 @@ # Table name: service_requests # # id :bigint(8) not null, primary key +# geometry :geography({:srid geometry, 4326 # raw_data :json # requested_datetime :datetime # status :string @@ -16,6 +17,7 @@ # # index_service_requests_on_city_id (city_id) # index_service_requests_on_city_id_and_service_request_id (city_id,service_request_id) UNIQUE +# index_service_requests_on_geometry (geometry) USING gist # index_service_requests_on_status (status) # @@ -34,6 +36,7 @@ def raw_data=(json) self[:status] = json['status'] self[:requested_datetime] = DateTime.iso8601(json['requested_datetime']) if json['requested_datetime'].present? self[:updated_datetime] = DateTime.iso8601(json['updated_datetime']) if json['updated_datetime'].present? + self[:geometry] = "POINT(#{json['long']} #{json['lat']})" if json['lat'].present? end rescue => e Raven.capture_exception e, diff --git a/config/database.yml b/config/database.yml index dedb90e..ae44592 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,5 +1,5 @@ defaults: &defaults - adapter: postgresql + adapter: postgis encoding: unicode pool: 20 username: <%= ENV['POSTGRES_USERNAME'] || ENV['USER'] %> @@ -20,3 +20,4 @@ staging: production: database: open311status_production <<: *defaults + url: <%= ENV.fetch("DATABASE_URL", "").gsub(/postgres/, 'postgis') %> diff --git a/db/migrate/20180819143420_add_geometry_to_service_requests.rb b/db/migrate/20180819143420_add_geometry_to_service_requests.rb new file mode 100644 index 0000000..30a890a --- /dev/null +++ b/db/migrate/20180819143420_add_geometry_to_service_requests.rb @@ -0,0 +1,8 @@ +class AddGeometryToServiceRequests < ActiveRecord::Migration[5.2] + def change + enable_extension "postgis" + + add_column :service_requests, :geometry, :geometry, geographic: true + add_index :service_requests, :geometry, using: :gist + end +end diff --git a/db/schema.rb b/db/schema.rb index 68d2c7d..20b8ddb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,6 +14,7 @@ # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + enable_extension "postgis" create_table "cities", force: :cascade do |t| t.string "slug" @@ -53,8 +54,10 @@ t.datetime "created_at" t.datetime "updated_at" t.integer "city_id" + t.geography "geometry", limit: {:srid=>4326, :type=>"geometry", :geographic=>true} t.index ["city_id", "service_request_id"], name: "index_service_requests_on_city_id_and_service_request_id", unique: true t.index ["city_id"], name: "index_service_requests_on_city_id" + t.index ["geometry"], name: "index_service_requests_on_geometry", using: :gist t.index ["status"], name: "index_service_requests_on_status" end diff --git a/spec/factories/service_requests.rb b/spec/factories/service_requests.rb index 794a258..aac6234 100644 --- a/spec/factories/service_requests.rb +++ b/spec/factories/service_requests.rb @@ -10,6 +10,8 @@ service_name { Faker::Commerce.department(2, true) } description { Faker::Lorem.paragraph(1, false, 5) } requested_datetime { 10.minutes.ago } + lat { Faker::Address.latitude } + long { Faker::Address.longitude } end raw_data do @@ -19,6 +21,8 @@ 'description' => description, 'status' => status, 'requested_datetime' => requested_datetime.iso8601, + 'lat' => lat, + 'long' => long, } end end diff --git a/spec/models/service_request_spec.rb b/spec/models/service_request_spec.rb index 9dddb83..6887365 100644 --- a/spec/models/service_request_spec.rb +++ b/spec/models/service_request_spec.rb @@ -24,6 +24,11 @@ it 'extracts :updated_datetime' do expect(sr.updated_datetime).to eq DateTime.iso8601 json['updated_datetime'] end + + it 'extracts a point' do + expect(sr.geometry.lat).to eq json['lat'] + expect(sr.geometry.lon).to eq json['long'] + end end describe '#slug' do