RubyおよびPostgisタイムゾーンサービス

私が参加したプロジェクトの1つで、ユーザーの現在の位置情報からタイムゾーンを決定するという問題が発生しました。 バックエンドには、ユーザーがスマートフォンを使用して作成したレコードがありました。 時刻はUTCではありませんでしたが、パラメーターには座標が含まれていました。

もちろん、既製のサービス(たとえば、Googleタイムゾーン)はありますが、それらはすべて有料であるか、機能が非常に限られています。 そこで私は自分のサービスを書くことにしました。



サービスはできるだけシンプルにする必要があります。 その上で、フォームのリクエストを1つだけ行う必要があります

http://host/timezone/name?lat=55.2341&lng=43.23352
      
      





latは緯度、lngは経度です。



データベースをカスタマイズする



データベースとしてPostgreSQLを使用します 。 また、地理的機能を操作するように特別に調整されたPostgis拡張機能も必要になります。



PostgreSQLが既にインストールされていると仮定します。 そうでない場合、これを行う方法はインターネット上に多くのガイドとチュートリアルがあります。 Postgisのインストールプロセスも簡単である必要があります。公式Webサイトには、ほとんどの一般的なオペレーティングシステムの詳細な手順が記載されています。



必要なものをすべてインストールしたら、新しいデータベースを作成します。このデータベースを使用して、タイムゾーンを決定します。 例として、「tz_service」と記述します。

 CREATE DATABASE tz_service
      
      





データベースにPostgisを含めます:

 CREATE EXTENSION postgis; CREATE EXTENSION postgis_topology; CREATE EXTENSION fuzzystrmatch; CREATE EXTENSION postgis_tiger_geocoder;
      
      





ここで、サイトefele.netのすべてのタイムゾーンのシェープファイルが必要です 。 tz_world.zipをダウンロードします。 アーカイブにはtz_world.shpファイルが含まれています。 シェープファイルには、地理データのベクトル表現が含まれています。 ただし、これをSQLダンプに変換し、tz_serviceデータベースにロールする必要があります。

 $ /usr/lib/postgresql/9.1/bin/shp2pgsql -D tz_world.shp > dump.sql $ psql -d tz_service -f dump.sql
      
      





できた! 要求によって操作を確認します。

 SELECT tzid FROM tz_world WHERE ST_Contains(the_geom, ST_MakePoint(-122.420706, 37.776685));
      
      





次のようなものが得られるはずです。

 tzid --------------------- America/Los_Angeles (1 ROW)
      
      





RubyでSevrisを書く



Grapeフレームワークをサービスフレームワークとして使用します。 RESTのようなサーバーアプリケーションをすばやく作成するのに最適です。

まず、Gemfileを作成し、必要なgemをそこに記述します。

 source "https://rubygems.org" gem 'rake' gem 'activerecord' gem 'pg' gem 'grape' group :development, :test do gem 'shotgun' gem 'byebug' gem 'pry' gem 'pry-byebug' gem 'rspec' end
      
      





開発およびテストグループに含まれるものは、開発にのみ必要であり、本番モードでは使用されません。 ただし、開発にはそれほど必要ありません。

-コードの次の変更後、毎回サーバーを再起動しないためのショットガン

-デバッグ用のbuebugとpry

-テスト用のrspec



依存関係を持つすべてのgemをインストールします。

 $ bundle install
      
      





プロジェクトツリーは次のようになります。



画像



順番に行きましょう。 構成から始めましょう。



config / database.ymlには、データベースと通信するための情報が含まれます。

 development: &config adapter: postgresql host: localhost username: user password: password database: tz_service encoding: utf8 test: <<: *config poduction: <<: *config
      
      





次に、yamlファイルを解析するためのデータベース構成クラスconfig / configuration.rbを配置します。

 class Configuration DB_CONFIG = YAML.load_file(File.expand_path('../database.yml', __FILE__))[ENV['RACK_ENV']] class << self def adapter DB_CONFIG['adapter'] end def host DB_CONFIG['host'] end def username DB_CONFIG['username'] end def password DB_CONFIG['password'] end def database DB_CONFIG['database'] end def encoding DB_CONFIG['encoding'] end end end
      
      





App / environment.rbには環境設定が含まれます。

 require 'bundler' Bundler.require(:default) $: << File.expand_path('../', __FILE__) $: << File.expand_path('../../', __FILE__) $: << File.expand_path('../../config', __FILE__) $: << File.expand_path('../services', __FILE__) ENV['RACK_ENV'] ||= 'development' require 'grape' require 'json' require 'pry' require 'active_record' require 'timezone_name_service' require 'configuration' require 'application' require 'time_zone_api'
      
      





app / application.rbに、データベースとの接続のactiverecord設定を書き込みます。

 ActiveRecord::Base.establish_connection( adapter: Configuration.adapter, host: Configuration.host, database: Configuration.database, username: Configuration.username, password: Configuration.password, encoding: Configuration.encoding )
      
      





サービスの基礎は整っています。リクエストに応答するサービス自体のクラスを1つ記述するだけで、それで終わりです。 それだけですか? いや! 最初にテストを書く必要があります。 TDDを忘れないでください。



spec / spec_helper.rbを作成し、少し設定します:

 ENV['RACK_ENV'] ||= 'test' require_relative '../app/environment' require 'rack/test' RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true config.run_all_when_everything_filtered = true config.filter_run :focus config.order = 'random' config.include Rack::Test::Methods def app TimeZoneAPI end end
      
      





テストでは、サービスの動作を記述する必要があります。 そして、次の2つだけを期待しています。

1.リクエスト内の適切なパラメーターを使用した適切な回答

2.パラメータがない場合のエラー

これについて説明します。

 describe 'API' do let(:params) { { lat: 55.7914056, lng: 49.1120427 } } #    let(:error) { { error: 'lat is missing, lng is missing' } } #      let(:name_response) { { timezone: 'Europe/Moscow' } } #     #  it 'should greet us' do get '/' expect(last_response).to be_ok expect(last_response.body).to eq(%Q{"Welcome to Time Zone API Service"}) end #      describe 'Timezone name' do subject { last_response } #     context 'with wrong params' do before do get '/timezone/name' end its(:status) {should eq 400} its(:body) {should eq error.to_json} end context 'with right params' do before do get '/timezone/name', params end its(:status) {should eq 200} its(:body) {should eq name_response.to_json} end end end
      
      





コマンドを実行することにより:

 $ bundle exec rspec
      
      





単一のテストはパスしません。 それでも=)テストをグリーンにする必要があります。

非標準のクエリでデータベースに連絡する必要があります。 これはクラスapp / services / time_zone_service.rbを介して行います。

 class TimezoneNameService def initialize(lat, lng) @lat = lat @lng = lng end def name #"" .    ,        sql = "SELECT tzid FROM tz_world WHERE ST_Contains(geom, ST_MakePoint(#{ActiveRecord::Base.sanitize(@lng)}, #{ActiveRecord::Base.sanitize(@lat)}));" name_hash = ActiveRecord::Base.connection.execute(sql).first name_hash['tzid'] rescue nil end end
      
      





最後に、サービスアプリのメインクラス/ time_zone_api.rb:

 class TimeZoneAPI < Grape::API format :json default_format :json default_error_formatter :txt desc 'Start page' get '/' do 'Welcome to Time Zone API Service' end namespace 'timezone' do desc 'Time zone name by coordinates' params do requires :lat, type: Float, desc: 'Latitude' requires :lng, type: Float, desc: 'Longitude' end #  get '/name' do name = TimezoneNameService.new(params[:lat], params[:lng]).name { timezone: name } end end end
      
      





以上です! サービスの準備ができました。 Grapeアプリケーションを実行することで、その作業を「ライブ」で確認できます。

 $ bundle exec rackup
      
      





関連リンク

ブドウのフレームワーク

Postgis

Githubプロジェクトコード



All Articles