20210303のRubyに関する記事は20件です。

Docker環境でのByebugの使い方

docker&railsの開発を学んでるとByebugを使うには一手間いるとのこと

以下docker-compose.ymlにコード追加

docker-compose.yml
services:
  web:
    stdin_open: true <= 追加する
    tty: true <= 追加する

続けてコードを反映するために以下をする

$ docker-compose up -d

エラーが特に出なければOK

Byebugの使い方

コンテナIDまたはコンテナ名を調べる

$ docker ps

コンテナのIDか名前をアタッチする

$ docker attach <container name or ID>

デバッグの準備完了

Byebug終わり方

byebugが終わり、標準出力に戻りたい場合は

Ctrl + P + Q

または

(byebug) quit

でデバッグから抜けます
Ctrl + C で抜けようとするとめんどくさい事になるらしいのでやらないように

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose up で突然エラーになる

備忘録的なもの

dockerでのrails開発中にいきなり docker-compose up したらエラーになり頭を抱えた。

ERROR: for <コンテナ名>  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)

ERROR: for web  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)
ERROR: An HTTP request took too long to complete. Retry with --verbose to obtain debug information.
If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 60).

長い時間解決法を探していたがDocker for Macを再起動すれば解決した。
Dockerがたまにおかしくなるらしいとのことで、、
理解するのにはまだ長い道のり

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【HTML/CSS】リアルタイムな正規表現検証を簡単にフォームに実装する方法【Rails】

初めに

 この記事は、上記記事で紹介したポートフォリオで使用した技術を切り出した記事になります。もし宜しかったらこちらもご覧ください。
また、この記事はGIF画像を挿入しておりますが、あまり滑らかに動かない可能性があります。GIF画像をクリックして頂けるとGyazoのURLへ移動することが出来ますので、そちらでしたらヌルヌル動くかと思います。

イメージ図

Image from Gyazo

 今回紹介する方法を使用すると、上記GIF画像のinput要素のように、

  • フォーカスされたら要素が大きくなる・背景が白っぽくなる
  • 正規表現にマッチするなら枠が緑色に、マッチしないなら赤色になる
  • 正規表現とマッチしない状態で送信ボタンを押すと、入力内容が送信される前に入力アシスタントを表示する

といった機能を割と簡単に実装出来るようになります。

HTMLで正規表現を記述する

/views/devise/registrations/new.html.erb
<div class="field font sign-up">
  <%= f.label :nickname %><br />
  <%= f.text_field :nickname, autofocus: true, autocomplete: "off", required: true, maxlength: 8, pattern: "^([a-zA-Z0-9]{1,8})$", placeholder: "^([a-zA-Z0-9]{1,8})$" %>
</div>

 上記コードはGIF画像でいうところの「Nickname」部分のフォームのソースコードです。form_withを使ったinput要素ですが、f.text_field :nicknameの後に何やらゴチャゴチャ書かれていますね。今回特に注目して頂きたいのは下記の部分です。

  • required: true
    • 必須項目になる
  • maxlength: 8
    • 8文字までしか入力出来なくなる
  • pattern: "^([a-zA-Z0-9]{1,8})$"
    • この正規表現(半角英数1~8文字)にマッチする入力内容しか通さなくなる

 そして、これらの条件を満たさないまま送信をしようとすると、
Image from Gyazo
以下のような入力アシスタントが表示される具合です。

裏でどのような処理がされているか

 詳しい内容は下記URLに全て書かれています。

 ここでは、今回の内容に関係する部分のみ引用します。

要素が妥当な場合は、次のようになります。

  • 要素が CSS の :valid 疑似クラスに一致します。これにより、妥当な要素に特定のスタイルを適用することができます。
  • ユーザーがデータを送信しようとすると、ブラウザーは止めるものが他になければ(JavaScript など)、フォームを送信します。

要素が不正なときは、次のようになります。

  • 要素が CSS の :invalid 疑似クラスに一致します。これにより、不正な要素に特定のスタイルを適用することができます。
  • ユーザーがデータを送信しようとすると、ブラウザーはフォームをブロックしてエラーメッセージを表示します。

 つまり、HTMLがフォームの入力内容を常に監視し、その検証結果を要素の擬似クラスとして出力するということです。
 擬似クラスがどのようなものかと言うと、簡単に言えば「そのクラスの状態」のことを指します。例えば<div class="test"></div>のような要素があるとして、この要素をCSSで装飾する時は

style.css
.test {
  display: flex;
}

のような記述になるかと思います。この要素がホバーされると裏では擬似クラス:hoverが要素に付与されるので、

style.css
.test:hover {
  background-color: red;
}

のような記述がCSSにあると、これが適用されるようになります。これを利用すれば、

style.css
.test {
~~省略~~
  background-color: blue;
  transition: all 0.3s ease;
}

.test:hover {
  background-color: red;
  transform: scale(1.1, 1.1);
}

のように記述をすると、testクラスを持った要素は、「ホバーされると0.3秒掛けて色が青から赤へ、大きさが縦横ともに1.1倍になる」といった装飾を適用することが出来る、という寸法です。ちなみに、transitionは要素の変化を滑らかに繋ぐためのプロパティで、transformは要素の形状を変化させるためのプロパティです。
 たったこれだけの記述を追加するだけでアプリに動きを出せる訳ですから、労力対効果は高そうですね。

擬似クラス別にCSSを記述してみる

 それでは、入力内容の検証が行われるフォームに装飾を追加してしてみましょう。

style.css
.input {
~~省略~~
  transition: all 0.3s ease;
}

 まずはいつも通りにinputクラスにCSSを記述し、フォームを作成します。そしてtransitionを追加することで、これから記述していく変化の内容が滑らかなアニメーションで表現されるようにしておきます。
 次に、要素がfocusされた時に起こしたい変化を記述していきます。

style.css
.input {
~~省略~~
  transition: all 0.3s ease;
}

.input:focus {
  transform: scale(1.1, 1.1);
  background-color: rgba(223, 223, 223, 0.5);
}

 更に今回は入力内容の検証も行いたいので、条件を満たさない時は枠を赤色に、条件を満たしたら緑色に変化するようにします。条件を満たさない時は擬似クラス:invalidが、満たすときは:validが適用されますので、

style.css
.input {
~~省略~~
  transition: all 0.3s ease;
}

.input:focus {
  transform: scale(1.1, 1.1);
  background-color: rgba(223, 223, 223, 0.5);
}

.input:focus:invalid {
  border: red 5px solid;
}

.input:valid {
  border: rgb(2, 175, 2) 6px solid;
  background-color: rgba(223, 223, 223, 0.5);
}

これで「フォーカスされている・入力内容が条件を満たさない」場合は枠が赤色に、「入力内容が条件を満たす」場合は緑色に変化させることが出来ます。

まとめ

 上記の記述だけでは、「入力したNicknameが既に使われているかどうか」といった「データベースと照合する検証」は行うことが出来ません。HTMLで行うことが出来るのは、あくまで入力の書式に関する検証のみになります。リアルタイムでデータベースとの照合を行う方法は発見することが出来ませんでした。
 その方法を発見し次第また記事にしようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Formオブジェクトを使用した際の単体テスト(FactoryBotの記述)

Formオブジェクトを使用した場合のテストコードを記述した際に、
何度もエラーが出てしまった部分を、忘れないように記録します。

フリマアプリで商品購入機能を実装し、その時のテストコードの一部になります。
以下がその一部です。

qiita.rb
   before do
    @user = FactoryBot.create(:user)
    @item = FactoryBot.build(:item)
    @item.image = fixture_file_upload('app/assets/images/test_image.jpeg')
    @item.save
    @order_purchaser = FactoryBot.build(:order_purchaser, user_id: @user.id, item_id: @item.id)

  end

@itemの部分でbuildアクションを使用しているのは、その後の記述で画像情報を@itemに追加するためで、
createアクションで定義してしまうとデータが保存され、後からの記述で追加ができなかったためです。

qiita.rb
 @order_purchaser = FactoryBot.build(:order_purchaser, user_id: @user.id, item_id: @item.id)

今回のフリマアプリで扱った購入者情報(@order_purchaser)は、user、itemに紐づいているため、カッコ内の記述で、user_id、item_idのデータを使用できるようにしています。

今回のコードが必要になったのは、Formオブジェクトを使用しており、モデル間のアソシエーションが記述されておらず、FactoryBotでassociationを組めなかった為です。

不足箇所や間違えているところがあれば、随時更新します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Formオブジェクトを使用した際の単体テスト(FactoryBot)

Formオブジェクトを使用した場合のテストコードを記述した際に、
何度もエラーが出てしまった部分を、忘れないように記録します。

フリマアプリで商品購入機能を実装し、その時のテストコードの一部になります。
以下がその一部です。

qiita.rb
   before do
    @user = FactoryBot.create(:user)
    @item = FactoryBot.build(:item)
    @item.image = fixture_file_upload('app/assets/images/test_image.jpeg')
    @item.save
    @order_purchaser = FactoryBot.build(:order_purchaser, user_id: @user.id, item_id: @item.id)

  end

@itemの部分でbuildアクションを使用しているのは、その後の記述で画像情報を@itemに追加するためで、
createアクションで定義してしまうとデータが保存され、後からの記述で追加ができなかったためです。

qiita.rb
 @order_purchaser = FactoryBot.build(:order_purchaser, user_id: @user.id, item_id: @item.id)

今回のフリマアプリで扱った購入者情報(@order_purchaser)は、user、itemに紐づいているため、カッコ内の記述で、user_id、item_idのデータを使用できるようにしています。

今回のコードが必要になったのは、Formオブジェクトを使用しており、モデル間のアソシエーションが記述されておらず、FactoryBotでassociationを組めなかった為です。

不足箇所や間違えているところがあれば、随時更新します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

検証ツールなどを使った情報の改竄を防ぐControllerの実装

なぜ必要か

商品を出品するフリマアプリのようなサービスを例に考えます。
あるユーザーが商品を出品後、その商品の情報を編集したり、商品を削除したりする場合、そうした操作をするのは当然、商品を出品した当事者であるべきです。
そこでビューファイルの実装においては、以下のようにif文を用いた条件分岐により、商品を出品したユーザーと現在ログイン中のユーザーが一致した場合に限り、「編集」や「削除」のボタンを表示するようにしています。

Viewファイルの実装

 <% if current_user.id == @item.user_id %>
      <%= link_to "商品の編集", edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
      <p class="or-text">or</p>
      <%= link_to "削除", item_path(@item.id), method: :delete, class:"item-destroy" %>
   <% end %> 

しかし、これだけですと、検証ツールなどを使用して、第3者が勝手に商品の情報の編集をできてしまう状態です。
そこで、Controller側でも制限を設ける記述をすることで、そうしたリスクを排除します。

Controllerの実装

 @item = Item.find(params[:id])
  unless @item.user_id == current_user.id
   redirect_to root_path
  end

このように、unlessメソッドを使い、その商品を投稿したユーザーと現在ログインしているユーザーが一致しない場合、ログイン画面に遷移する実装をすれば検証ツールによる編集も防げます。
ちなみに、current_userメソッドを使っているのは、ユーザー管理機能を実装するにあたり事前にdeviseを導入しているためです。

まとめ

検証ツールを用いた悪意ある情報編集などを防ぐためにも、Controllerにおいて頻繁に使われる記述のようなので、これを機に覚えておきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

子レコードの条件で親レコードを絞り込みたいときはEXISTS句を活用しよう

はじめに

Railsで次のような親子関連を持ったモデルがあったとする。

class Parent < ApplicationRecord
  has_many :children
end

class Child < ApplicationRecord
  belongs_to :parent
end

そして、DBのデータが次のように登録されていたとする。

  • parent = Namihei
    • child = Sazae
    • child = Katsuo
    • child = Wakame
  • parent = Misae
    • child = Shinnosuke
    • child = Himawari

この状況で以下のようなメソッドを作りたい。

# 引数で与えられた文字列を子どもの名前に含む親(Parent)を返す
# (子どもの名前の大文字・小文字は無視する)
Parent.children_name_with(str)

この要件を満たすためのテストコードは次のようになる。

require "test_helper"

class ParentTest < ActiveSupport::TestCase
  test "zで検索" do
    # Sazaeが該当するのでNamiheiが返る
    parents = Parent.children_name_with('z').order(:name)
    assert_equal [parents(:namihei)], parents
  end

  test "kで検索" do
    # ShinnosukeとKatsuoとWakameが該当するのでMisaeとNamiheiが返る
    parents = Parent.children_name_with('k').order(:name)
    assert_equal [parents(:misae), parents(:namihei)], parents
  end

  test "rで検索" do
    # Himawariが該当するのでMisaeが返る
    parents = Parent.children_name_with('r').order(:name)
    assert_equal [parents(:misae)], parents
  end
end

この要件を満たすchildren_name_withメソッドの実装方法を考えたい。

JOIN + DISTINCTを使う(あまり推奨しない)

この要件を満たすコードとして、以下のような実装をよく見かける。

scope :children_name_with, -> (str) do
  joins(:children)
    .where("LOWER(children.name) LIKE ?", "%#{str.downcase}%")
    .distinct
end

参考:発行されるSQL

SELECT DISTINCT "parents".* 
FROM "parents" 
INNER JOIN "children" 
  ON "children"."parent_id" = "parents"."id" 
WHERE (LOWER(children.name) LIKE '%k%') 
ORDER BY "parents"."name" ASC

たしかにこの実装でも要件は満たせる。
しかし、childrenテーブルをJOINすると大量のparentsレコードの重複行が発生してしまう恐れがある。
そのためにdistinctを呼んで重複行を排除する必要があるが、一般にこの処理はRDBMSにとってハイコストなものでであるため、パフォーマンスが悪化する恐れがある。

参考:DISTINCTを付けなかった場合

DISTINCTなしで子どもの名前に"k"が含まれる親を検索するSQLを実行すると以下のような結果になる。

id name created_at updated_at
2 Misae 2021-03-03 22:33:05 2021-03-03 22:33:05
1 Namihei 2021-03-03 22:33:05 2021-03-03 22:33:05
1 Namihei 2021-03-03 22:33:05 2021-03-03 22:33:05

Namiheiのレコードが重複する理由は、JOIN先のchildrenテーブルでKatsuoとWakameの2件が該当したためである。

EXISTS句を使う(個人的におすすめ)

上のようなコードは以下のようにEXISTS句を使ったクエリが発行されるように書き直すと、RDBMS上の処理効率が良くなる。

scope :children_name_with, -> (str) do
  sql = <<~SQL
    EXISTS (
      SELECT *
      FROM children c
      WHERE c.parent_id = parents.id
      AND LOWER(c.name) LIKE ?
    )
  SQL
  where(sql, "%#{str}%")
end

参考:発行されるSQL

SELECT "parents".*
FROM "parents"
WHERE (EXISTS (
  SELECT *
  FROM children c
  WHERE c.parent_id = parents.id
  AND LOWER(c.name) LIKE '%k%'
))
ORDER BY "parents"."name" ASC

このSQLにするとparentsレコードの重複行が発生しなくなり、DISTINCTの処理も不要になる。

この記事で示した程度の少量のレコード数であれば体感できる速度差はないが、何万、何十万という重複行が発生するような状況では無視できない違いが出てくる。

もしくはIN + サブクエリを使う

次のように書く方法もある。

scope :children_name_with, -> (str) do
  ids = Parent
    .joins(:children)
    .where("LOWER(children.name) LIKE ?", "%#{str.downcase}%")
    .select(:id)
  where(id: ids)
end

参考:発行されるSQL

SELECT "parents".* 
FROM "parents" 
WHERE "parents"."id" IN (
  SELECT "parents"."id" 
  FROM "parents" 
  INNER JOIN "children" 
    ON "children"."parent_id" = "parents"."id" 
  WHERE (LOWER(children.name) LIKE '%k%')
)
ORDER BY "parents"."name" ASC

このSQLでも重複行は発生しないのでDISTINCTは不要。
EXISTS句を使ったときとどちらが実行効率が良いのかは未検証。

ただ、個人的にはEXISTS句を使った方が「子レコードの存在有無(EXISTS OR NOT)で絞り込みたい」という意図が明確になるので、EXISTS句を使う方が好み。

まとめ

子レコード(has_manyで関連する関連先のレコード)の条件で親レコードを絞り込みたいときは、JOIN + DISTINCTよりもEXISTSを使って絞り込む。

実行環境

  • Ruby on Rails 6.1.3
  • SQLite3 (PostgreSQLやMySQLを使ったときも同じ議論になるはず)

サンプルコード

本記事のサンプルコードはこちら。

おまけ:子どもが一人もいないParentを検索する

次のように子どもが一人もいないParentレコードがあったとする。

  • parent = Namihei
    • child = Sazae
    • child = Katsuo
    • child = Wakame
  • parent = Misae
    • child = Shinnosuke
    • child = Himawari
  • parent = Golgo13
    • (no children)

子どもが一人もいないParentを探すwithout_childrenメソッドの実装を考える。
テストコードを書くと次のようになる。

test ".without_children" do
  parents = Parent.without_children.order(:name)
  assert_equal [parents(:golgo13)], parents
end

方法1:LEFT OUTER JOIN + id IS NULLを使う

scope :without_children, -> do
  left_outer_joins(:children).where(children: { id: nil })
end

参考:発行されるSQL

SELECT "parents".*
FROM "parents"
LEFT OUTER JOIN "children"
  ON "children"."parent_id" = "parents"."id"
WHERE "children"."id" IS NULL
ORDER BY "parents"."name" ASC

方法2:NOT EXISTS句を使う

scope :without_children, -> do
  sql = <<~SQL
    NOT EXISTS (
      SELECT *
      FROM children c
      WHERE c.parent_id = parents.id
    )
  SQL
  where(sql)
end

参考:発行されるSQL

SELECT "parents".*
FROM "parents"
WHERE (NOT EXISTS (
  SELECT *
  FROM children c
  WHERE c.parent_id = parents.id
))
ORDER BY "parents"."name" ASC

方法3:NOT IN + サブクエリを使う

scope :without_children, -> do
  ids = Parent.joins(:children).select(:id)
  where.not(id: ids)
end

参考:発行されるSQL

SELECT "parents".*
FROM "parents"
WHERE "parents"."id" NOT IN (
  SELECT "parents"."id"
  FROM "parents"
  INNER JOIN "children"
    ON "children"."parent_id" = "parents"."id"
)
ORDER BY "parents"."name" ASC

どれがいいか?

  • 方法1

    • メリット:SQLを書かずにActiveRecordの機能だけで済む
    • デメリット:RDBMSによっては内部的に大量に子レコードをJOINしてからidがNULLの行を絞り込む、というような処理が走りそう(未検証)
  • 方法2

    • メリット:書き手の意図が明確になる。RDBMSのクエリオプティマイザが効きやすそう(未検証)
    • デメリット:生SQLを書かなければいけない。状況によってはscopeの再利用性が下がる
  • 方法3

    • メリット:SQLを書かずにActiveRecordの機能だけで済む
    • デメリット:NOT INだとテーブルの全件走査が走るかも?(未検証。クエリオプティマイザが賢ければうまくindexが使われるかも?)

クエリオプティマイザの性能次第のところはあるが、個人的にはクエリの意図が明確で実行速度も速そうな方法2を使いたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6.1で発生する"Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated"警告を修正する

Ruby on Rails 6.1に上げると以下のような警告が出ることがある。

DEPRECATION WARNING: Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.

この問題は次のように修正する。

-record.errors[:base] << "Something went wrong."
+record.errors.add :base, "Something went wrong."

応用:正規表現でプロジェクト全体のコードを一括置換する

が、数が多いと大変なので、エディタのGrep置換機能と正規表現を使ってこんなふうに一括置換すると便利かも。

  • 検索条件 = errors\[:(\w+)\] <<
  • 置換文字列 = errors.add :$1, (エディタによっては$1ではなく\1になることもある)

もちろん、これはプロジェクト全体でerrors[:hoge] <<が同じフォーマットで書かれていることが前提なので、フォーマットに一貫性がないときは置換対象から外れたり、おかしな置換結果になったりする場合があるので注意。

正規表現がわからん!という場合

僕が以前書いた正規表現の入門記事を見ればわかるはずです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】通知機能をリファクタリングしてみた

プログラミングスクールに通っています。
先日ポートフォリオのフィードバックが返って来た際に、通知機能のリファクタリングをした方が良いとの指摘を受けたので、修正方法を記事にします。
通知機能に関しては以下の記事を参考にさせて頂きました。
https://qiita.com/nekojoker/items/80448944ec9aaae48d0a

修正方法

cook.rb
  def create_notification_like!(current_user)
    already_like_check = Notification.where(
      [
        "visitor_id = ? and visited_id = ? and cook_id = ? and action = ? ",
        current_user.id, user_id, id, 'like',
      ]
    )
    if already_like_check.blank?
      notification = current_user.active_notifications.new(
        cook_id: id,
        visited_id: user_id,
        action: 'like'
      )
      if notification.visitor_id == notification.visited_id
        notification.is_checked = true
      end
      notification.save if notification.valid?
    end
  end

投稿した料理に対して「いいね」がされた際に通知が行われるようにしています。
今回はモデルに定義したcreate_notification_like!メソッドをリファクタリングしていきます。

上記では
- already_like_check が blank だった場合
- notification.visitor_id と notification.visited_id が一緒だった場合
- notification が valid? だった場合
と条件が3つ出てきます。しかし、 already_like_checkblank じゃなかった場合の処理は出てきません。
つまり、already_like_checkblank じゃなかった場合 は例外的なパターンとして処理をその時点で中断してあげると良いということになります。
この考え方を適用すると下記のように実装できます。

cook.rb
class Cook < ApplicationRecord
  def create_notification_like!(current_user)
    already_like_check = Notification.where(
      [
        "visitor_id = ? and visited_id = ? and cook_id = ? and action = ? ",
        current_user.id, user_id, id, 'like',
      ]
    )
    return unless already_like_check.blank? #これを追加
    notification = current_user.active_notifications.new(
      cook_id: id,
      visited_id: user_id,
      action: 'like'
    )
    if notification.visitor_id == notification.visited_id
      notification.is_checked = true
    end
    notification.save if notification.valid?
  end
end

早期リターンという手法でifのネストを避けることができました。
(早期リターンとは簡単に言うと、「条件に合致しない場合は処理をそこでストップさせる」ことです。今回のケースではalready_like_checkblank じゃなかった場合、return unless already_like_check.blank?以下の処理を実行しないということです。)
次は メソッドの抽出 という方法を行い処理をみやすくしてみましょう。
メソッドの抽出 というのは処理の塊をメソッドに切り出してあげることです。
今回抽出できそうな処理は下記になります。

notification = current_user.active_notifications.new(
    cook_id: id,
    visited_id: user_id,
    action: 'like'
  )
if notification.visitor_id == notification.visited_id
  notification.is_checked = true
end
notification.save if notification.valid?

この処理は、
- notification.visitor_id と notification.visited_id が一緒だった場合
- notification が valid? だった場合
という2つの条件が含まれており、 create_notification_like! メソッドの中にあるとやや処理が複雑になってしまいます。
Notification クラスから作成したインスタンスに対する処理の実行ですので、 Notification クラスにインスタンスメソッドを作成してあげると良いでしょう。

notification.rb
def update_is_checked_and_save
  if visitor_id == visited_id
    is_checked = true
  end
  save if self.valid?
end

さらに、後置ifを活用して以下のようにします。

notification.rb
def update_is_checked_and_save
  is_checked = true if visitor_id == visited_id
  save if self.valid?
end

そして、このメソッドを create_notification_like! メソッド内で呼び出します。

cook.rb
def create_notification_like!(current_user)
  already_like_check = Notification.where(
    [
      "visitor_id = ? and visited_id = ? and cook_id = ? and action = ? ",
      current_user.id, user_id, id, 'like',
    ]
  )
  return unless already_like_check.blank?
  notification = current_user.active_notifications.new(
    cook_id: id,
    visited_id: user_id,
    action: 'like'
  )
  notification.update_is_checked_and_save
end

これで最初よりかはスッキリとしたと思います。

最後に

あくまで一例ですので、もっとわかり易い方法があれば教えて下さい!
以上です!ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecord::PendingMigrationErrorの対処法

ActiveRecord::PendingMigrationErrorの対処法

マイグレーションファイルを編集し、rails db:migrateでマイグレーションを実行したときに、このようなエラーが出た場合の対処法について記載します。

エラーメッセージ

error.png

開発環境

・データベース:MYSQL
・railsのバージョン:6.0.0
・使用PC:macbook pro

エラーの原因

Image from Gyazo
このように、usersテーブルが既に作ってあるにも関わらず、また作ろうとしてエラーになったようです。

エラーの解決方法

rails db:reset

このコマンドをターミナルに打つと、
①すでにあるテーブルを削除、
②再度マイグレーション実行
というタスクを一度に行ってくれます。

これを実行したあと、rails db:migrate:statusコマンドにてマイグレーションの状況を確認してみると、
Image from Gyazo

ちゃんとupの状態になっていました。

最後に

今回のエラーに直面し最初は困っていましたが、以下の記事を参考にして解決することができました。
同様のエラーに直面した方にはきっと参考になる記事ですので、ご一読下さい。
https://qiita.com/KONTA2019/items/0444ae3b8c8936a56ee0
https://qiita.com/ryota_ueda/items/fcf111ed0b56822408ed
https://qiita.com/ARTS_papa/items/64416fd4e05250941fb4

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseで複数のモデルを作成する

WHY

このことについて同じ記事はいくつもあるが、その記事の言っていることをきちんと理解しているか確認するため


今回はdeviseで複数のモデルを作成していきます。
どういうことかと言うと、deviseを使って、2つのテーブルを作成し、2つのユーザー管理機能を一つのアプリケーション内で管理しようということです。

deviseの導入

gemfile.
gem 'devise'
ターミナル.
bundle install
rails g devise:install

deviseの設定

主に2つの項目をコメントアウトを外し、変更します。

config/initializers/devise.rb.
#config.scoped_views = false
        ↓
config.scoped_views = true

#config.sign_out_all_scopes = true
     ↓
config.sign_out_all_scopes = false

これらの変更で
- それぞれのモデルで個別のログイン画面を使用することができる
- それぞれのモデルを扱う際に、一方をログアウトした時に、もう片方もログアウトすることを防ぐ

という設定が完了しました。
続いてMVCを作成していきます。

MVC作成

ターミナル.
#モデル
rails g devise user
rails g devise admin

#ビューファイル
rails g devise:views users
rails g devise:views admins

#コントローラー
rails g devise:controllers users
rails g devise:controllers admins

それぞれ最後にモデル名を記述することを忘れないでください。
忘れてしまって作成した場合は「rails d」で削除後、また行ましょう。

ルーティングの設定

現在のルーティングをみてみます

ターミナル.
%rails routes

                 Prefix Verb   URI Pattern                       Controller#Action
       new_admin_session GET    /admins/sign_in(.:format)         devise/sessions#new
           admin_session POST   /admins/sign_in(.:format)         devise/sessions#create
   destroy_admin_session DELETE /admins/sign_out(.:format)        devise/sessions#destroy
      new_admin_password GET    /admins/password/new(.:format)    devise/passwords#new
     edit_admin_password GET    /admins/password/edit(.:format)   devise/passwords#edit
          admin_password PATCH  /admins/password(.:format)        devise/passwords#update
                         PUT    /admins/password(.:format)        devise/passwords#update
                         POST   /admins/password(.:format)        devise/passwords#create
        new_user_session GET    /users/sign_in(.:format)          devise/sessions#new
            user_session POST   /users/sign_in(.:format)          devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)         devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)     devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format)    devise/passwords#edit
           user_password PATCH  /users/password(.:format)         devise/passwords#update
                         PUT    /users/password(.:format)         devise/passwords#update
                         POST   /users/password(.:format)         devise/passwords#create
cancel_user_registration GET    /users/cancel(.:format)           devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)          devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)             devise/registrations#edit
       user_registration PATCH  /users(.:format)                  devise/registrations#update
                         PUT    /users(.:format)                  devise/registrations#update
                         DELETE /users(.:format)                  devise/registrations#destroy
                         POST   /users(.:format)                  devise/registrations#create

右側の「Controller#Action」に注目しましょう。
コントローラーがすべてdeviseになっています。

なのでルーティングをこのように設定してあげます。

routes.rb
devise_for :admins, controllers: {
    sessions:      'admins/sessions',
    passwords:     'admins/passwords',
    registrations: 'admins/registrations'
  }
devise_for :users, controllers: {
    sessions:      'users/sessions',
    passwords:     'users/passwords',
    registrations: 'users/registrations'
  }

もう一度確認すると

ターミナル.
rails routes

                 Prefix Verb   URI Pattern                          Controller#Action
       new_admin_session GET    /admins/sign_in(.:format)            admins/sessions#new
           admin_session POST   /admins/sign_in(.:format)            admins/sessions#create
   destroy_admin_session DELETE /admins/sign_out(.:format)           admins/sessions#destroy
      new_admin_password GET    /admins/password/new(.:format)       admins/passwords#new
     edit_admin_password GET    /admins/password/edit(.:format)      admins/passwords#edit
          admin_password PATCH  /admins/password(.:format)           admins/passwords#update
                         PUT    /admins/password(.:format)           admins/passwords#update
                         POST   /admins/password(.:format)           admins/passwords#create
        new_user_session GET    /users/sign_in(.:format)             users/sessions#new
            user_session POST   /users/sign_in(.:format)             users/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)            users/sessions#destroy
       new_user_password GET    /users/password/new(.:format)        users/passwords#new
      edit_user_password GET    /users/password/edit(.:format)       users/passwords#edit
           user_password PATCH  /users/password(.:format)            users/passwords#update
                         PUT    /users/password(.:format)            users/passwords#update
                         POST   /users/password(.:format)            users/passwords#create
cancel_user_registration GET    /users/cancel(.:format)              users/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)             users/registrations#new
  edit_user_registration GET    /users/edit(.:format)                users/registrations#edit
       user_registration PATCH  /users(.:format)                     users/registrations#update
                         PUT    /users(.:format)                     users/registrations#update
                         DELETE /users(.:format)                     users/registrations#destroy
                         POST   /users(.:format)                     users/registrations#create

きちんとコントローラーが割り振られました。
それぞれのコントローラーも先程作成していればうまく遷移してくれるはずです。

パスの指定

今回は
test.png

このようにページ遷移していきたいのでそれぞれにパスを指定していきます。
パスを指定するときには

def after_sign_in_path_for(resource)
    指定したいパス
end

をコントローラーに記述していきます。

controllers/admins/registrations_controller.rb
コメントアウトされているafter_sign_in_path_for(resource)を編集します。同じくusersディレクトリでも行ます。

registrations_controller.rb.
# The path used after sign up.
  def after_sign_in_path_for(resource)
    items_path # 指定したいパスに変更
  end

これで新規登録をそれぞれ行い、指定したパスへ遷移できると成功です。

続いてsession(ログイン後)の遷移の記述をしていきます。
先程と同じく次はそれぞれのsessions_controller.rbの一番下に記述していきます。

sessions_controller.rb.
def after_sign_in_path_for(resource)
    items_path # 指定したいパスに変更
end

ブラウザでログイン後うまく遷移できれば成功です。


sessionsにcreateアクションにredirect_toなどでもできるかもしれません、、(まだ試してませんし、調べ足りてません...)
挙動的には問題はないとおもいます、、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】FactoryBotとFakerについてまとめ

初学者です。
テストコードがなぜか大好きです。

Ruby on Railsでテストコードを書く際にFactoryBotとFakerを使うと効率的にできます。
使い方をまとめます。

前提条件

Rspecを導入済みであること

FactoryBot

FactoryBotとは、Railsで利用できるGemです。
FactoryBot用のファイルであらかじめのインスタンスに定める値を設定しておき、各テストコードで使用します。
例えばユーザー登録のテストコードで、ユーザー名やメールアドレスやパスワードをFactoryBotにまとめておくことで効率的に記述をすることができます。

FactoryBot導入

下記のようにGemfilegroup :development, :test dogem 'factory_bot_rails'と記述します。

Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 4.0.0'

# 以下を追加する
  gem 'factory_bot_rails'

end

ターミナルで下記のコマンドを実行しGemを導入します。

ターミナル
bundle install

このあとにテストコードを記述するためのファイルを生成すると、specディレクトリ配下にfactoriesディレクトリFactoryBot用のファイル(例:users.rbなど)が生成されますが、既にテストコード用のファイルを生成している場合などは生成されないので手動で作成しても問題ありません。

specディレクトリ配下にfactoriesディレクトリFactoryBotを記述するためのファイルを作成します。

例:spec/factories/users.rb

そのファイル内に記述していきます。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name                  {'test'}
    email                 {'test@example.com'}
    password              {'123456'}
    password_confirmation {password}
  end
end

上記のように記述しておけば、テストコード内で下記のように記述してデータを使い回すことができます。

spec/models/user_spec.rb
user = FactoryBot.build(:user)

上記の例ではuserという変数にFactoryBotのデータを入れているので、例えばuser.nameのように呼び出して使うことができます。

このままでもいいのですが、実際のサービスはユーザーがどんなデータを入れてくるかわからないこともあるのでそれを想定していた方がいいと思います。
そのために使うのがFakerです。

Faker

Fakerとは、Railsで利用できるGemです。
ランダムな値を生成してくれて、人名や住所や電話番号など様々用意されています。
Faker公式Github
アニメの名前とか登場人物とかもあって見てるだけでも面白いです。

ちなみに日本語のデータがよければgimeiというGemもあります。
例えばバリデーションで日本語のカナじゃないとダメとかにしてる場合はFakerよりもgimeiが使いやすいと思いました。
gimei公式Github

Faker導入

下記のようにGemfilegroup :development, :test dogem 'faker'と記述します。

Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 4.0.0'
  gem 'factory_bot_rails'

# 以下を追加
  gem 'faker'
end

ターミナルで下記のコマンドを実行しGemを導入します。

ターミナル
 bundle install

先ほどのFactoryBotの内容をFakerを利用したものに変更します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name                  {Faker::Name.initials(number: 2)}
    email                 {Faker::Internet.free_email}
    password              {Faker::Internet.password(min_length: 6)}
    password_confirmation {password}
  end
end

書き方は公式のGIthubにも書いてあるのですが
Faker::の後に指定したいものを選択します。

例えば名前だけでもたくさんあります。

Faker
Faker::Name.name                 #=>フルネームの名前
Faker::Name.first_name           #=>firstnameだけ
Faker::Name.initials             #=>イニシャル
Faker::Name.initials(number: 2) #=>2文字のイニシャル

基本的に公式に書いてある通りに記述すればOKなので簡単にできます。
コンソールでデータを見ると面白いですよ。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

The Rails Doctrine(日本語訳)

(訳者注: 原文は https://rubyonrails.org/doctrine/ です。しばらく寝かして問題なさそうであれば本家に投げようかと思っています。おかしいところがあればコメント・編集リクエストをお待ちしております。)

The Rails Doctrine

By David Heinemeier Hansson in January, 2016

Ruby on Railsの驚異的な台頭は、斬新な技術とタイミングによるところが少なからずあります。しかし、技術的な優位性は時間の経過とともに失われていきますし、タイミングの良さだけでは長期にわたってムーブメントを維持できません。そのため、Railsがどのようにして的確さを維持してきたのかだけでなく、どのようにしてそのインパクトとコミュニティを成長させてきたのかについて、より広範な説明が求められています。私が提唱するのは、永続的な実現要因は、今も昔も物議を醸しているドクトリン(信条)にあるということです。

このドクトリンは過去10年の間に進化してきましたが、その最も強力な柱のほとんどは開発当初からのものでもあります。私はこれらの考え方の根本的なオリジナリティを主張していません。Railsの主な成果は、プログラミングとプログラマーの本質についての異端的な考えを手広く集めたものを中心に、強力な部族を団結させ、育成したことです。

以上のことを踏まえて、Railsのドクトリンとして最も重要な9つの柱を以下に紹介します。

  • プログラマーの幸せへの最適化 Optimize for programmer happiness
  • 設定より規約 Convention over Configuration
  • メニューはおまかせ The menu is omakase
  • 唯一のパラダイムはない No one paradigm
  • 美しいコードの称賛 Exalt beautiful code
  • 鋭利なナイフの提供 Provide sharp knives
  • 価値の統合されたシステム Value integrated systems
  • 安定性よりも進歩 Progress over stability
  • 大きなテントの押し上げ Push up a big tent

プログラマーの幸せへの最適化 Optimize for programmer happiness

RubyなしではRailsは存在しません。そのため、最初の信条の柱がRubyを作る動機の核心から直接導き出されたものであることは当然のことです。

Rubyの独特な特徴は、プログラマーの幸せを台座に据えたことでした。それ以前のプログラミング言語やエコシステムを牽引してきた他の多くの競合する有効な懸念事項よりも優先して、です。

Pythonが「何かをするための方法は一つしかない」を長所として掲げるのに対し、Rubyは表現力と繊細さを追求してきました。Javaがプログラマーを自分たちから強制的に保護することを主張していたのに対し、Rubyは初学者向けキットに鋭利なナイフ一式を入れていました。Smalltalkがメッセージパッシングの純粋さを追求していたのに対し、Rubyはキーワードやコンストラクトを貪欲に蓄積していきました。

Rubyが他の言語と異なっていたのは、Rubyが異なるものを大切にしていたからです。そして、それらの多くはプログラマーの幸せへの憧れにつながるものでした。Rubyのポリシーは、他の多くのプログラミング環境だけでなく、プログラマーとは何か、どのように行動すべきかという主流の認識とも対立していました。

Rubyはプログラマーの感情を認識するだけでなく、それを受け入れ、高めようとしました。それが不完全であろうと、気まぐれであろうと、喜びであろうと。Matzは、驚くほど複雑な実装のハードルを飛び越えて、機械が人間の共犯者を見て笑っているように見えたり、お世辞を言っているように見えたりするようにしました。Rubyは、私たちの心の目にはシンプルで明確で美しく見えるものが、実際には床下配線のアクロバティックな混乱であるような錯覚に満ちています。これらの選択は容易に達成されたものではありませんでした(この魔法のオルゴールをリバースエンジニアリングしようとしたことについてはJRubyのクルーに尋ねてみましょう!)。それこそが、彼らが賞賛に値する理由です。

私のRubyへの恋心を決定づけたのは、プログラミングとプログラマーのための新たなビジョンへの真摯な姿勢でした。それは単に使いやすさだけではありません。ブロックの美学だけでもありません。また一つの技術的な成果でもありません。それはビジョンでした。カウンターカルチャーでした。既存のプロのプログラミングの型にはまってしまった人たちが、同じような心を持った人たちと仲間になるための場所でした。

私は以前、このRubyの発見を、自分の脳に完璧にフィットする魔法の手袋を見つけたと表現したことがあります。それまで自分に合った手袋として期待していたあらゆる想像を上回るものでした。しかし、それ以上のものでした。「プログラムが必要だったからプログラミングする」から、「知的なエクササイズと表現の手段としてのプログラミングが気に入ったからプログラミングする」へと、私自身の変化を示す出来事だったのです。それは、フローの源泉を見つけて、それを自由にオンにすることができるようになったことでした。チクセントミハイの仕事をよく知っている人にとっては、このインパクトは言い過ぎではないでしょう。

Rubyは私を変え、私のライフワークへの道を切り開いてくれたと言っても過言ではありません。それほど深い啓示でした。それは私にMatzの創造物に奉仕して宣教活動をするという召命を吹き込みました。この深遠な創造とその恩恵を広める手助けをするために。

皆さんの多くが信じられずに首を振っているのは想像できます。私はあなたを責めるつもりはありません。もし私がまだ「プログラミングはただの道具」というパラダイムの下で生きていたときに、誰かが上記の経験を説明してくれたら、私も首を横に振っていたでしょう。そして、宗教的な言葉を使った大げさな表現には笑っていたでしょう。しかし、これが真実であるためには、一部の人やほとんどの人が不快に思うようなことであっても、正直でなければなりません。

それはさておき、このことはRailsにとって何を意味し、この原則はどのように進化の指針となり続けるのでしょうか? その問いに答えるためには、初期の頃にRubyを説明するのによく使われていた別の原則、「驚き最小の原則」を見てみるのが有益だと思います。Rubyはあなたが期待するように振る舞うべきです。これはPythonとの対比で簡単に説明できます。

    $ irb
    irb(main):001:0> exit
    $ irb
    irb(main):001:0> quit

    $ python
    >>> exit
    Use exit() or Ctrl-D (i.e. EOF) to exit

Ruby は exitquit の両方を受け入れ、プログラマが対話型コンソールを終了したいという明らかな欲求に対応しています。一方、Python は、(エラーメッセージを表示しているため) 何を意味しているのか明らかに分かっているにもかかわらず、要求されたことを適切に行う方法をプログラマに指示します。これは、小さいとはいえ、「驚き最小の原則」のかなり明確な例です。

「驚き最小の原則」が Ruby コミュニティの支持を得られなくなった理由は、この原則が本質的に主観的だからです。誰にとって驚きが最小なのでしょうか? まあ、Matzにとってですね。そして、彼と同じように驚いている人たちです。Ruby コミュニティが成長し、Matz とは異なることに驚く人の割合が増えていくと、メーリングリストでは、この原則は実りのない自転車小屋の議論の源になっていきました。そこで、XさんがYという挙動に驚いたかどうかについて、どこにも行かないような議論をさらに招くことを避けるために、この原則は背景に消えていったのです。

では、繰り返しになりますが、これがRailsと何の関係があるのでしょうか。まあ、Railsは「(Matzにとっての)驚き最小の原則」と似たような原理で設計されています。「(DHHにとっての)微笑み最大の原則」という原則は、まさにブリキに書いてある通りです。私をもっともっと広く笑顔にしてくれるようなものに細心の注意を払って設計されたAPIです。このように書き出すと、ほとんどコミカルなナルシストのように聞こえますし、私でさえ、その第一印象に反論するのは難しいと思います。

しかし、RubyやRailsのようなものを作るということは、少なくともその最初の段階では、深く自己愛的な献身の賜物なのです。どちらのプロジェクトも、一人のクリエイターの頭の中から生まれたものです。しかし、おそらく私はここでMatzに自分の動機を投影しているのかもしれないので、私の宣言の範囲を私が知っていることに絞ってみます。私は私自身のためにRailsを作りました。まず第一に、私を笑顔にするために。Railsの実用性は、私の人生をより楽しいものとするためにRailsに持たせた、その能力に従属するものでした。Web情報システムへの要求やリクエストをこなす日々の労苦を豊かにするために。

Matzのように、私も時折、自分の主義主張を貫くためにふざけたことをしました。その一例が、PersonクラスをPeopleテーブルに、AnalysisクラスをAnalysesに、そしてCommentクラスをCommentsにマッピングするのに十分な英語のパターンと不規則性を理解しているクラスであるInflectorです。この動作は現在ではRailsの疑う余地のない要素として受け入れられていますが、まだドクトリンとその重要性がまとまっていなかった初期の頃には、論争の火種が猛威を振るっていました。

実装の労力はそれほどかからなかったものの、ほぼ同じくらいの騒動を引き起こした別の例を紹介します。配列#secondから#fifthまで(および#forty_twoは荒らし対策として)です。これらのエイリアスアクセサは、Array#[1]Array#[2](及びArray[41])のように書くことができるものの肥大化(及び文明の終焉に近いもの)として非難され、たいへん声高な支援者に深く不快感を与えました。

しかし、この二つの決定は今でも私を笑わせてくれます。テストケースやコンソールでpeople.thirdを書く楽しさを満喫しています。もちろん、この楽しさは論理的ではありません。効率的ではありません。病的かもしれません。しかし、それは私を笑顔にし続け、こうして原則を満たし、私の人生を豊かにし、12年間のサービスの後にRailsに関わり続けていることを正当化するのに役立っています。

パフォーマンスの最適化とは異なり、幸福度の最適化を測定するのは困難です。そのため、ほとんど本質的に非科学的な努力になっています。ある人にとっては重要性が低く、イライラさせてしまっているかもしれません。プログラマは、測定可能なものを議論し、征服するように教えられています。明確な結論があり、AがBよりも優れていると断言できるものを、です。

とはいえ、幸せの追求はミクロレベルでは測定が困難ですが、マクロレベルで観察するとはるかに明確になります。Ruby on Railsコミュニティには、まさにこの追求のためにここにいる人たちがたくさんいます。彼らは、より良い、より充実した仕事人生を自慢しています。このような感情の集合体の中で、勝利は明らかなのです。

というわけで、結論です。幸せのために最適化することは、おそらくRuby on Railsの最も形成的な鍵なのです。そして今後もそうあり続けるでしょう。

設定より規約 Convention over Configuration

Railsの初期の生産性のモットーの1つは、次のようなものでした。「あなたは美しくてユニークな雪の結晶ではない」というものです。空虚な個性を手放すことで、平凡な意思決定の煩わしさを跳ね除け、本当に重要な分野ではより速く進歩することができると仮定しています。

データベースの主キーがどのような形式で記述されているかなんて誰が気にするでしょうか? それが "id"、"postId"、"posts_id"、または "pid "であるかどうかは本当に重要でしょうか? これは繰り返し審議する価値のある決定なのでしょうか? いいえ、そうではありません。

Railsのミッションの一部は、Web用の情報システムを作成する開発者が直面する、分厚くて増え続けている反復的な意思決定のジャングルに鉈を振るうことです。このような意思決定は何千もありますが、本来は一度で済むことですし、誰かが代わりにやってくれるのであれば、それに越したことはありません。

設定を慣習に移行することで、私たちは審議から解放されるだけでなく、より深い抽象化を成長させるための青々としたフィールドを提供してくれます。
Person クラスの people テーブルへのマッピングに頼ることができれば、同じ屈折規則を使って has_many :people として宣言されたアソシエーションを Person クラスを探すためにマッピングすることができます。優れた規約の力は、幅広い使用範囲で利益を得ることができるということです。

しかし、エキスパートの生産性向上だけでなく、初心者の参入障壁を下げることもできます。Railsには、初心者が知らなくても、知らなくても恩恵を受けられるような規約がたくさんあります。すべてのものがなぜそのようになっているのかを知らなくても、素晴らしいアプリケーションを作ることは可能です。

フレームワークが分厚い教科書に過ぎず、新しいアプリケーションが白紙の紙切れに過ぎないのであれば、そんなことは不可能です。どこからどのように始めればいいのかを把握するのには、膨大な努力が必要です。始めるための戦いの半分は、引っ張るべきスレッドを見つけることです。

すべてのピースがどのように組み合わされているのかを理解している場合も、同じことが言えます。変更する際、変更後の状態がいつでも明確であれば、私たちは、その前のすべてのアプリケーションと同じか、非常に似ているアプリケーションの多くの部分を、駆け足で通過することができます。すべてのもののための場所、すべてが備わっている場所。制約は、最も有能な心をも解放します。

しかし、何事もそうですが、慣習の力には危険がないわけではありません。Railsがこれだけ多くのことをあまりにも取るに足らないようにみせていると、アプリケーションのあらゆる側面は既成のテンプレートで作れるのではないかと考えがちです。しかし、構築する価値のあるほとんどのアプリケーションには、何らかの方法でユニークな要素があります。それは5%や1%に過ぎないかもしれませんが、そこに存在してします。

難しいのは、いつ慣習から逸脱するかを把握することです。逸脱した特殊性は、いつ、遠出を正当化するのに十分なほど重大なものなのでしょうか? 私は、美しくユニークな雪片になりたいという衝動のほとんどは、十分に考慮されておらず、Railsから外れて行くことのコストが過小評価されていると主張しますが、慎重に検討するまでもないこともあるでしょう。

メニューは「おまかせ」 The menu is omakase

何が美味しいか分からないのに、レストランで何を注文すればいいのか、どうやってわかるのでしょうか? そうですね、シェフに選んでもらえば、何が「美味しいもの」かわからないうちから、たぶん「美味しいもの」にありつけます。それが「おまかせ」です。料理の達人でなくても、暗中模索の運任せでなくても、美味しいものを食べるための方法なのです。

プログラミングにとって、他の人にスタックの組み立てを任せるというこのプラクティスの利点は、「Convention over Configuration」から得られるものと似ていますが、さらに高いレベルでのものです。CoCが個々のフレームワークをどのように使うのがベストなのかということで占められているのに対し、おまかせはどのフレームワークをどのように組み合わせて使うのかということに関心があります。

これは、利用可能なツールを個々の選択肢として提示し、個々のプログラマに決定する特権(と負担)を与えるという崇高なプログラミングの伝統と対立しています。

「仕事に最適なツールを使え」という言葉を聞いたことがあるでしょうし、うなずいたこともあるでしょう。これは議論の余地がないほど基本的なことのように聞こえますが、「最高のツール」を選べるかどうかは、自信を持って「最高」を決められるほどの根拠にかかっています。これは見た目よりもずっと難しいことです。

これは、レストランでのディナーの問題に似ています。そして、8品の食事の各コースを選ぶように、個々のライブラリやフレームワークを選ぶことは、独立して行う仕事ではありません。どちらの場合も、目的はディナーやシステム全体を考慮することです。

そこでRailsでは、道具箱の中から好きなツールをそれぞれ選ぶというプログラマーの個人的な特権である善の一つを減らすことにしました。より大きな善、「何にでも使えるより良いツールボックス」のためにです。その結果、多くの利益を得ることができました:

  1. 数は安全性を生む: ほとんどの人が同じデフォルトの方法でRailsを使っているとき、私たちは同じ経験を共有しています。このような共通の基盤があると、人に教えたり助けたりするのが格段に容易になります。アプローチについての議論の基礎を築くことができます。昨日の夜7時にみんなで同じ番組を見たので、次の日にはその話をすることができます。それはより強いコミュニティの感覚を育みます。

  2. みんなで同じ基本ツールボックスを完成させる: フルスタックフレームワークであるRailsには多くの動作部品があり、それらがどのように連携して動作するかは、単体で何をするかと同じくらい重要です。ソフトウェアの痛みの多くは、個々のコンポーネントではなく、その相互作用から生じます。同じように構成され、同じように失敗するコンポーネントの共通の痛みを和らげることに全員で取り組めば、痛みを経験することは少なくなります。

  3. 置き換えることもできるが、置き換えなくともよい: Railsはおまかせスタックですが、特定のフレームワークやライブラリを他の物で置き換えることはできます。ただ、それは必須ではありません。つまり、特別な違いを好むような明確で個人的なパレットを開発するまで、そのような決定を遅らせることができるということです。

なぜなら、Railsに入ってきた、あるいはRailsにとどまっている、最も経験豊富で熟練したプログラマーでさえ、メニューのすべてに反対しているわけではないはずだからです(もしそうであれば、おそらくRailsに留まっていなかったでしょう。) そのため、彼らは真摯に代替品の選別を行い、その後、他の人々と同様に残りの部分を楽しむようにしています。

唯一のパラダイムはない No one paradigm

一つの中心的なアイデアを掲げて、そこから論理的帰結としてアーキテクチャの基盤を導こうとする強く熱心な主張があります。このような規律には純粋さがあるため、プログラマーが自然とこの明るい光に惹かれるのは明らかです。

Railsはそうではありません。一枚の完璧な反物ではありません。キルトです。多くの異なるアイデアやパラダイムの複合体です。その多くのものは、単独で一つ一つ対比させれば、通常は対立していると見られるようなものかもしれません。でも、私たちがやろうとしているのはそのような対立ではありません。それは、唯一の勝者が宣言されなければならない、優れたアイデアの選手権ではないのです。

Rails MVCの中のビューを構築しているテンプレートを見てみましょう。デフォルトでは、これらのテンプレートからコードを抽出されたヘルパーは、すべてを大きな鍋に突っ込まれた関数にすぎません! 名前空間すら一つにまとめられています。なんと衝撃的で恐ろしいことでしょう、昔ながらのPHPのスープのようなものです!

しかし、ビューテンプレートの多くの抽象化がそうであるように、相互作用をほとんど必要としない個々の関数を提示するという点では、PHPは正しかったと私は考えています。そして、この目的のためには、単一の名前空間、メソッドの大きな鍋は、合理的な選択であるだけでなく、優れた選択でもあります。

だからといって、ビューを構築する際に、よりオブジェクト指向的なものに手を伸ばしたくなることもないわけではありません。プレゼンターのコンセプトは、相互に依存している多くのメソッドとその下にあるデータをラップすることで、依存関係によって酸っぱくなってしまったメソッドのスープの完璧な解毒剤になることがあります。しかし、これは一般的に適合するものではなく、稀に適合するものであることが示されています。

一方、私たちは通常、MVCにレイヤー化されたケーキのうちのモデルについては、オブジェクト指向の素晴らしさの最高の砦として扱っています。オブジェクトにちょうど良い名前を見つけ、凝集度を高め、結合度を減らすことがドメインモデリングの面白さです。ビューとは全く違うレイヤーなので、私たちは別のアプローチをとっています。

しかし、ここでも私たちはシングルパラダイムのドグマを支持していません。Railsのconcerns、Rubyのミックスインの特殊版は、個々のモデルに非常に広い拡張を与えるためによく使われています。これは、関係するメソッドが相互作用するデータやストレージに直接アクセスできるようにすることで、アクティブレコードパターンによく合っています。

Active Recordフレームワークの基礎でさえも、一部の純粋主義者を怒らせています。私たちは、データベースと直接連動するために必要なロジックを、ビジネスドメインのロジックと混ぜてしまっています。なんという境界の侵犯! その通りですね。これは、ドメインモデルの状態を維持するために、事実上常に何らかのデータベースとつながっているウェブアプリを実現するための実用的な方法であることが明らかになったためです。

思想的に柔軟であることが、Railsがこのような幅広い問題に取り組むことを可能にしています。ほとんどの個別のパラダイムは、問題空間のある範囲内では非常にうまく機能しますが、自然な範囲を超えて適用されると、厄介なものになったり、硬直化したりします。たくさんの重複するパラダイムを適用することで、私たちは側面をカバーし、後方をガードします。最終的にできるフレームワークは、個々のパラダイムが許容していたであろうよりもはるかに強力で、より有能なものになります。

さて、プログラミングについての多くのパラダイムとこのような多元的な関係を持つことの代償は、概念的なオーバーヘッドです。オブジェクト指向プログラミングを知っているだけではRailsを楽しむことはできません。手続き的な経験や関数的な経験も十分に積んでいる方が望ましいのです。

これはRailsの多くのサブ言語にも当てはまります。私たちは、例えばビューのためのJavaScriptや、時折複雑なクエリのためのSQLを学ばなければならないことから、あなたを遮ろうとはしていません。少なくとも、可能性のピークに達するまでは。

学習の負担を軽減する方法としては、フレームワークのあらゆる側面を理解する前に、簡単に始められるようにすることが挙げられます。怒涛のハローワールドが用意されているのはこのためです。あなたのためのテーブルはすでに準備されていて、前菜が並べられています。

本当の価値のあるものを早い段階で提供することで、Railsの実践者に早くレベルアップしてもらおうという考えです。彼らの学習の旅を障害ではなく、楽しめるものとして受け止めてください。

美しいコードの称賛 Exalt beautiful code

私たちは、コンピュータや他のプログラマーに理解されるためだけにコードを書くのではなく、あたたかな美しさの光を浴びるためにコードを書きます。美的に優れたコードはそれ自体が価値であり、精力的に追求されるべきものです。だからといって、美しいコードが常に他の懸念事項に勝るというわけではありませんが、優先順位のテーブルに完全に座るべきです。

では、美しいコードとは何でしょうか? Rubyではたいていの場合、ネイティブのRubyイディオムと各ドメイン固有言語の力との間のどこかにあります。これは曖昧な基準ですが、取り組んでみる価値は十分にあります。

以下はActive Recordの簡単な例です。

  class Project < ApplicationRecord
    belongs_to :account
    has_many :participants, class_name: 'Person'
    validates_presence_of :name
  end

これは DSL のように見えますが、実際にはただのクラス定義で、シンボルとオプションを取る 3 つのクラスメソッドを呼び出しています。派手なものは何もありません。しかし、確かに見事です。確かにシンプルです。この数少ない宣言から、膨大な量のパワーと柔軟性を得ることができます。

美しさの一部は、これらの呼び出しが以前の原則を尊重していることから来ています。 belongs_to :account を呼び出すとき、外部キーは account_id と呼ばれ、それがプロジェクトのテーブルにあることを前提としています。participantsアソシエーションの役割の指定としてclass_namePersonを必要とする場合、そのクラス名の定義を必要とするだけです。そこから我々は、再び、外部キーと他の設定ポイントを導出します。

データベース移行システムからの別の例を見てみましょう。

    class CreateAccounts < ActiveRecord::Migration
      def change
        create_table :accounts do |t|
          t.integer :queenbee_id
          t.timestamps
        end
      end
    end

これがフレームワークの力の本質です。プログラマは特定の規約に従ってクラスを宣言します。例えば、#changeを実装したActiveRecord::Migrationのサブクラスのように、フレームワークはその周りのすべての下回りをつなげ、これが呼び出すべきメソッドであることを知ることができます。

こうして、プログラマの書くべきコードは非常に少なくなります。マイグレーションの場合、rails db:migrateを呼び出してデータベースをアップグレードして新しいテーブルを追加するだけでなく、別の呼び出しでこのテーブルを削除することもできます。これは、プログラマーが同様のことを実現するために、自分自身で呼び出すライブラリを使ってワークフローをつなぎ合わせるのとは大きく異なります。

しかし、美しいコードはもっと些細な場合もあります。何かを可能な限り短くしたり、強力にしたりすることよりも、宣言のリズムが流れるようにすることの方が重要なのです。

以下の2つの文も同じことを行います。

    if people.include? person
      

    if person.in? people

しかし、フローとフォーカスは微妙に異なっています。最初の文では、焦点はコレクションにあります。それが主語です。2つ目の文では、主語は明らかに人です。2つのステートメントの間には長さの差はあまりありませんが、条件がその人についてのものである場所で使われると、2つ目のステートメントの方がはるかに美しく、私を笑顔にしてくれる可能性が高いと主張します。

鋭利なナイフの提供 Provide sharp knives

Rubyは機能の引き出しの中に鋭いナイフをたくさん含んでいます。それは偶然そうなったのではなく、そのように設計されたためです。最も有名なのはモンキーパッチング、既存のクラスやメソッドを変更するものです。

この力は、普通の人間であるプログラマーには扱えないと揶揄されてきました。より制限の多い環境から来た人たちは、言語の利用者にこういった機能を与えるような絶大な信頼は、Rubyを破滅させる様々な災難を引き起こすことを想像していました。

もし何でも変更できるのであれば、String#capitalize を上書きして "something bold".capitalize"Something bold" ではなく "Something Bold" を返すようにすることを止める仕組みがあるでしょうか? このような変更はローカルアプリケーションではうまくいくかもしれませんが、元の実装に依存する様々な補助コードを壊してしまうことになります。

「何もない」、というのが答えです。あなたが鋭いナイフを使うのを理性によって止めさせるための、プログラム的な仕掛けはRubyにはありません。私たちはそのような良識を、慣習によって、ナッジによって、そして教育によって強制しています。キッチンで鋭いナイフを使うことを禁止し、トマトを切るのにもスプーンを使うことを強制したりするのではありません。

なぜなら、モンキーパッチの裏側にあるのは、2.day.previous(現在から2日前の日付を返す)のような驚異的な偉業を成し遂げる力だからです。今、あなたはそれが割りに合わない取引と思うかもしれません。プログラマーがString#capitalizeを上書きするのを防ぐためなら2.days.previousを失うことを望むかもしれません。もしあなたがそのようなスタンスであれば、あなたはRubyには向いていないかもしれません。

しかし、コアクラスやメソッドを変更する力が言語としてのRubyを破滅させたと主張するのは難しいでしょう -- ある程度のセキュリティのためであればそのような自由を手放してしまう人でさえも。それどころか、この言語が繁栄したのは、プログラマーの役割についての異なる急進的な視点を提供したからに他なりません。鋭利なナイフを渡しても信頼するということです。

そして、単に信頼するだけでなく、そのような有能なツールの使い方を教えることができたのです。ほとんどのプログラマーは、指を切らずに鋭いナイフを使いこなすことができる、より優れたプログラマーになりたいと思っているだろうと仮定することで、職業全体を向上させることができるということです。それは信じられないほど野心的な考えであり、他のプログラマーについての多くのプログラマーの直感に反しています。

なぜなら、鋭利なナイフの価値が争われるとき、それは常に他のプログラマーについてのものだからです。私はまだ一人のプログラマが手を挙げて「自分のこの力は信用できないから、この力を奪ってくれ!」と言うのを聞いたことがありません。それはいつも「他のプログラマーがこれを悪用すると思う」というものです。その父権主義的な路線は私には魅力を感じたことがありません。

そこでRailsの話になります。フレームワークで提供されるナイフは、言語で提供されるナイフほど鋭くはありませんが、それでも切りたくてたまらない人はたくさんいます。このようなツールをキットの一部として提供することに謝罪はしません。実際には、私たちは、仲間のプログラマーの願望に十分な信頼を持っていることを祝うべきなのです。

Railsの多くの機能は、時間の経過とともに「自由度が高すぎる」として議論されてきました。しかし、現在流行している例として、concerns機能があります。これはRubyの組み込み機能であるモジュールに加えたシンタックスシュガーの薄い層で、1つのクラスで複数の、関連しながらも独立して理解される懸念事項(concernsの名前の由来です)をカプセル化できるように設計されています。

concernsは、プログラマーがオブジェクトを肥大化させてしまいかねない、ごちゃごちゃしたものを詰め込むための新しい引き出しのセットを提供してしまっている、という批判があります。そして、それは本当です。concernsは確かにそのように使うことができます。

しかし、concernsのような機能を提供しないことは壮大な誤りです。この力を温和な手で使っても、概念の部分的分離を巧みに行うことができれば、プログラマーは素晴らしいアーキテクチャへの道を歩むことになるでしょう。もしあなたが過剰なconcernsからキッチンシンクを守ることを信頼できないのであれば、まばゆい優雅の指針を手に入れることはできないでしょう。

鋭いナイフを振り回すことを学んでいないプログラマーは、まだメレンゲを作ることはできません。ここでは最適な言葉として「まだ」と言っておきます。私は、すべてのプログラマには、権利とまではいかないまでも、完全に有能なRubyとRailsのプログラマになる道があると信じています。そして、有能というのは、コンテキストに応じて、引き出しの中のさまざまな、時に危険なツールをいつ・どのように使うべきかを知っている、という意味です。

だからといって、彼らがそこにたどり着くのを助ける責任を放棄しているわけではありません。言語とフレームワークは、誰もが専門家になれるように手助けし、導く忍耐強いチューターでなければなりません。一方で、そこに至る唯一の信頼性の高いコースは間違いだらけの場所--間違って使用されるツール、血、汗、そしておそらく涙--をうまく通過することを認識しています。他の方法は単にありません。

Ruby on Railsは、シェフになりたいやシェフになった人のための環境です。最初は料理をすることから始めるかもしれませんが、キッチンを運営するまでの道のりを歩んでいくことができます。その旅の一環として、業界最高のツールを使わせるほどあなたを信頼できないと誰にも言わせないようにしましょう。

価値の統合されたシステム Value integrated systems

Railsは様々な文脈で利用できますが、その初恋は統合システムーー壮大なモノリス! すなわち問題全体に対処できるシステム全体ーーを作ることです。つまり、Railsはライブアップデートを行うために必要なフロントエンドのJavaScriptから、本番でデータベースをどのようにバージョン間で移行するかまで、すべてに関わっているということです。

これまで議論してきたように、これは非常に広い範囲ですが、一人の人間が理解するのが現実的ではないというほどではありません。Railsは特に、ジェネラリストの個人がこれらの完全なシステムを作れるようにすることを目指しています。その目的は、専門家を小さなニッチ分野に隔離し、永続的な価値を持つものを構築するためにそのような完全なチームを必要とすることではありません。

このように個人に力を与えることに焦点を当てているのが、統合されたシステムなのです。統合システムでは、たくさんの不必要な抽象化を削減し、レイヤー間の重複を減らし(サーバーとクライアントの両方のテンプレートのような)、何よりも、どうしても必要になるまでは、システムを分散することを避けることができます。

システム開発における複雑さの多くは、AとBの間の呼び出し方法を制限する新しい境界を導入することに由来します。オブジェクト間のメソッド呼び出しは、マイクロサービス間のリモートプロシージャコールよりもはるかに単純です。分散の巣穴に足を踏み入れた人たちを待ち受けるのは、失敗状態や待ち時間の問題、依存関係の更新スケジュールなど、まったく新しい世界の苦痛です。

このディストリビューションが必要な場合も時にはあります。他の人がHTTPで呼び出せるようなAPIをWebアプリケーションに作りたいのであれば、それを我慢してこれらの問題の多くに対処しなければなりません(リクエストをアウトバウンドで送るよりもインバウンドで処理する方がはるかに簡単ですが、あなたのダウンタイムは他の誰かの障害状態です!)。しかし、これは少なくとも、あなた自身の個人的な開発経験に与えられた、限定的なダメージです。

もっとひどいことは、システムが早まって分散化されてしまい、サービスや、あるいはマイクロサービスに分断されてしまうことです。このような状況は、モダンなインターネットアプリケーションを作りたければ、何度もシステムを構築しなければならないという単なる誤解から始まっていることも多くあります。サーバ側で一度、JavaScriptクライアント側で一度、ネイティブモバイルアプリケーションのそれぞれで一度、などです。これは自然の法則に則ったことではなく、そうである必要はありません。

アプリケーション全体の大きな塊を複数のアプリやアクセスにまたがって共有することは完全に可能です。ネイティブモバイルアプリに埋め込まれたものと同じコントローラとビューのしくみをデスクトップウェブで使用することも同様です。壮大で見事なモノリスこと統合システムの中に、可能な限りのことを集中させることができるのです。

これはすべて、スピード、ユーザーエクスペリエンスなど、開発者を早まった分散化に誘導しがちな点については何も妥協することなく実現されています。

これこそが、私たちが求める「すべてのもののほとんどを備え持つ」ことなのです。使いやすく理解しやすい統合された単一のシステムによる、個別にチューニングされた分散アプリケーションの力です。

安定性よりも進歩 Progress over stability

Railsのように10年以上前から存在しているシステムでは、自然と硬直化に向かう傾向があります。どんな変更でも、過去の挙動に依存していた誰かにとって、問題になる可能性はいくらでもありえます。そして、実際にそれが当てはまる当人にとっては公平な理由です。

しかし、あまり保守的な声に耳を傾けすぎてしまうと、その反対側に何があるかが見えなくなってしまいます。私たちは、進化と成長のために、時にはあえて壊したり、やり方を変えたりしなければなりません。この進化こそが、これからの(数?)十年にわたってRailsが生存と繁栄に適した状態を維持することになるのです。

これは論理的には理解しやすいですが、これを受け入れて実践するのははるかに難しいことです。特に、Railsのメジャーバージョンでの下位互換性のない変更が原因で、自分のアプリケーションが壊れてしまった場合はなおさらです。そのようなときこそ、私たちは安定性よりも進歩を大切にし、壊れたものをデバッグして、それを解明し、時代とともに歩んでいく強さを与えてくれる、この価値を覚えておく必要があるのです。

これは、必要のない、あるいは過剰なダメージをいたずらに与えてもよいということではありません。2.xから3への大規模なRailsの移行は、その当時の関係者の多くに傷跡として今でも残っています。実に大変なことでした。深刻な大変動により、3へ移行できず2.xに長い間取り残されてしまった多くの人たちは、納得できないほど酸っぱくなっていました。しかし、大局的には、やはり実行する価値のあることでした。

これは、私たちがこれからも続けていかなければならない厳しい交渉です。今日行った変更によって、5年後のRailsはより良い状態になっているでしょうか? ジョブキューやWebSocketsのような別の問題領域を採用したほうが、数年後のRailsの利益になるのでしょうか? もしそうだとしたら、それを取り入れて作業しようではありませんか。

この作業はRails自体だけでなく、より大きなRubyコミュニティでも行われる必要があります。Railsは、Rubyコミュニティの人々がより新しいバージョンを採用するように促すことで、Rubyの進歩を支援する最前線に立つべきです。

これまでのところ、私たちはこの点では非常によくやっています。私が始めたときから、Ruby 1.6、1.8、1.9、2.0、2.1、2.2、2.3、2.4、2.5、そして現在は2.6へと移行してきました。この間、多くの大きな変更がありましたが、RailsはRubyの後ろ盾となり、誰もがより速くプログラムを利用できるようにするために存在していました。これは、RailsがRubyの主要な普及者としての特権と義務の一部です。

これは補助ツールチェーンにも当てはまります。Bundlerはかつて物議をかもしたアイデアでしたが、未来を共有するための礎となるものだとRailsが主張したことで、今日では当たり前のように使われるようになりました。アセットパイプラインや、永続的なコマンドプロセスであるSpringなどについても同じことが言えます。これら3つはいずれも、成長の痛みを経験したか、あるいは今も経験していますが、長期的に見て価値があることが明らかであるため、私たちはそれを押し通してきました。

進歩とは、最終的にはほとんどの場合、変化を推し進めようとする人々と、その意欲にかかっているのです。Rails CoreRails Committersのようなグループに終身の席がないのはこのためです。どちらのグループも、フレームワークの進歩に積極的に取り組んでいる人たちのためのものです。ある人にとっては、そのような進歩への関与はわずか数年で終わり、私たちは彼らのサービスに永遠に感謝するでしょうが、他の人にとっては何十年も続くかもしれません。

同様に、このことはコミュニティの新しいメンバーを歓迎し、奨励し続けることが非常に重要な理由でもあります。私たちは、より良い進歩を遂げるために、新鮮な血と新鮮なアイデアを必要としています。

大きな傘を広げる Push up a big tent

多くの物議を醸すアイデアを持つRailsは、すべての人に常にすべての信条を完全に尊重することを求めれば、すぐに偏った思想の隠者による孤立した集団になる可能性があります。だから私たちはそうしません!

私たちには意見の相違が必要です。方言が必要です。思想や人物の多様性が必要です。このアイデアのるつぼの中にこそ最高の共有物があるのです。多くの人々が、コードや考察に基づく議論を通じて、協力しています。

このように、このドクトリンでは理想化された形を説明していますが、日常の現実はもっと繊細な(そして興味深い)ものです。Railsがこのような大規模なコミュニティを一つの傘のもとで支えることができるのは、リトマス試験がない(あったとしてもほとんどない)からです。

私がしばしば重大な不満を表明してきたテスト用DSLであるRSpecの継続的な成功は、完璧な証拠です。私はなぜこれではいけないと思うのか、顔を真っ赤にしてわめき散らすことができますが、にも関わらずRSpecは依然として花を咲かせ、繁栄することができます。その点の方が遥かに重要なのです

APIとしてのRailsの到来についても同じことが言えます。私の個人的な焦点とこだわりは、ビューを含む統合システムにありますが、クライアントとサーバを分割したいと考えている人にも、Railsがうまくいく余地があることは間違いありません。私たちはこれを二次的なミッションとして共存できる限り受け入れるべきであり、それは確実に可能だと信じています。

大きな傘を広げるということは、すべての人に万能であろうとすることではありません。すべての人を歓迎し、自分の飲み物を持ってくることを許可するということです。他の人にも参加してもらうことで、私たちの魂や価値観を失う必要はありませんし、おいしい飲み物の新しい混ぜ方も学べるかもしれません。

これはタダではできません。歓迎するための努力が必要です。特に、あなたの目標が、すでにコミュニティの一部になっている人たちと同じような人たちをより多く集めることだけではないのであれば、なおさらです。参入障壁を下げることは、常に真剣に取り組むべき仕事です。

ドキュメントのスペルミスの修正を始めた隣の人が、次の素晴らしい機能を実装することになるかどうかは誰にもわかりません。しかし、あなたが微笑んで、どんな小さな貢献にも感謝を示すことで、モチベーションを高めさせ、その可能性も生まれるかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで競馬情報をスクレイピングしてみた

馬券を当てたかった

毎週500円ほど重賞レースに賭けているのですが、負けが続いたので予想アプリでも作ってみるかとなった。

まずは情報が必要だ

ということでJRAのサイトに下記のようなレース結果が載っているので、このデータをスクレイピングしてみました。
https://www.jra.go.jp/datafile/seiseki/replay/2021/001.html

2020年から現在までの重賞レース結果をcsvに出力します。
Rubyのバージョンは2.7.1

ソース

main.rb

require 'bundler/setup'
require 'nokogiri'
require 'open-uri'
require 'csv'
require 'pry'
require './create_url.rb'

INDEX_YEAR = [2020, 2021] # データを取得したい年
CSV_HEADER = ['日時', 'レース名', 'グレード', '着順', '馬名', '年齢', '騎手', '調教師']

def setup_doc(url)
  doc = Nokogiri::HTML.parse(URI.open(url, "r:CP932").read)
  doc.search('br').each { |n| n.replace("\n") }
  doc
end

def scrape_data(data)
  horse_data = []
  # 着順
  horse_data << data.children[1].text
  # 馬名
  horse_data << data.children[7].text
  # 年齢
  horse_data << data.children[9].text
  # 騎手
  horse_data << data.children[13].text
  # 調教師
  horse_data << data.children[25].text

  horse_data.map(&:strip)
end

def race_info(doc)
  race_info = []
  # 日時
  race_info << doc.xpath("//div[@class='cell date']").text.split('(').first
  # レース名
  race_info << doc.xpath("//span[@class='race_name']").text
  # グレード
  race_info << doc.xpath("//span[@class='grade_icon lg']").first.children.attribute('alt').value

  race_info.map(&:strip)
end

if __FILE__ == $0
  index_url_list = index_urls(INDEX_YEAR)
  urls = index_url_list.map do |index_url|
    index_doc = setup_doc(index_url)
    create_result_urls(index_doc)
  end.flatten

  CSV.open('../result.csv', 'w') do |csv|
    csv << CSV_HEADER
    urls.each do |url|
      begin
        doc = setup_doc(url)
        race_info_columns = race_info(doc)
        table_data = doc.xpath("id('race_result')/div[@class='race_result_unit']/table[@class='basic narrow-xy striped']/tbody[1]/tr")
        table_data.each do |data|
          csv << race_info_columns + scrape_data(data)
        end
      rescue => e
        p e
      end
    end
  end
end

create_url.rb

require 'active_support/all'

# 結果ページURLの配列を生成
def create_result_urls(doc)
  table_data = doc.xpath("id('contentsBody')/div[@class='scr-md']/table[@class='basic narrow-xy striped mt20']/tbody[@class='td_left']/tr")
  urls = table_data.map do |data|
    result_path = data&.children[15]&.children[0]&.values&.first
    "https://www.jra.go.jp/#{result_path}" if result_path.present?
  end

  urls.compact
end

# 年から重賞ページURLの配列を生成
def index_urls(years = [])
  urls = years&.map do |year|
    "https://www.jra.go.jp/datafile/seiseki/replay/#{year}/jyusyo.html"
  end

  urls
end

Gemfile
pry-byebugはデバッグ用です。
activesupportはcreate_url.rbでpresent?を使いたかったので追加しました。

gem 'nokogiri'
gem 'pry-byebug', '~> 3.9'
gem 'activesupport'

main.rb解説

定数

INDEX_YEAR = [2020, 2021] # データを取得したい年
CSV_HEADER = ['日時', 'レース名', 'グレード', '着順', '馬名', '年齢', '騎手', '調教師']

INDEX_YEARの配列に取得したいデータの年を入れます。
例えば2019を追加すれば、3年分のデータが取得できるようになります。

CSV_HEADERはまんま、ヘッダーです。とりあえずこの項目を取得することにしました。
馬体重やタイムなど欲しい情報が増えてきたら追加しようかなと思います。

パース

def setup_doc(url)
  doc = Nokogiri::HTML.parse(URI.open(url, "r:CP932").read)
  doc.search('br').each { |n| n.replace("\n") }
  doc
end

HTMLをパースしている箇所です。
open(url, "r:CP932")とするとwarningが出るため、URI.open()としています。
またそのままだと日本語が文字化けしてしまうのですが、r:CP932をセットすることで解決しました。

データ取得

def scrape_data(data)
  horse_data = []
  # 着順
  horse_data << data.children[1].text
  # 馬名
  horse_data << data.children[7].text
  # 年齢
  horse_data << data.children[9].text
  # 騎手
  horse_data << data.children[13].text
  # 調教師
  horse_data << data.children[25].text

  horse_data.map(&:strip)
end

def race_info(doc)
  race_info = []
  # 日時
  race_info << doc.xpath("//div[@class='cell date']").text.split('(').first
  # レース名
  race_info << doc.xpath("//span[@class='race_name']").text
  # グレード
  race_info << doc.xpath("//span[@class='grade_icon lg']").first.children.attribute('alt').value

  race_info.map(&:strip)
end

scrape_dataではそのレースの馬の情報を配列にして返します。
race_infoはそのレースの情報を配列にして返します。
また最後のmap(&:strip)で不要な改行や空白をまとめて削除しています。

実行箇所

if __FILE__ == $0
  index_url_list = index_urls(INDEX_YEAR)
  urls = index_url_list.map do |index_url|
    index_doc = setup_doc(index_url)
    create_result_urls(index_doc)
  end.flatten

  CSV.open('../result.csv', 'w') do |csv|
    csv << CSV_HEADER
    urls.each do |url|
      begin
        doc = setup_doc(url)
        race_info_columns = race_info(doc)
        table_data = doc.xpath("id('race_result')/div[@class='race_result_unit']/table[@class='basic narrow-xy striped']/tbody[1]/tr")
        table_data.each do |data|
          csv << race_info_columns + scrape_data(data)
        end
      rescue => e
        p e
      end
    end
  end
end

まずindex_urlsメソッドでデータが欲しい年のレース結果一覧URLの配列を取得します。
続いてそれをmapし、create_result_urlsメソッドで各レース結果のURLを取得します。
この辺はcreate_url.rbに定義しているものです。

結果ページURLの準備ができたらCSVに書き込みます。
まずはcsv << CSV_HEADERで項目を入れ、結果ページURLを回してレース情報と馬情報を書き込みます。
途中、変数table_dataがありますが、中身はパースされたテーブルのhtmlです。
スクリーンショット 2021-03-03 1.45.06.png
これをeachで回して各行の情報を取得しています。

実行

ターミナルにて

$ ruby main.rb

ターンッ

結果

2020~2021年現在までの重賞レースのデータを抽出することに成功しました?
keibascrape.gif

あとがき

人生初スクレイピングでしたがとても楽しかったです。
思い通りに出力できた時はおーすげーとなりました。
あとはこのデータを駆使するのみ。
勝ちたい。以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flashの使い方

フラッシュ

画面に一度だけ表示されるメッセージで別のページに遷移や、時間がたつと消えるメッセージ

フラッシュを利用するには特殊な変数 flash を利用する
flash[:notice]に文字を設定すると、<%= flash[:notice] %> で表示できる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

renderメソッドとredirect_toの使い方

renderメソッド

別のアクションを経由せずに直接ビューを表示できる
render("フォルダ名/ファイル名")
のように表示したいビューを指定する

コントローラー
render("ccc/ddd") ビューファイルを指定

ccc/ddd.html.erb

renderメソッドを使った場合は、そのアクション内で定義した@変数をビューでそのまま使うことができる

コントローラー
@fefefe = test
render("ccc/ddd")
-+-+-+-+-+-+-+
ccc/ddd.html.erb
<%= @fefefe =>

redirect_toメソッド

redirect_toはURLを指定して
redirect_to(URL)
のように書く

コントローラー
redirect_to("/aaa/bbb")

ルーター
get "aaa/bbb" => "aaa#bbb"

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【リーダブルコーディング】読みやすいコードを書くためのチェックリスト☑️

0.はじめに

プログラミング初心者は必ず読むべきとも、言われている良書「リーダブルコーディング」を読み、要約をチェックリストとして残していく。
 この記事を見直せば、誰でも読みやすく現場でも評価されるようなコードを書けるようにしたい。

読み進める度、更新していきます。

第1部|表面上の改善

変数やクラス名などの付け方などについて

チェックリスト

  1. 情報を含まない名前を使用していない(スコープが短い(使用される行数が短い)一部の名前を除く)

  2.  ループイテレーター(ブロックメゾットなどで使われるi,j,kなど)は、意味のある名前を使用している

  3.  定数やグローバル変数は長くても分かりやすい名前をつけているか

  4.  大文字やアンダースコア、キャメルスタイルなどはフォーマット規約に乗っ取っているか

  5. 名前が他の意味に捉えられることはないだろうか(何度も自問自答したか?)

  6. 限界値を明確にするためにMAXやMINを使う

  7. 範囲を指定するときはfirstとlastを使う

  8. 含包・排他的範囲(どこからどこまで的な表現)にはbeginとendを使う

  9. ブール値(true or falseを返す値)は文字の最初に動詞を持ってくると良い、
     否定系はわかりづらいので避ける

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションの使い方

バリデーションはモデルで設定する
validates :content, {presence: true, length:{maximum: 10}}
content : バリデーションをかけるカラム

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションやエラーメッセージ

バリデーションはモデルで設定する
validates :content, {presence: true, length:{maximum: 10}}
content : バリデーションをかけるカラム

バリデーションでfefefe.saveなどが失敗した場合は
fefefe.errors.full_messages
の中にバリデーションで引っかかったエラーの内容が自動的にはいる

<% @post.errors.full_messages.each do |message| %>
     <%= message %>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ARGVとgetsの併用

Rubyで暗号化プログラムを書いてたとき、
ARGVと標準入力getsを一緒に使うとエラーが起きたので解決策をポストしときます。

encrypt.rb
file = open(ARGV[0])
puts "暗号キーを入力してください"
num = gets.to_i

# 以下省略

プログラムを実行するとgetsでの標準入力を受け付けない。

解決策

STDIN.getsにする。

encrypy.rb
file = open(ARGV[0])
puts "暗号キーを入力してください"
num = STDIN.gets.to_i

# 以下省略

別解

encrypy.rb
file = open(ARGV[0])
ARGV.clear
puts "暗号キーを入力してください"
num = gets.to_i

# 以下省略

理由

getsとARGFについて

  • Kernel#gets、Kernel#readline、Kernel#readlines

    • それぞれARGF.gets、ARGF.readline、ARGF.readlinesと同じ。モジュール関数
  • ARGF

    • ARGVの各引数をファイルパスとみなし、それらのファイルを連結した1つの仮想ファイルを表すオブジェクト。 ARGVが空なら標準入力する

ARGVが空じゃなかったのでgetsできなかったんですね。
ARGV.clearで配列をきれいにしてあげるか、STDIN.getsのように別オブジェクトで標準入力を受け付けましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む