🌐
【Rails】GeoLite2を使ってIPアドレスから都道府県を特定する
前提知識
- Railsで簡単なCRUDアプリを作れる
- GeoIP2のことを理解している
今回触れないこと
- gemのインストール方法
- active_hashの説明
- CRUDの実装
- GeoLiteの会員登録方法
コード実装
controllers/concerns/geoip_module.rbを実装する
- geoip_module.rbを以下のように実装する
- 環境変数は適宜変更してください
app/controllers/concerns/geoip_module.rb
module GeoipModule
def get_to_geoip(ip)
uri = URI.parse("https://geolite.info/geoip/v2.1/city/#{ip}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Get.new(uri)
# 環境変数には会員登録時に発行されるIDとパスワードを設定する
req.basic_auth(ENV['ID'], ENV['PASSWORD'])
prefecture = http.request(req)
JSON.parse(prefecture.body.encode('SJIS', 'UTF-8', invalid: :replace, undef: :replace, replace: '')&.encode('UTF-8'), symbolize_names: true)
end
end
models/prefecture.rbを実装する
- prefecture.rbを以下のように実装する
app/models/prefecture.rb
class Prefecture < ActiveHash::Base
#-----------------------
# Active_hash
#-----------------------
self.data = [
{id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
{id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
{id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
{id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
{id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
{id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
{id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
{id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
{id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
{id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
{id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
{id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
{id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
{id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
{id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
{id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
]
#-----------------------
# Scope
#-----------------------
scope :get_prefecture_name, ->(prefecture_name) { find_by(name: prefecture_name) }
end
controllers/article_controller.rbを実装する
- article_controller.rbを以下のように実装する
app/controllers/article_controller.rb
class ArticleController < ApplicationController
include GeoipModule
def create
@articles = Article.new(article_params)
begin
remote_ip = request.env["HTTP_X_FORWARDED_FOR"]&.to_s&.split(', ')&.first || request.remote_ip
subdivisions = get_to_geoip(remote_ip)&.dig(:subdivisions)
prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
@articles.get_prefecture_id = Prefecture.get_prefecture_name(prefecture_name)&.id
rescue => e
Rails.logger.error(e)
Rails.logger.error(e.backtrace.join("\n"))
end
if @articles.save
redirect_to home_index_path
else
render "new"
end
end
end
controllers/article_controller.rbの解説
※今回remote_ipの中には東京都のIPアドレスが入っていることを想定しています。
まずget_to_geoip(remote_ip)
でGeoIPにリクエストを投げるとレスポンスとして以下のような値が返ってきます。
{:city=>{:geoname_id=>1850147, :names=>{:"zh-CN"=>"京", :de=>"Tokio", :en=>"Tokyo", :es=>"Tokio", :fr=>"Tokyo", :ja=>"東京", :"pt-BR"=>"Tquio", :ru=>"Токио"}},
:continent=>{:code=>"AS", :geoname_id=>6255147, :names=>{:"zh-CN"=>"洲", :de=>"Asien", :en=>"Asia", :es=>"Asia", :fr=>"Asie", :ja=>"アジア", :"pt-BR"=>"sia", :ru=>"Азия"}},
:country=>{:iso_code=>"JP", :geoname_id=>1861060, :names=>{:fr=>"Japon", :ja=>"日本", :"pt-BR"=>"Japo", :ru=>"Япония", :"zh-CN"=>"日本", :de=>"Japan", :en=>"Japan", :es=>"Japn"}},
:location=>{:accuracy_radius=>50, :latitude=>35.6893, :longitude=>139.6899, :time_zone=>"Asia/Tokyo"}, :postal=>{:code=>"102-0082"},
:registered_country=>{:iso_code=>"JP", :geoname_id=>1861060, :names=>{:en=>"Japan", :es=>"Japn", :fr=>"Japon", :ja=>"日本", :"pt-BR"=>"Japo", :ru=>"Япония", :"zh-CN"=>"日本", :de=>"Japan"}},
:subdivisions=>[{:iso_code=>"13", :geoname_id=>1850144, :names=>{:ja=>"東京都", :en=>"Tokyo", :fr=>"Prfecture de Tokyo"}}]}
今回は、都市名"東京都"を取得したいのでget_to_geoip(remote_ip)&.dig(:subdivisions)
で取得した値をsubdivisions変数の中に格納しています。
変数の中身は以下の通りになっているかと思います。
subdivisions = [{:iso_code=>"13", :geoname_id=>1850144, :names=>{:en=>"Tokyo", :fr=>"Prfecture de Tokyo", :ja=>"東京都"}}]
次にprefecture_name変数にsubdivisions.present?
がtrueの場合は都市名を
falseの場合にはnil返す処理をしています。
# trueの場合
prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
→ "東京都"
# falseの場合
prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
→ nil
都市名を取得できたらprefecture.rbで定義したスコープget_prefecture_name
の引数にprefecture_nameを渡せば...
# 今回はidが欲しいのでidを取得
Prefecture.get_prefecture_by_name(prefecture_name)&.id
→ 13
あとは@articles.save
で無事保存されていることが確認できたら完成です!
request.env["HTTP_X_FORWARDED_FOR"]
について
- Ruby on Railsではwebサーバーの種類によりますが以下のコードIPの取得ができます
request.env["HTTP_X_FORWARDED_FOR"] || request.remote_ip
- しかし
request.env["HTTP_X_FORWARDED_FOR"]
ではカンマ区切りで複数のIPが返ってきてしまうので以下の形に書き換えることを推奨します
- request.env["HTTP_X_FORWARDED_FOR"] || request.remote_ip
+ request.env["HTTP_X_FORWARDED_FOR"]&.to_s&.split(', ')&.first || request.remote_ip
また今回のコードではnilが返ってくる可能性が高いのでぼっち演算子を使用しています。
メソッド名が存在しない場合はNoMethodError
が出力されてしまうので注意が必要です。
最後に
不明な点などありましたらお気軽にコメントよろしくお願いします🤲
Twitterのフォローもよろしくお願いします! → https://twitter.com/te_obata
Discussion