スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

開発中にDBに一括で登録するスクリプト

開発中には、以下ような感じでSQLファイル等を分けて管理しています。

db
|- bin
|- erd
|- sql
|- 01schema.sql
|- 02data.sql
|- lib
|- uv_plpgsql.sql
|- ...
|- stored
|- ...
|- table
|- users.sql
|- ...

これを一括でDBに登録するのがめんどくさいです。
なのでスクリプト書きました。
vi bin/config.yaml

default: &default
psql: /usr/bin/psql
base: ../sql
db_host: localhost
db_user: user
db_name: web_development
params:
- name: schema
path: 01schema.sql
command: c
- name: lib
path: lib/*.sql
command: l
- name: table
path: table/*.sql
command: t
- name: stored
path: stored/*.sql
command: s
- name: data
path: 02data.sql
command: d

development:
<<: *default

test:
<<: *default
db_name: web_test


vi bin/make_sql.rb

require 'optparse'
require 'tempfile'
require "yaml"
require "erb"

# ファイルを読み込む
def read_one(path, dst)
open(path) do |file|
dst.puts file.read
end
end

# ディレクトリの中のファイルを読み込む
def read_many(path, dst)
Dir::glob(path).each do |f|
read_one(f, dst)
end
end

# パラメーターをパースする
def parse(params)
ary = []
all = []
params.each do |param|
all << param["command"]
ary << "-#{param["command"]} : #{param["name"]}"
end
ary << "-e : 環境名"
ary << "-#{all.join("")} : all"
[ary.join("\n"), all.join("") + "e:"]
end

# コマンドを生成する
def make_command(environment, file)
"#{environment["psql"]} -h #{environment["db_host"]} -U #{environment["db_user"]} #{environment["db_name"]} < #{file.path}"
end

#
# 処理本体
#

# コンフィグを読み込む
config = open("./config.yaml") do |f|
::YAML::load(::ERB.new(f.read).result)
end

# エラーメッセージと全てのパラメーター取得
error_message, all_parameters = parse(config["development"]["params"])

# 引数が空の時は終了
if ARGV.empty?
puts error_message
exit 0
end
params = ARGV.getopts(all_parameters)

# 環境が無い場合は終了
if params["e"].nil? || "" == params["e"]
puts error_message
exit 0
end

environment = config[params["e"]]
base = environment["base"]

Tempfile.open('makesql') do |file|
targets = environment["params"]
targets.each do |target|
# コマンドが選択されているものだけ処理する
if params[target["command"]]
path = "#{environment["base"]}/#{target["path"]}"
if /\*/ =~ target["path"]
read_many(path, file)
else
read_one(path, file)
end
end
end
file.close
command = make_command(environment, file)
system(command)
end


以下のように実行します。
ruby make_sql.rb -cltsd -e development

RailsでProxy経由の接続で本当のIPアドレスを取得する

WAFなどのプロキシーを通した時にRailsでは発信元のIPを取得することができなくなります。
その対応方法です。

プロキシーのIPアドレスを
1.2.3.4
5.6.7.8
の2つとします。
またAWSのELBではプライベートアドレスで接続してくるので、それも追加しています。

まず、nginxがRailsの前にいる場合はそれから設定します。

set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 127.0.0.1/32;
set_real_ip_from 1.2.3.4/32;
set_real_ip_from 5.6.7.8/32;
real_ip_header X-Forwarded-For;

次にRails側です。
vi config/application.rb
config.action_dispatch.trusted_proxies = /(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)|(1\.2\.3\.4)|(5\.6\.7\.8)/

RailsではIPアドレスの正規表現で設定します。

RailsでPostgreSQLのストアードプロシージャの例外コードを受ける

PostgreSQLのストアードプロシージャで例外を出した時に、素のままではRailsではエラーコードが受け取れません。
RailsというかRubyのpgというドライバーがデフォルトでは受け取らないようになっています。
pgを直接扱っている場合には、簡単に設定できるのですがRailsでは一苦労いります。
initializersにドライバーと例外に手を入れるコードを書きます。

vi config/initializers/stored_procedure.rb

require 'active_record/connection_adapters/postgresql_adapter'

module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
alias :base_configure_connection :configure_connection
def configure_connection
base_configure_connection
@connection.set_error_verbosity(PG::PQERRORS_VERBOSE)
end
end
end
end

module PG
class Result
def error_code
error_message[8..12]
end
end
end


使い方は以下の通り。

def index
begin
TestModel.find_by_sql(["SELECT * FROM test_get_list_data(p_id := :id", {id: 1}])
rescue => e
Rails.logger.debug(e.original_exception.result.error_code)
Rails.logger.debug(e.original_exception.result.error_message)
end
end


例外eはActiveRecord::StatementInvalidのインスタンスでoriginal_exceptionに本当の例外PG::ServerErrorを持っています。
PG::ServerErrorはPG::Resultのインスタンスresultを持っていて、ここのエラーコードが含まれています。
エラーメッセージは以下の通りです。

ERROR: U0001: error occerd p_id=1
LOCATION: exec_stmt_raise, pl_exec.c:3068

アプリケーションとしてはエラーコードだけ欲しいので、PG::Resultに手を入れて
error_codeでメッセージだけを取るようにメソッドを追加しています。

Railsで複数のWebを管理してみる

ユーザが使うWebと管理者が使うWebを作りたい時に別々に作るとModelやViewの共通化がやりにくくなります。
だけど1つにした時にControllerやViewがユーザと管理者と同じ階層に入るのは使いにくいです。
そこで、ControllerとViewはディレクトリを分けて扱うことにします。
Modelはユーザと管理者であまり変わらないので1つの階層で扱います。

例えばprojectを扱う場合以下のコマンドで雛形を生成します。

rails g model projects nm:text
rails g scaffold_controller admin/projects
rails g scaffold_controller user/projects

routeは以下のようにします。

namespace :admin, path: "administrator" do
resources :projects
end
namespace :user, path: "" do
resources :projects
end


管理者は
http://localhost/administrator/projects
ユーザは
http://localhost/projects
でアクセスできます。

階層を好きなように切っても、後でrouteで実際のURLを調整できるのが便利です。
管理者のアクセスではIPアドレス制限したい場合などは
ユーザと管理者で別々にRailsを起動して、nginx等でIPアドレスを制限するのがよいです。

ストアードプロシージャのノウハウ。例外編

複数行返すようなストアードプロシージャの時エラーを返したい場合に行で返すのが難しい場合があります。
そこで例外を使います。

DROP TYPE IF EXISTS type_test_get_list_data CASCADE;
CREATE TYPE type_test_get_list_data AS (
id BIGINT
);

CREATE OR REPLACE FUNCTION test_get_list_data(
p_id BIGINT DEFAULT NULL
) RETURNS SETOF type_test_get_list_data AS $FUNCTION$
DECLARE
BEGIN
IF p_id = 1 THEN
RAISE SQLSTATE 'U0001' USING MESSAGE = 'error occerd p_id=%', p_id;
END IF;
END;
$FUNCTION$ LANGUAGE plpgsql;


例外を出すにはRAISEを使います。
SQLSTATEには例外のコードを、MESSAGEには例外のメッセージが設定できます。

例外のコードは5桁の文字列です。先頭の2桁が種類を、残り3桁がエラーの詳細を意味しているそうですが
別にコードは何を使ってもかまわないのですし、受け取る側が解釈するだけなので適当でもいいです。
ですがPostgreSQLが内部で返すエラーコードが詳細に定義されているので
それに被らないコードが望ましいです。
先頭にUの文字を使っているエラーコードは事前に定義されていないので
私はU0001からコードを振っています。

メッセージには入力パラメーターの情報など渡すと確認しやすくなります。
%を使うことで、いくつも変数をメッセージに付与できます。

アプリケーションでは、メッセージをログにはいたり、エラーコードに合わせた
ユーザ向きのメッセージに変換して画面に表示したりしています。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。