ryoma's note

マイペース ੯•́ʔ̋ ͙͛*͛ ͙͛*͛ ͙͛̋و

ON 句に検索条件を書くとき

業務でクエリを読んでいたところ、ON 句には結合条件だけではなく検索条件も書けることを知った。WHERE 句に書く場合との違いはあるのかなと調べてみると、外部結合で利用すると抽出結果に差異が生じることがあるので注意する必要があるみたい。

外部結合で WHERE 句に検索条件を記述

SELECT
  * 
FROM
  users 
  LEFT JOIN prefectures 
    ON users.prefecture_id = prefectures.id 
WHERE
  prefectures.id = 5;
+------+---------------+--------+------+-----------+
| id   | prefecture_id | name   | id   | name      |
+------+---------------+--------+------+-----------+
|    1 |             5 | イヌ   |    5 | 秋田県    |
+------+---------------+--------+------+-----------+

外部結合で ON 句に検索条件を記述

SELECT
  * 
FROM
  users 
  LEFT JOIN prefectures 
    ON users.prefecture_id = prefectures.id 
    AND prefectures.id = 5;
+------+---------------+--------+------+-----------+
| id   | prefecture_id | name   | id   | name      |
+------+---------------+--------+------+-----------+
|    1 |             5 | イヌ   |    5 | 秋田県    |
|    2 |             9 | サル   | NULL | NULL      |
|    3 |            33 | キジ   | NULL | NULL      |
+------+---------------+--------+------+-----------+

ON 句の場合は、テーブルを結合する前に検索条件の絞り込みが行われるので、左側の軸となるテーブルのレコードがすべて表示されている。個人的に ON 句には結合条件だけ書くようにして、意図しない結果にならないように気をつけようと思う。

API Client の Gem を作った

日頃いろいろな Web API のお世話になっているのですが、API Client の仕組みをきちんと理解したかったので Gem を作成してみました。

法人情報を調べるときにいつも利用している gBizINFO (METI)経済産業省 が Web API を提供していることを知りました。現在 SPARQL と REST の2つのエンドポイントが公開されていますが、今回作成した Gem では REST API のみ対応しています。

gBizINFO REST API の利用には、あらかじめ利用申請を行う必要があります。発行されたアクセストークンを使用します。

つくったもの

つかいかた

client = Gbizinfo::Client.new(
  token: 'xxxxxxxxxx'
)

# 法人を検索する
options = { name: '○○株式会社' }
client.hojin_search(options)

# 法人基本情報を取得する
client.hojin(corporate_number: 'xxxxxxxxxxxxx')

作成にあたって、いつも利用している Gem のソースコードを読んで API Client に必要な処理を学んだり、設計を参考にさせてもらいながら実装を進めました。試行錯誤のなかで API Client の仕組みについて理解が進んだのでよかったです。もっと気軽に作成できるように慣れていきたいと思います。

reCAPTCHA v3を試してみる

reCAPTCHA は手軽に導入できるらしいと聞いたので、ローカル環境で試しに動かしてみる。

現在は reCAPTCHA v3 と v2 から選択して利用できる。v3 は利用者またはボットの行動を数値化し、基準値を満たしているかで検証する。v2 は利用者に「私はロボットじゃないよ」のチェックボックス等を操作してもらって検証を行うみたい。

reCAPTCHA v3 verifies requests with a score and gives you the ability to take action in the context of your site.
reCAPTCHA v2 verifies if an interaction is legitimate with the “I am not a robot” checkbox and invisible reCAPTCHA badge challenges.

今回は v3 と v2 の両方を試したいので、それぞれでサイトを登録して API キー(サイトキーとシークレットキー)を発行する。ローカル環境の場合、ドメインには「localhost」を登録する。

Ruby on Rails の Web アプリケーションへの導入を検討しているので、yasslab/sample_apps のサンプルアプリケーションをお借りする。また、ambethia/recaptcha という便利な Gem が公開されていたので使わせてもらう。

README を読んでみると、初回は v3 で検証を行うが、失敗した場合に v2 のチェックボックス等を表示させてフォールバックできる方法が紹介されていた。v3 は便利だが検証の精度が気になっていたので、その通りに実装する。

# .env
RECAPTCHA_SITE_KEY=***
RECAPTCHA_SECRET_KEY=***
RECAPTCHA_SITE_KEY_V3=***
RECAPTCHA_SECRET_KEY_V3=***
# config/initializers/recaptcha.rb
Recaptcha.configure do |config|
  config.site_key = ENV['RECAPTCHA_SITE_KEY']
  config.secret_key = ENV['RECAPTCHA_SECRET_KEY']
end
# app/controllers/sessions_controller.rb
def create
  success = verify_recaptcha(action: 'login', minimum_score: 0.5, secret_key: ENV['RECAPTCHA_SECRET_KEY_V3'])
  checkbox_success = verify_recaptcha unless success
  if success || checkbox_success
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ログイン成功
    else
      # ログイン失敗
    end
  else
    if !success
      @show_checkbox_recaptcha = true
    end
    render 'new'
  end
end
# app/views/sessions/new.html.erb
<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
  …
  <% if @show_checkbox_recaptcha %>
    <%= recaptcha_tags %>
  <% else %>
    <%= recaptcha_v3(action: 'login', site_key: ENV['RECAPTCHA_SITE_KEY_V3']) %>
  <% end %>
  <%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>

通常は v3 で検証を行うが、スコアが基準値を下回るなどして検証に失敗すると v2 のチェックボックスが表示されるログインフォームになった。

f:id:ryoma123:20211116000921p:plainf:id:ryoma123:20211116000924p:plain
reCAPTCHA v3 と v2 の表示例

自動判定されるスコアの精度が気になっていたが、この方法であれば v2 で救済できるので気軽に導入を試してみてもよさそうに感じた。実装も Gem のおかげで手軽でした 🙏

Laravelのファサードを作ってみる

PHP フレームワークの基礎力を養うために、所属するチームのメンバーと一緒に Laravel の読書会を始めた。 先日ファサードに関する章を読んだが、理解度がいまいちだったので、簡単なファサードを作成して動かしてみた。

はじめに、文字列を返すだけのメソッドを持つ Greet クラスを app/Services/Greet.php に用意する。

<?php

namespace App\Services;

class Greet
{
    public function getGreet(string $name)
    {
        return 'Hello, ' . $name;
    }
}

つぎに、Artisan コマンドを使ってサービスプロバイダを作成する。
この場合は app/Providers/GreetServiceProvider.php が自動作成される。

$ php artisan make:provider GreetServiceProvider
Provider created successfully.
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class GreetServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

register メソッドを書き換えて、App\Services\Greet クラスをサービスコンテナにバインドする。

    public function register()
    {
        app()->singleton('greet', 'App\Services\Greet');
    }

config/app.php の providers プロパティに追記して、サービスプロバイダを定義する。

    'providers' => [
        // 省略
        App\Providers\GreetServiceProvider::class,
    ],

同じ設定ファイルの aliases キーにファサードで利用するクラスを追加する。

    'aliases' => [
        // 省略
        'Greet' => App\Facades\Greet::class,
    ],

app/Facades/Greet.php に Facade クラスを作成する。Illuminate\Support\Facades\Facade を継承することで、実装されていないクラスメソッドが呼ばれた場合は、スーパークラス__callStatic というマジックメソッドを経由して、サービスコンテナから取得したインスタンスに対してメソッド実行してくれる。

<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Greet extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'greet';
    }
}
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

ファサードを利用して文字列を取得できた 🙌

echo Greet::getGreet('Goodbye'); // Hello, Goodbye

ファサードの処理の流れをイメージできた気がする。引き続きみんなで読書していきます 🚀

PHPフレームワーク Laravel Webアプリケーション開発 バージョン8.x対応

自分の仕事を支えるツールをつくる

GMOペパボ Advent Calendar 2019 - Qiita 13日目の記事です。

昨日は tosite0345 さんの コミュニティを支える技術 でした。

今年をふりかえると、自分の仕事がすこしだけ楽になるようなツールを作って OSS として公開することにチャレンジしていました。今日は最近つくったツールの紹介と、そのような取り組みをはじめたきっかけについて書きたいと思います。


最近

業務にて不正利用が疑わしい海外の電話番号を見かけたときに、国番号を調べたことがありました。特別手間ではないけれど、検索しなくても済むようにしてみようかと思って、電話番号の地理情報を調べる CLI ツールを作りました。実装においては google/libphonenumberサードパーティをお借りしています 🙏

つくったもの

つかいかた

$ phong 0363849000
🇯🇵  Japan -- Tokyo

$ phong +41446681800
🇨🇭  Switzerland -- Zurich

$ phong -r US 6502530000
🇺🇸  United States of America -- Mountain View, CA

 


ツールを作るようになったきっかけ

自分はアウトプットすることに苦手意識を持っており、所属する事業部の CTL (2018年当時)に相談をしました。そのときに聞いた話は 僕どうやってコードかけるようになったんだっけ のエントリにも書かれていたので引用します。

僕どうやってコードかけるようになったんだっけって思うと、元々ペパボには簡単なシェルスクリプトとか、Railsでscaffoldかけるくらいなレベルで入社してきて、多分最初に買った本はリーダブルコードだったように思う。それを読んでふんふん言いながら雰囲気でコード書いてた。そのうち自分でOSS書きたいと思って、謎のAlias登録gemを作ったりしたんだっけ。

何でもいいから、自分が繰り返してる作業を楽にするものを作ったらいいよーとアドバイスを受けました。その時に素直にチャレンジしてみようと思えたのは、雲の上に見えているエンジニアでもそりゃ最初からすごかった訳じゃないよねと気付かされて、言い訳できないやという気持ちになったから。

当時、あんちぽさんが使ってくれてとても嬉しく感じたのを覚えている。実はいい人なのかもしれない。

「拙いコードだったけど CTO が使ってくれて、感想をもらえて嬉しかった」という話にもグッと来ました。ペパボの組織文化としてチャレンジしたことを褒め合う慣習があり、まさにそれでいい話だなと思った。

コードを書く

RubyGems での公開方法を調べたり、Go 製の OSS をリーディングして見様見真似で書いてみたりと、自分にとってはじめての経験をすることができました。成果物はささいなものだけど、頭を抱えながら手を動かしている時間はとても楽しく充実していました。ペパボの人たちが感想をくれたりして、自分もそんな組織文化に対してお返ししていきたいなと思った。

ryoma123.hatenablog.com

ryoma123.hatenablog.com

さいごに

職業なので自然とコードを書いたりアウトプットしているのが本来の姿かもしれないけれど、自分にはきっかけが必要でした。当たり前になるまで続けていきたいな。つくることを素直に楽しんでいこうと思います!

明日は litencatt さんです! 😸

AWS認定ソリューションアーキテクトアソシエイトを取得した

先日、AWS 認定ソリューションアーキテクトアソシエイトの試験を受けてきて合格しました。

最近の業務でクラウドサービスの運用をよくしているという訳ではないのだけど、ホスティングサービスのバックエンドエンジニアを担当している身として、利用者視点でクラウドの強みを理解しておくことは大事だよな〜と思ったのがきっかけでした。プライベートの開発でもクラウドサービスを利用していきたいなと思っていたので、その点でも都合がよかった。資格取得は手近な目標として設定しやすいし、体系的にインプットする手段として便利だった。

学習にあたって AWS の主要サービスの一部機能(ロードバランシングやオートスケール、DNS フェイルオーバーなど)を実際に動かしてみて「かんたん便利!」となったものの、まだまだ実務レベルの技能があるとは言えないので触る機会をつくって身につけていこうと思っている。

やったこと

最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本

最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本

結果

f:id:ryoma123:20190812182304p:plain f:id:ryoma123:20190812182310p:plain

感想

クラウドにおけるアーキテクチャの設計思想を学べてよかった。障害は起こるものとして可用性の高い設計によってダウンタイムを最小化したり、ビジネスに集中できるようにサーバ構築や運用の手間を省力化したり。日頃の開発においても大事な考え方に触れられたので役立てていけるといいな。せっかく学んだので AWS 活用していくぞ 💪

Goでdicというツールを作った

ホスティングサービスの運用をしていると、dig コマンドの力を借りてドメイン情報をしらべる機会がよくあります。

たとえばウェブサイト表示に問題があったり、ブラウザから SSL 証明書を確認できなかったり、メールの不通で困っているようなお問合せに関する調査です。トラブルシューティングの際に、自社管理をふくむ複数の DNS サーバーに登録されているレコード情報が誤っていないかをしらべる必要があります。

また特定のレコード情報を更新したあとは、DNS キャッシュサーバに問い合わせると TTL の影響を受けるので、権威 DNS サーバやパブリック DNS に対して問い合わせをすることになります。

クエリタイプやDNS サーバをコマンド引数で都度わたしてあげる作業がすこし手間だったので、あらかじめ知りたい条件を定義した config ファイルに沿って、ドメイン情報を集めてくれる dic *1 という CLI ツールを作りました。

並行処理がどうしても必要になりそうだったのと、以前から勉強してみたかった言語だったので、今回は Go でつくることを選択しました。

つくったもの

つかいかた

config ファイルを TOML で記述。

# config.toml
[[sec]]
  name = "demo"

  [[sec.args]]
    server = ""
    qtypes = ["a", "txt"]

  [[sec.args]]
    server = "1.1.1.1"
    qtypes = ["a", "txt"]

  [[sec.args]]
    server = "ns"
    qtypes = ["a", "txt"]

[[sec]]
  name = "demo2"

  [[sec.args]]
    server = ""
    qtypes = ["any"]

デフォルトで利用するセクションを設定。(一時的な利用は -n, --name

$ dic e
$ dic s demo
Changed the default section to "demo"
$ dic l
DEFAULT SECTION
 demo

SECTION NAME        SERVER          QUERY TYPES
*demo               -               [a txt]
                    1.1.1.1         [a txt]
                    ns              [a txt]

 demo2              -               [any]
$ dic example.com www.example.com
[example.com]
  -
    example.com.        40170   IN      A       93.184.216.34
    example.com.        86400   IN      TXT     "v=spf1 -all"
  @1.1.1.1
    example.com.        3200    IN      A       93.184.216.34
    example.com.        5668    IN      TXT     "v=spf1 -all"
 *@a.iana-servers.net.
    example.com.        86400   IN      A       93.184.216.34
    example.com.        86400   IN      TXT     "v=spf1 -all"

[www.example.com]
  -
    www.example.com.    66099   IN      A       93.184.216.34
    www.example.com.    86388   IN      TXT     "v=spf1 -all"
  @1.1.1.1
    www.example.com.    9276    IN      A       93.184.216.34
    www.example.com.    10788   IN      TXT     "v=spf1 -all"
 *@b.iana-servers.net.
    www.example.com.    86400   IN      A       93.184.216.34
    www.example.com.    86400   IN      TXT     "v=spf1 -all"

おまけ

config ファイルに server = "ns" と記述すると、 NS レコード(サブドメインの場合はそのスーパードメインに設定される NS レコード)の DNS サーバに対して問い合わせを行います。用途として、レコード情報の更新や DNS サーバをお引越しした直後に、 DNS キャッシュサーバの TTL を待つことなく正常に変更されたことを確認したかった 👀

また server = "" のように空欄にすると resolv.conf を利用します。

ref. https://github.com/ryoma123/dic/blob/master/README.md#example

感想

  • ずっと気になっていた Go の学習機会をつくれてよかった!
  • シンプルで書きやすいところが大好きになってしまったのでもっと触っていこう〜 🙌
  • 正しい設計がわからずリファクタリングにだいぶ苦労してしまったので、Go のデザインパターンなどから学んでみようかなぁと思っている

*1:dic: Domain Infomation Collector