【Rails】request.remote_ipの値がプライベートネットワークの通信で120.0.0.1になる

https://qiita.com/yasu/items/da7ebdb01cb3209583df こちら参考にした。

環境

Rails 4.2.10

結論

config/application.rbに以下を追加すればOK

require 'ipaddr'

module Kimromi
  class Application < Rails::Application
    # これを追加
    config.action_dispatch.trusted_proxies = %w(127.0.0.1 ::1).map { |proxy| IPAddr.new(proxy) }
  end
end

原因

request.remote_ipにはTRUSTED_PROXIESというプライベートネットワークのIPアドレス帯を除外する機能がある。

https://github.com/rails/rails/blob/v4.2.10/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L33-L40

    TRUSTED_PROXIES = [
      "127.0.0.1",      # localhost IPv4
      "::1",            # localhost IPv6
      "fc00::/7",       # private IPv6 range fc00::/7
      "10.0.0.0/8",     # private IPv4 range 10.x.x.x
      "172.16.0.0/12",  # private IPv4 range 172.16.0.0 .. 172.31.255.255
      "192.168.0.0/16", # private IPv4 range 192.168.x.x
    ].map { |proxy| IPAddr.new(proxy) }

今回の自分の環境の通信は10.*.*.*だったので、この"10.0.0.0/8"に掛かり除外され、request.remote_ipとして127.0.0.1だけが残る形となっていた。

なお、TRUSTED_PROXIESの設定はconfig.action_dispatch.trusted_proxiesで上書きできる。プライベートネットワーク帯の除外は自環境では特に必要なかったので、ローカル通信の127.0.0.1::1だけ残した形で以下のように設定してOKだった。

config.action_dispatch.trusted_proxies = %w(127.0.0.1 ::1).map { |proxy| IPAddr.new(proxy) }

しかしRailsのログが127.0.0.1のまま

Started GET "/" for 127.0.0.1 at 2018-04-23 20:01:42 +0900

こんな感じでRailsのログには127.0.0.1のまま出力されていた。ただこれは既知の問題だったようで、本家のRailsの方で修正されていた。

github.com

ソースコードを追ってみるとrails v5.0.7には当たっていなく、v5.1.0には当たっていたのでv5.1.0以上にあげる治っていそう。 https://github.com/rails/rails/blob/v5.1.0/railties/lib/rails/application/default_middleware_stack.rb

早くRails5にしたい。

php-activerecordのすすめ

GMOペパボ Advent Calendar 2017の初日のエントリーです!

私が所属しているムームードメインではPHP環境とRuby on Railsが並行で運用されています。その中でPHP環境はが統一されたWebフレームワークを使用しておらず、データベース接続のクエリビルダーも自作されていて、書き方が冗長だったり、気をつけないとSQLインジェクションの危険がでるようなものであったので、何かいいクエリビルダーはないかと探していました。他のチームではpixieを導入していたりしていましたが、なにかもっと書いてて楽しくなるようなものはないか、RailsもやっているしActiveRecordライクに書けるものがないか探りました。

そこで見つけたのがphp-activerecordです。使っていてよくできてるなと感じましたので今回は使い方を紹介しようと思います。

インストール

$ composer require php-activerecord/php-activerecord

PHP 5.3以上で、PDOでクエリ実行はPrepared Statementを使用しています。

接続設定

<?php
ActiveRecord\Config::initialize(function($cfg)
{
   $cfg->set_model_directory('/path/to/your/model_directory');
   $cfg->set_connections(
     [
       'development' => 'mysql://username:password@localhost/development_database_name',
       'test' => 'mysql://username:password@localhost/test_database_name',
       'production' => 'mysql://username:password@localhost/production_database_name'
     ]
   );
   $cfg->set_default_connection('production');
});
  • set_model_directory()でmodelのファイル群をおいているディレクトリを指定すると自動でrequire_onceしてくれます(source)
  • set_connections()で環境ごとの接続設定をURLのようなフォーマットで指定します。MySQLSQLitePostgreSQLOracleデータベースに対応しています。(source)
  • set_default_connection()で環境を指定します。指定しない場合はdevelopmentです。(source)

Modelの作成

下のようなpeopleテーブルがあったとします。

create table people(
  id integer not null primary key autoincrement,
  name text,
  created_at datetime,
  updated_at datetime
);

こんなmodelをかけば準備完了です。model名は単数形(Person)で、テーブル名は複数形(people)になっているところはRailsActiveRecordと同じで、そのルールに乗れない場合はstatic $table_name = 't_people';のようにテーブル名をmodelの中でかけばOKです。

<?php
class Person extends ActiveRecord\Model
{
}

ちなみにphp-activerecordで複数形<->単数形の変換はどうやってるのか調べたところ、ActiveSupportのinflectionsアルゴリズムを参考に、ライブラリ内で実装を書いていました。(source)

CRUD

Create

<?php
Person::create(['name' => 'kimromi1']);

$p = new Person();
$p->name = 'kimromi2';
$p->save();

created_atupdate_atカラムがあれば自動で日時を挿入します。(source)

Read

<?php
Person::all();
Person::all(['conditions' => ['name like ?', 'kimromi%']]);  // where
Person::count();
Person::first();
Person::last();
Person::find(1);  // find by id
Person::find_by_name('kimromi2');  // find_by_[カラム名]のメソッドで検索できる
Person::find_by_sql('select * from people');  // raw SQL

Person::all(['limit' => 10, 'offset' => 5)])
Person::all(['order' => 'name desc'])
Person::all(['group' => 'name', 'having' => 'length(name) > 7'])

Update

<?php
$p = Person::first();
$p->name = 'kimromi3';
$p->save();

$p->update_attributes([
    'name' => 'kimromi4'
]);

update_atカラムも更新します。

Delete

<?php
$p = Person::first();
$p->delete();  // 1件

Person::delete_all();  // 全件
Person::delete_all(['conditions' => ['name = ?', 'kimromi4']]);  // 条件指定

見覚えのある感じですね。

Query Log

log()という名前のメソッドを持っているクラスをセットしておくとCRUDのたびにクエリログを吐いてくれます。

<?php
class Logger
{
    public function log($a) {
        var_dump($a);
    }
}
<?php
ActiveRecord\Config::initialize(function($cfg)
{
   $cfg->set_model_directory('/path/to/your/model_directory');
   $cfg->set_connections(['development' => 'mysql://username:password@localhost/development_database_name']);

   $cfg->set_logging(true);
   $cfg->set_logger(new Logger());
});

Associations

Railsと同じhas_manyhas_onebelongs_toの関連をもつ機能があります。

<?php
class Person extends ActiveRecord\Model
{
    static $has_many = [
        ['domains'],
    ];
}

class Domain extends ActiveRecord\Model
{
    static $belongs_to = [
        ['person'],
    ];
}
<?php
$p = Person::first();
$p->domains;  // DomainインスタンスのArrayで返る

$d = $p->create_domain(['sld' => 'kimromi', 'tld' => 'com']);
$d->person;

Validations

modelのバリデーションもあります。

<?php
class Person extends ActiveRecord\Model
{
    static $validates_presence_of = [
        ['name']
    ];
}
  • validates_presence_of : null or 空文字以外
  • validates_size_of / validates_length_of : 長さ
  • validates_inclusion_of / validates_exclusion_of : 指定した配列に含まれるか/含まれないか
  • validates_format_of : 指定した正規表現に当てはまるか(preg_matchで検証)
  • validates_numericality_of : 数値かどうか
  • validates_uniqueness_of : 一意であるかどうか
  • validate() : カスタムメソッドで検証

Callbacks

before_after_のcallbackも実装されています。

  • before_save : called before a model is saved
  • before_create : called before a NEW model is to be inserted into the database
  • before_update : called before an existing model has been saved
  • before_validation : called before running validators
  • before_validation_on_create : called before validation on a NEW model being inserted
  • before_validation_on_update : same as above except for an existing model being saved
  • before_destroy : called before a model has been deleted
  • after_save : called after a model is saved
  • after_create : called after a NEW model has been inserted into the database
  • after_update : called after an existing model has been saved
  • after_validation : called after running validators
  • after_validation_on_create : called after validation on a NEW model being inserted
  • after_validation_on_update : same as above except for an existing model being saved
  • after_destroy : called after a model has been deleted

その他

Cache

クエリ実行結果を一定時間(デフォルトは30sec)メモリにキャッシュすることができ、高速化を図れます。キャッシュのキーは[テーブル名]-[主キーの値]になっており、レコードごとにキャッシュしているようでした。(source)

<?php
ActiveRecord\Config::initialize(function($cfg)
{
   $cfg->set_model_directory('/path/to/your/model_directory');
   $cfg->set_connections(['development' => 'mysql://username:password@localhost/development_database_name']);

   // Memcacheモジュールのみ対応...
   $cfg->set_cache("memcache://localhost:11211", ["expire" => 60]);
});

class Person extends ActiveRecord\Model
{
    // キャッシュするmodel内でフラグをON
    static $cache = true;
}

Transaction

Model::transaction()に無名関数を渡せば、関数内の処理が始まるときにtransactionを張り、処理が正常に終了するとcommitします。処理の途中でExceptionがスローされるか、戻り値にfalseが返るとrollbackされます。(source)

<?php
Person::transaction(function()
{
    $p = Person::create(['name' => 'kimromi']);
    $p->create_domain(['sld' => 'kimromi', 'tld' => 'com']);

    // throw new Exception("rollback!");  # rollback
    // return false;  # rollback
});

Aliased attributes

カラム名の別名をつけるalias_attributeも用意されています。(これが地味に一番助かる...)

<?php
class Person extends ActiveRecord\Model
{
    // nameカラムをnick_nameで扱えるようになる
    static $alias_attribute = [
        'nick_name' => 'name',
    ]
}

Serialization

modelクラスをjsonxmlシリアライズできます。

<?php
$p = Person::create(['name' => 'kimromi']);

$p->to_json();
# => {"id":"1","name":"kimromi","created_at":"2017-11-30T17:22:01+0000","updated_at":"2017-11-30T17:22:01+0000"}

$p->to_array();
# =>
# {
#   ["id"]=>
#   string(1) "1"
#   ["name"]=>
#   string(8) "kimromi"
#   ["created_at"]=>
#   string(24) "2017-11-30T17:23:08+0000"
#   ["updated_at"]=>
#   string(24) "2017-11-30T17:23:08+0000"
# }

$p->to_csv();
# => 1,kimromi,2017-11-30T17:24:11+0000,2017-11-30T17:24:11+0000

$p->to_xml();
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <person>
#   <id>1</id>
#   <name>kimromi</name>
#   <created_at>2017-11-30T17:24:37+0000</created_at>
#   <updated_at>2017-11-30T17:24:37+0000</updated_at>
# </person>

ActiveRecordに比べてちょっと不便なところ

RailsActiveRecordに比べて当然機能は落ちますが、その中でもちょっと不便に感じているところです。

  • has_manyで指定したテーブルを取得したときの値がArray型なため、2階層以上の関連テーブルを検索するのに苦労する(relationを使わずjoinでとらないといけない)
  • enumがないため、連番のカラムが管理しにくい
  • CacheがMemcacheモジュールしか対応してなく、Memcachedモジュールに対応していない
  • 開発があまり活発でなく枯れ気味

最近は目立った新規機能もあまりないため、Forkして独自で機能あげていこうかなと考えています。

まとめ

今回はphp-activerecordの使い方を紹介しました。最近はPHPのWebフレームワークも増えてきたし使う機会はあまり多くなさそうですが、日本語のドキュメントが少なかったのでまとめてみました。ライブラリとして思ったより機能も多く実装されていたので内部実装を追うのも勉強になりそうです。PHP Advent Calendar 2017にも参加しているので次回第2弾としてパフォーマンスの調査や内部実装の話などしたいと思っています。

(チームの人がこのエントリを見ながら便利!とPHPをガンガン書いてくれますように😌 )

明日は@kuroyamさんです! -> GMOペパボ Advent Calendar 2017

「ぼくのかんがえたさいきょうのPull Request」というテーマで発表しました

8/28(月)夜、第9回ペパボテックカンファレンスにてVue.js on Railsというテーマで登壇したのですが、その2時間前、GMOペパボに今年4月に入社した新卒7期生向けに座学を行いました。テーマは自由ということで、これまでの経験してきたエンジニア生活の中で培ったチーム開発におけるPull Requestについての考え方やTipsを1時間お話ししました。

当日の資料はこちらです。

内容としては

  • マージできるPull Requestがさいきょうだ
  • 根拠が説明できるようにしておこう
  • Pull Requestは可能な限り小さくしよう
  • Pull Requestを構成する各項目について説明

新卒の方々、タイムリーで悩んでいた内容だったらしく質問もいただきお役に立てたようで安心しました。

1日に2発表は精神的にも体力的にもきつかった・・・