Rustでwebアプリを実装して勉強 その1 - sqliteを使う

Rustを勉強するためにactix-webを使ってwebアプリを書いてみた備忘録。

github.com

今日やったこと

Rust初心者で何も分からないので、actix-web と diesel のGetting Startedを読みながら、雰囲気でコードを書いてみた。

diesel_cli のインストール

DBの作成・マイグレーションなどをするため、cliをインストールする必要がある。

$ cargo install diesel_cli

で普通にインストールできた。 もし postgresql とかがローカルになければ、sqlite だけのインストールもできるらしい。

$ cargo install diesel_cli --no-default-features --features sqlite

DBの作成

DATABASE_URLを指定して diesel setup するとDBが作れる。

$ DATABASE_URL=development.sqlite3 diesel setup

毎回指定するのも面倒なので、.envrc に入れておく。

$ echo export DATABASE_URL=development.sqlite3 > .envrc

マイグレーションファイルの作成

diesel migration generate で作成できる。

$ diesel migration generate create_posts
Creating migrations/2019-10-27-152149_create_posts/up.sql
Creating migrations/2019-10-27-152149_create_posts/down.sql

ActiveRecordみたいにRubyではなくて、生SQLだった。この潔さは嫌いじゃない。

DBのマイグレーション

diesel migration runマイグレーションを実行してくれる。

$ diesel migration run
Running migration 2019-10-27-152149_create_posts

diesel migration redo で down.sql の動作確認もできる。

DBの接続

こんな感じで connection を作る。

pub fn establish_connection() -> SqliteConnection {
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    SqliteConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
}

コネクションの管理を diesel でやっているのか、自分でやる必要あるのかはよく分かっていない。

複数のPostを取得する

こんな感じでfilterやlimitが書けるらしい。たぶんorderとかも使える気がする。

let results = posts
    .filter(published.eq(true))
    .limit(5)
    .load::<Post>(&connection)
    .expect("Error loading posts");

1つのPostを取得する

findのあとにfirstを書くのが冗長感あるけど、たぶん posts.find(id) がWHERE句になるんだと思う。

let post = posts.find(id)
    .first::<Post>(&connection)
    .expect("Error finding posts");

Postを登録する

insert_intoを使う。

ちなみにdieselのGetting Started だとget_resultを使っているけど、sqliteは対応してないので代わりにexecuteを使っている。

let new_post = NewPost {
    title: "title",
    body: "body",
};
let connection = establish_connection();
diesel::insert_into(posts::table)
    .values(&new_post)
    .execute(&connection)
    .expect("Error saving new post");

Postを更新する

Getting Startedを参考にして書いたら、普通に動いた。

diesel::update(posts.find(id))
    .set(published.eq(true))
    .execute(&connection)
    .expect(&format!("Unable to find post {}", id));

Postを削除する

これもドキュメント通り。

diesel::delete(posts.find(id))
    .execute(&connection)
    .expect("Error deleting posts");

ルーティング周り

それっぽく書いたら、動いた。

HttpServer::new(|| {
    App::new()
        .route("/", web::get().to(index))
        .route("/posts", web::get().to(posts_index))
        .route("/posts/{id}", web::get().to(posts_show))
        .route("/posts", web::post().to(posts_create))
        .route("/posts/{id}", web::put().to(posts_update))
        .route("/posts/{id}", web::patch().to(posts_update))
        .route("/posts/{id}", web::delete().to(posts_destroy))
})

次にやりたいこと

  • SQLに関する処理をModelに移す(もしくはRepositoryを作る)
  • テストコードを書く
  • jsonを返すように直す
  • テンプレートエンジンを使ってhtmlを返す

Railsアプリの開発環境を速くするための調査 その1 - gemの読み込み時間

その1って書いたけど、続くかは不明。

今回は使っているgemの読み込み時間を測ってみた。

Benchmark を仕込む

config/application.rb でgemを読み込む前に Kernel.require を上書きして、計測する。

+require 'benchmark'
+$result = {}
+Kernel.singleton_class.prepend(Module.new do
+  def require(feature)
+    ret = nil
+    $result[feature] = Benchmark.realtime { ret = super }
+    ret
+  end
+end)
 Bundler.require(*Rails.groups)
+$result.sort_by { |_, t| -t }.take(20)
+  .each { |feature, time| puts "#{format("%0.3f", time)}: #{feature}" }

実際に測ってみる

tootsuite/mastodon で試してみた。

$ bin/rails runner nil
0.486: chewy
0.228: pry-byebug
0.167: charlock_holmes
0.161: fog/core
0.110: fuubar
0.105: devise-two-factor
0.101: json/ld/preloaded
0.098: omniauth-saml
0.085: goldfinger
0.077: json/ld
0.064: hamlit-rails
0.064: paperclip
0.058: health_check
0.050: fabrication
0.049: twitter-text
0.046: rqrcode
0.042: parslet
0.039: annotate
0.035: pghero
0.034: iso-639

こんな感じで読み込みに時間がかかっているgemが分かる。

Rails の issue を解決するまでの手順とOSS初心者でもできること

突然ですが、あなたはRailsのissueとプルリクがいくつあるかご存知でしょうか?

2019年10月17日現在、それぞれ issue 384 / PR 803 になります。

f:id:sinsoku:20191017000221p:plain

多いですよね...。

個人的に、最近このissueを減らすのを少しでも手伝えないものかとissueにコメントしてみたり、パッチを書いたりしてるけど、 なかなか大変なので、コントリビューターの敷居を下げるためにブログ記事を書いてみました。

コントリビュータが増えれば、きっとissueも減るはず!!

Rails への貢献について

Railsガイドに丁寧な説明が記載されているので、読んだ事がない方は一読するのをオススメします。

railsguides.jp

この記事で紹介すること

Rails への貢献方法は色々なものがあります。

  • 新機能の追加
  • バグの報告
  • バグを修正するプルリク作成
  • ドキュメントの追加や修正
  • ...etc

このなかのうち バグの修正 に絞って紹介します。

バグ修正を行うときの手順

  1. 報告されたissueを読んで、問題を手元で再現させる
  2. 原因を突き止める
  3. コードを直す
  4. 動作確認をする
  5. プルリクを投げる

こんな感じでしょうか。 まぁ、仕事でバグを直すときと同じですね。

1. 問題を再現する

Rails の issue には問題を再現させるためのバグテンプレートが存在します。

  • issue に再現手順、再現スクリプトがない場合
    • バグテンプレートを使って書けないか issue の作者に依頼する
    • 例: rails/rails#36413
  • issue に再現スクリプトがある場合
    • 自分の環境でも再現するか確認してみる

2. 原因を調べる

問題が再現できたら、原因であるコードを調べます。

3. コードを直す

問題を再現させるテストコードを書いて、テストが失敗することを確認してから、コードを直します。 (例: rails/rails#37457) 他のテストコードを参考にすれば、意外とテストは書けます。

あと、Rails では minitest が使われているので、普段 RSpec を使っている人はテストの実行方法が分からないかも。

などを参考にしてみてください。

4. 動作確認

ローカルで適当にRailsアプリを作り、修正した Rails のコードを使うように Gemfile を直します。

- gem "rails", "~> 6.0.0"
+ gem "rails", path: "~/.ghq/github.com/rails/rails"

pathディレクトリを指定すると、そのディレクトリの gem を使えます。

あとは rails consolerails server などで動作確認をします。

5. プルリクを投げる

コミットログとプルリクに変更理由をちゃんと書く必要があります。

英語で変更理由を書くのはとても大変なので、いくつか英文を書くテクニックを紹介します。

  • 変更理由を日本語で書いて Google翻訳 でざっくり翻訳する
  • 簡単な英文(=自分が読める英文)になるように調整する
  • 他のバグ修正のプルリクを読んで、似たような英語で書き直せないか考える
  • git log --no-merges --author=kamipo でkamipoさんのコミットログを読んで参考にする

Rails のコミットログやプルリクには参考になる英文がたくさんあるので、うまく探してパクってください。

変更内容に自信がなかったり、不安な場合

都内に住んでいる方であれば、Asakusa.rb や永和さんのOSSパッチ会などに参加して、プルリクの内容について相談するという方法があります。

OSS初心者向け

ここまでで issue を解決する方法を紹介しましたが、「ハードルが高い」と思った人向けにいくつか初心者向けにできることを紹介します。

  1. https://github.com/rails/rails/issues の中から、興味のあるタイトルを読んでみる
    • issue を全部読むのは大変なので、例えば ActiveRecord や ActiveStorage など絞って読むと良い
  2. 自分の環境で問題を再現させてみる
    • 例えば、5.2 で起きる issue を「6.0 でも再現しました」とコメントするのも大事
    • 再現スクリプトのないissueに「この再現スクリプトで再現できました」とコメントするのも良い
  3. Close した issue とプルリクを読んで、コードの直し方を学ぶ
    • 問題と解答例みたいなものなので、読むだけでも勉強になります

最後に Rails Contributors の紹介

Railsで1回でもプルリクがマージされると Rails Contributors に名前が載ります。

contributors.rubyonrails.org

まだランキングに名前が載っていない方は、これに載ることを目標に Rails の issue を眺めてみてはどうでしょう?

意外と簡単に直せるバグが見つかるかもしれませんよ。

*1:過去のバージョンでは動いていて、最新のバージョンでバグっているケース

勉強用にRustのスクリプトをDockerでビルドし、即実行するbashスクリプトを書いた

Rustの勉強をするため、ちょっとしたコードをDockerでビルドして実行するスクリプトを書いたのでメモ。

コード

勉強用のディレクトリに以下の bin/exec というファイルを作り、 $ chomod +x bin/exec で実行権限をつけておく。

#!/bin/sh
set -e

ROOT=$(cd $(dirname $0)/../;pwd)
IMAGE=rust
RUST_CMD="docker run -e USER=$USER -v ${ROOT}:/app -w /app ${IMAGE}"

if [ -n "$1" ]
  then
    $RUST_CMD rustc $1
    EXEC_PATH=`basename $1 .rs`
    $RUST_CMD ./$EXEC_PATH
    rm ./$EXEC_PATH
else
  echo "Usage: bin/exec <source>"
fi

Dockerでビルドし、Docker上で実行して、実行後にバイナリを消すスクリプトです。

試したいファイルを引数に渡すと、実行できる。

$ bin/exec hello_world.rs
Hello, world!

Rustの勉強

昔にもRust入門してたなーと思ってたら、2年前にRustやってたブログが出てきた。

3日坊主にならないように、定期的に Rust 触ってブログに書きたいですね。そんな気持ちはあります。たぶん。

sinsoku.hatenablog.com

持っている技術の棚卸し 2019年

一応フリーランスなので、自分のできることを整理するためにまとめた。

(文章でまとめるの難しい...)

ソフトウェア開発

RubyRails

  • 1人で仕様の調整を行い、実装できる
  • コードレビューを通して、若手エンジニアにRailsを教えられる
  • OSSにプルリクを投げられる
  • RailsのIssue/PRを読んでいる(=最新機能をある程度知っている)
  • 勉強会に参加したり、登壇したことがある

JavaScript

  • ES2015の基本的な文法を知っている
  • jQueryを使った経験がある
  • React.jsでステートレスを意識してコンポーネントを書ける
  • Puppeteerを使ったE2Eテストを書いた経験がある

CSS

  • Bootstrap v3 の基本的な知識がある
  • BEMの基本的な知識がある
  • 他サイト、周りのデザインを参考にしてcssを組める
    • サイト全体のデザイン設計は出来ません

SQL

  • SQLの基本的な知識がある
  • パフォーマンスを意識したSQLを書ける
    • RDBMSの実行計画には詳しくありません
  • リプレイス案件でDB間のデータ移行作業の経験がある

インフラ

  • TerraformとAWSを使ってインフラ環境を構築できる
    • Route53, CF, ALB, ECS(Fargate)の構成を組める
    • GCPは未経験
  • aws-samでサーバレスAPIを組める

開発環境

  • GitHub周りの環境を整えられる
    • ブランチ保護、 Issue/PRテンプレート機能など
    • GitHub Appsを作れる
  • CI環境を構築できる
    • 静的解析(Lint)の設定ができる
    • CircleCIのWorkflow、Orbsを活用できる
  • CD環境を構築できる
    • ローリングアップデートの経験のみ
    • Blue/Greenデプロイ、カナリアデプロイは未経験
  • エラー管理にSentryなどを導入・使用したことがある
  • パフォーマンス改善にDatadogなどを導入・使用したことがある

その他

  • SEO対策の作業をした経験がある
  • ユーザーテストやUX改善について少し勉強したことがある
  • Redashを使ってKPIを表示するダッシューボードを作れる
  • リリースにあわせてGitのブランチ運用を提案できる

興味のあること

  • 開発環境を改善すること
  • お金の話(売上を増やしたり、経費を減らしたり)
  • 読みやすいコードを考えたり、書いたりすること
  • 新しい言語の習得(直近だとRust, Golang
  • エンジニアの評価・採用・組織作り

Rubyのライブラリやアプリケーションで使えるENVの一覧を取得する方法

最近、コンテナ化や The Twelve-Factor App環境変数を使う機会が多くなってきたので、一覧を作る方法が欲しくなった。

gemにする気力は今無いので、とりあえずコード片をブログに書いておく。*1

# config/application.rb の最後に以下のコードを貼ったら、それっぽく動いた。

module EnvLogger
  def self.histories
    @histories ||= []
  end

  def [](key)
    EnvLogger.histories << key
    super
  end

  def fetch(key, default = nil)
    EnvLogger.histories << key
    super
  end
end
ENV.singleton_class.prepend EnvLogger

at_exit do
  EnvLogger.histories.uniq.sort.each { |key| puts key }
end

Special thanks

ruby-jp で聞いたら、 @p_ck_ さんが即レスしてくれた。はやい。

f:id:sinsoku:20190826204921p:plain
ruby-jp

*1:将来、気が向いたらgem化するかも?やる気ある人いたら誰か代わりに頼む

コミットメッセージを書くときに気をつけていること

コミットメッセージに関するブログを読んだので、自分がコミットメッセージを書くときに気にしてることを書いておく。

chiastolite.hatenablog.com

sue445.hatenablog.com

基本的な話

まずは Gitのコミットメッセージの書き方 を読んでおく。

コミットメッセージは "将来の同僚のため" に書く

3ヶ月後にチームに入った人が理解できる内容 になっているのが理想。

チームメンバーが分かる内容で満足すると、暗黙的な業務知識に依存したコミットメッセージになっていることがあるので注意。

XXXを追加/変更/削除しました はダメ

油断するとこういうメッセージを書いてしまいがち。

git-diff の内容を日本語で書いただけだと意味がないので、コミットメッセージで付加価値がつくように気をつける。

コミットした後に見直す

コミットした後、git show @ を実行して、以下の点を確認する。

  1. diffの全てをコミットメッセージで説明できているか?
    • 関係ない変更が混ざっていないか?
  2. コミットメッセージを読んで「なぜ?」と自問する
    • 更に Why を深掘りして書けないか?

参考にしたURLを残す

自分が知らなかったことは、たぶん他メンバーも知らない可能性が高い。

コードを書いていて参考になったWebページがあったら、コミットメッセージに残しておく。

GitHubのURLを固定する

  1. https://github.com/rails/rails/blob/master/README.md
  2. https://github.com/rails/rails/blob/v6.0.0.rc2/README.md

1だとmasterブランチが更新されると内容が変わってしまう可能性があるので、2のようにタグの入ったURLにする。

ちなみに、GitHubでファイルを表示しているときに y を押すとURLが master から blob に変わります。*1

f:id:sinsoku:20190817013444p:plain
GitHub shortcut

URLを張りつつ、文面も引用してメッセージに含める

@sue445 さんのブログと半分同じなのですが、文面を引用して残すところが少し違うかな。

  • コミットメッセージ単体だと意味が分からなくなる事が多い
  • 自分がログを見ていたときに別ページを毎回開くのが面倒だった
  • リンク先が将来も残っているとは限らない
    • 突然リポジトリが消えてissueを読めなくなった経験があるので...*2

コミットメッセージは長くても良い

コミットメッセージが短くて不足しているより、多少冗長でも変更理由が書いてある方が嬉しい。

*1:下のヘルプ画像は ? で出ます

*2:https://tech.grooves.com/entry/2017/01/18/111900