Service time zones in Ruby and Postgis
In one of the projects in which I participated, arose the problem of determining the time zone for the current geolocation of the user. On the backend came a record generated by the user through the smartphone. The time has come not in UTC, but the provided coordinates.
Of course, there are services (like The Google Time Zone), but they all paid or are severely limited in functionality. So I decided to write my own.
The service should be as simple as possible. We need to make only one request of the form
the
Where lat is the latitude, lng — longitude.
Nastraivaet database
As the database will use PostgreSQL. We will also need extension Postgis custom-tailored to work with geographic objects.
We assume that PostgreSQL is already installed. If not, the Internet has a lot of guides and tutorials on how to do it. The process of installing Postgis also should not cause difficulties on the official website has detailed instructions for most popular operating systems.
After installing everything, create a new database, which we will use to determine the time zone. Handling example, I write "tz_service":
the
Enable Postgis in your database:
the
Now we need a Shapefile of all time zones with the efele.net. Download tz_world.zip. The archive is a file tz_world.shp. Shape files contain vector representation of geographic data. But we need to convert it to an SQL dump and roll it to our base "tz_service":
the
Ready! I approve the request:
the
You should get something like this:
the
Write sevris on Ruby
As a skeleton service will use the framework Grape. It is great to quickly write REST-like server applications.
First, create a Gemfile and write there the gems we need:
the
What is the group development and test is necessary only for development and in production mode will not be used. But we need to develop not so much:
— shotgun that would not restart every time the server after the next code change
— buebug and pry for debugging
— rspec for tests
Install all gems with dependencies:
the
The project tree should look like this:
Let's go in order. Let's start with the configs.
In config/database.yml will contain information for communication with the database:
the
Next put the configuration class database config/configuration.rb for parsing yaml file:
the
In the app/environment.rb will contain environment settings:
the
In app/application.rb set configure activerecord to connect to the database:
the
The basis for the service ready, it is only necessary to write one class of the service, which will answer our request and all. All? No! First you need to write tests. Do not forget about TDD.
Sosedin spec/spec_helper.rb and his moods a bit:
the
In the tests we shall describe the behaviour of the service. But we expect only two things:
1. An adequate response with the appropriate parameters in the query
2. Error when no parameter
Describe it:
the
By running the command:
the
No test will pass. Another would be =) Have to zaselenie tests.
We need to address in database with custom query. We will do this through the class app/services/time_zone_service.rb:
the
And finally, the main class of the app/time_zone_api.rb:
the
That's all! The service is ready. To test it "live" can be running Grape-app:
the
related Links
Framework Grape
Postgis
Code project at Github
Article based on information from habrahabr.ru
Of course, there are services (like The Google Time Zone), but they all paid or are severely limited in functionality. So I decided to write my own.
The service should be as simple as possible. We need to make only one request of the form
the
http://host/timezone/name?lat=55.2341&lng=43.23352
Where lat is the latitude, lng — longitude.
Nastraivaet database
As the database will use PostgreSQL. We will also need extension Postgis custom-tailored to work with geographic objects.
We assume that PostgreSQL is already installed. If not, the Internet has a lot of guides and tutorials on how to do it. The process of installing Postgis also should not cause difficulties on the official website has detailed instructions for most popular operating systems.
After installing everything, create a new database, which we will use to determine the time zone. Handling example, I write "tz_service":
the
CREATE DATABASE tz_service
Enable Postgis in your database:
the
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION postgis_tiger_geocoder;
Now we need a Shapefile of all time zones with the efele.net. Download tz_world.zip. The archive is a file tz_world.shp. Shape files contain vector representation of geographic data. But we need to convert it to an SQL dump and roll it to our base "tz_service":
the
$ /usr/lib/postgresql/9.1/bin/shp2pgsql-D tz_world.shp > dump.sql
$ psql -d tz_service -f dump.sql
Ready! I approve the request:
the
SELECT tzid FROM tz_world WHERE ST_Contains(the_geom, ST_MakePoint(-122.420706, 37.776685));
You should get something like this:
the
tzid
---------------------
America/Los_Angeles
(1 ROW)
Write sevris on Ruby
As a skeleton service will use the framework Grape. It is great to quickly write REST-like server applications.
First, create a Gemfile and write there the gems we need:
the
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
What is the group development and test is necessary only for development and in production mode will not be used. But we need to develop not so much:
— shotgun that would not restart every time the server after the next code change
— buebug and pry for debugging
— rspec for tests
Install all gems with dependencies:
the
$ bundle install
The project tree should look like this:
Let's go in order. Let's start with the configs.
In config/database.yml will contain information for communication with the database:
the
development &config
adapter: postgresql
host: localhost
username: user
password: password
database: tz_service
encoding: utf8
test:
<<: *config
poduction:
<<: *config
Next put the configuration class database config/configuration.rb for parsing yaml file:
the
class Configuration
DB_CONFIG = YAML.load_file(File.expand_path('../database.yml', __FILE__))[ENV['NOT']]
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
In the app/environment.rb will contain environment settings:
the
require 'bundler'
Bundler.require(:default)
$: << File.expand_path('../', __FILE__)
$: << File.expand_path('../../', __FILE__)
$: << File.expand_path('../../config', __FILE__)
$: << File.expand_path('../services', __FILE__)
ENV['NOT'] ||= 'development'
require 'grape'
require 'json'
require 'pry'
require 'active_record'
require 'timezone_name_service'
require 'configuration'
require 'time_zone_api'
In app/application.rb set configure activerecord to connect to the database:
the
ActiveRecord::Base.establish_connection(
adapter Configuration.adapter,
host: Configuration.host
database: Configuration.database
username: Configuration.username
password: Configuration.password
encoding: Configuration.encoding
)
The basis for the service ready, it is only necessary to write one class of the service, which will answer our request and all. All? No! First you need to write tests. Do not forget about TDD.
Sosedin spec/spec_helper.rb and his moods a bit:
the
ENV['NOT'] ||= '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
In the tests we shall describe the behaviour of the service. But we expect only two things:
1. An adequate response with the appropriate parameters in the query
2. Error when no parameter
Describe it:
the
describe 'API' do
let(:params) {
{
lat: 55.7914056,
lng: 49.1120427
}
} #send the parameters in the query
let(:error) {
{ error: 'lat is missing, lng is missing' }
} #the expected answer error parsing parameters
let(:name_response) {
{ timezone: 'Europe/Moscow' }
} #the expected response on successful request
#Home
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
#Description of the process of getting the name of the time zone
describe 'Timezone name' do
subject {
last_response
}
#Description of various situations in contexts
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
By running the command:
the
$ bundle exec rspec
No test will pass. Another would be =) Have to zaselenie tests.
We need to address in database with custom query. We will do this through the class app/services/time_zone_service.rb:
the
class TimezoneNameService
def initialize(lat, lng)
@lat = lat
@lng = lng
end
def name
#"Non-standard" request. To screen coordinates does not make sense, because validation will occur when parsing
sql = "SELECT tzid FROM tz_world WHERE ST_Contains(geom, ST_MakePoint(#{ActiveRecord::Base.sanitize(@lang)}, #{ActiveRecord::Base.sanitize(@lat)}));"
name_hash = ActiveRecord::Base.connection.execute(sql).first
name_hash['tzid'] rescue nil
end
end
And finally, the main class of the app/time_zone_api.rb:
the
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 #Validation settings
get '/name' do
name = TimezoneNameService.new(params[:lat], params[:lng]).name
{ timezone-name }
end
end
end
That's all! The service is ready. To test it "live" can be running Grape-app:
the
$ bundle exec rackup
related Links
Framework Grape
Postgis
Code project at Github
Комментарии
Отправить комментарий