dev/mom0tomo

技術メモ

つくりたいものメモ

jojobot

  • 今日の日付が
    • mm-dd
  • JOJOの奇妙なカレンダーに登録されている日付と合致すると
  • Slackのチャンネルで
  • その日あったできごとを
  • 教えてくれる
    • 朝10時に自動で投稿する
  • bot
    • Incoming webhook

つくった

gist.github.com

Railsのrespond_toの働き

読んだもの

http://railsdoc.com/references/respond_to

学んだこと

respond_toは、クライアントからの要求に応じて、返却するレスポンスのフォーマットを切り替えるメソッド。

respond_to do |format| 

  format.json

end

上のように記述し、items.json.slimを用意することで、 /items.jsonというURLでJSONでのレスポンスを返すことができる。

Rspecで実行順序に依存して落ちるテストの再現にはseed値を使う

読んだもの

ruby - Rspecのフィーチャーテストが失敗したりしなかったり - スタック・オーバーフロー

学んだこと

テストが落ちたり落ちなかったりする場合、まず問題が出るシード値を特定する。

Randomized with seed 12130

Rspecを実行すると、上のようにSpecの実行順を決める乱数のシード値が最後に表示される。
実行順に依存して失敗するのかどうかはっきりさせるために、同じシード値を使って落ちるテストを再現できるか試してみる。

もし再現したりしなかったりするのであれば、実行順以外の要因があるかもしれない。

FactoryBotとfixturesの比較

Rspecを使ってテストを書いているが、どちらを使うべきかいつもパッと浮かばないのがイヤなので、まとめた。

読んだもの

woshidan.hatenablog.com blog.jnito.com

学んだこと

fixtures

固定のデータ(都道府県など)がほしいときに使う。
コントローラのテストなどで使う。

良い点

  • IDが固定化できる
  • 誤って同じレコードを複数作成する恐れがない
  • 多対多のアソシエーションを簡単に書ける

悪い点

  • モデルとは連携しない。バリデーションも通らない
  • テーブルのカラムが増えたりすると書き直す必要がある
  • テストデータは毎回自動的に全件投入される
  • 「このテストケースのときだけここの値を変えたい」ができない

FactoryBot

モデルのメソッドの境界値条件などのテストを書きたいときに使う。

良い点

  • 複数のデータを簡単に生成できる
  • テストコード側で動的にテストデータを変更できる
  • モデルのvalidationを通る

悪い点

  • テストデータを投入するコードを書く必要がある
  • 親子孫や多対多のアソシエーションを書くときに面倒

インスタンスメソッドとクラスメソッドの違い

読んだもの

qiita.com

学んだこと

あるclass(仮に C とする)があるとして、

  • class C のインスタンスメソッドは,C のインスタンスをレシーバーとするメソッド
  • class C のクラスメソッドは C 自身をレシーバーとするメソッド

である。

違いはこんな感じ。

インスタンスメソッド

class Add
    def addition(a, b, c, d, e)
        puts a + b + c + d + e
    end
end

add = Add.new() # インスタンスを呼び出している
add.addition(1,2,3,4,5)

クラスメソッド

class Add
    def self.addition(a, b, c, d, e)
        puts a + b + c + d + e
    end
end

Add.addition(1,2,3,4,5)

(コードは上記参照先から抜粋)

【メモ】ストロングパラメータを使うのはどういうときか

読んだもの

railsguides.jp

学んだこと

結論

基本的にPOSTやPUTでリソースに変更を加えるときに使う。


ストロングパラメータの目的は、Active Modelに不適切な値が入れられ、それを公開してしまうのを防ぐこと。
そのため、POSTやPUT以外はまず使わない。

ストロングパラメータを使うことで、多くの属性を一度に更新したいときに、許可する属性を開発者が明示的に指定できる(ホワイトリスト)。

tryとtry!とぼっち演算子(&.)でレシーバがnilのときのエラーに対処する

読んだもの

sakurawi.hateblo.jp

qiita.com

学んだこと

tryとtry!とぼっち演算子(&.)は、レシーバがnilのときのエラーに対処する為に使う。

try

user.try(:name)

レシーバであるuserがnilでないなら、メソッドを実行する。

レシーバとは、hoge.methodと書かれている場合のhoge部分のこと。

tryメソッドを使用することで、下記のようにメソッドの記述を簡略化できる。

if @user.nil?
  @user = @user.find_by(:id)
end

 
# tryを使って簡略化する
@user.try(:name)

try!

レシーバがnilの場合、 trytry!nilを返す。

違いはメソッドが存在しない場合にある。 try!はメソッド自体が定義されていない場合にはエラーを返す。(NoMethodErorr)
tryはnilを返す。

NoMethodErorrを捕捉したい場合は try!を使う。

ぼっち演算子(&.)

try!と同様の処理をする。

# try!を使う場合
user.try!(:name)

# ぼっち演算子を使う場合
user&.(:name)

.try(:[], :hoge)という書き方について

user = {name: 'taro'}

user.try(:[], :name)

こういう書き方ができる。

これは、[]がメソッドであるということを利用している。

下記のコードは上下で同じ挙動になる。

user[:name]
# => "taro"

user.[] :name
# => "taro"

ビジネスロジックはモデルに書く、とはどういうことか

コードレビューで指摘を受けたので改めて調べてまとめた。

読んだもの

https://wa3.i-3-i.info/word13666.html ビジネスロジックとは - IT用語辞典

学んだこと

ビジネスロジックとは

アプリケーション固有の処理やルールを記述したもの。

ビジネスロジックを分ける」という場合、その本質は

  • 使い回しできるところとできないところを分離する
  • システムに変更が必要なときに見る必要があるところとないところを分離する

ことにある。

アプリケーション固有の処理と、データベースを操作する処理などの使い回しできる処理を分けておくことで、メンテナンス性・拡張性を高く保てる。

3階層システムという概念においては、プレゼンテーション層(ユーザインターフェース層)とデータアクセス層(データベース層)の中間に位置づけられる。 アプリケーション層と呼ばれることもある。

Railsの様なMVCモデルを利用する場合、モデル(M)クラスにメソッドとして記述する。

form_withを使って検索フォームをつくる

form_withを使ってこんな検索フォームを作った。

user/index.html.slim

  = form_with model: User.new do |form|
    = form.label :name, '名前'
    = form.text_field :name, value: (params[:user][:name] if params[:user])
    = form.submit '検索する'

users_controller.rb

class UserController < ApplicationController
  def index
    @users = query.order(:id).page(params[:page])
  end

private

      def query
        if params[:user].present? && params[:user][:name]
          User.where('LOWER(name) LIKE ?', "%#{params[:user][:name].downcase}%")
        else
          User.all
        end
      end
end

検索フォームによくある下記の様な要件を実装しているのでちょっと複雑に見える。

  • 検索した文字列を検索した後もフォームに残す (value: (params[:user][:name] if params[:user]))
  • 大文字小文字を区別せずに検索する (LOWER() 〜 .downcase)
  • 検索結果をID順に並べる ( .order(:id))
  • kaminariを使ってページネーションする (.page(params[:page]))

この条件を外してシンプルにしてみる。

user/index.html.slim

  = form_with model: User.new do |form|
    = form.label :name, '名前'
    = form.text_field :name
    = form.submit '検索する'

users_controller.rb

class UserController < ApplicationController
  def index
    @users = query
  end

private

      def query
        if params[:user].present? && params[:user][:name]
          User.where('name LIKE ?', "%#{params[:user][:name]}%")
        else
          User.all
        end
      end
end

こうしてシンプルにしてみるとわかりやすい。


form_withのポイントは、

user/index.html.slim

  = form_with model: User.new, method: :get do |form|
    = form.text_field :name

このようなフォームのパラメータは params[:user][:name]という形で取得できること(controller参照)。

また、検索フォームとしてのポイントはコントローラの中での検索処理をqueryメソッドに切り出していること。
こうしておくとUserのIDやemailなど他の条件で絞り込みを行いたくなったときに拡張しやすい。

form_forとform_tagはform_withへ統合された(Rails 5.1)

読んだもの

Ruby on Rails 5.1リリースノート | Rails ガイド

学んだこと

なんとRails5.1から統合されていた。マジカヨ...

  • form_for : Modelに基づいたformを作るときに使う
  • form_tag : Modelに基づかないformを作るときに使う(検索フォームなど)

と覚えていたが...

現在は下記のように書く。

form_forのように使う場合

<%= form_with model: Post.new do |form| %>
  <%= form.text_field :title %>
<% end %>
 


# ↓生成されるタグ
 
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

既存のモデルに対してupdate的に使う場合はこちら。

<%= form_with model: Post.first do |form| %>
  <%= form.text_field :title %>
<% end %>
 


# ↓生成されるタグ
 
<form action="/posts/1" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch">
  <input type="text" name="post[title]" value="<postのtitle>">
</form>

form_tagのように使う場合

<%= form_with url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>
 


# ↓生成されるタグ
 
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="title">
</form>

inputフィールド名にスコープを追加する場合はこちら。

<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>
 


# ↓生成されるタグ 
 
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

結構がんばってform_for / form_tag を覚えていたのでショックだった。便利。

Rubyのattr_accessorとは

読んだもの

Rubyのattr_accessor, attr_reader, attr_writerとは何か -- ぺけみさお

学んだこと

attr_accessor とは、インスタンス変数にアクセスするためのメソッドを定義するメソッドのこと。
プライベート変数に対するセッタやゲッタを自動的にセットしてくれる。

以下の3種類が使える。

  • attr_accessor: セッタとゲッタを共に定義する
  • attr_reader : ゲッタのみを定義する
  • attr_writer : セッタのみを定義する

attr_readerは下のメソッドと同じ働きをする。

class User
  attr_reader :person
end
class User
  def person
    @person
  end
end

attr_writerは以下のメソッドと同じ働きをする。

class User
  attr_writer :person
end
class User
  def person=(val)
    @person = val
  end
end

Railsのバリデーションエラーを日本語化する

読んだもの

Railsのバリデーションエラーのメッセージの日本語化

学んだこと

下記の手順で設定する。

1.以下のgemをインストールする

gem rails-i18n

2.config/application.rbに設定を追加する

config/application.rb

config.i18n.default_locale = :ja

3.ja.ymlをつくる

ディレクトリは作っても作らなくてもいいが、私は下記のように細かく作る派。

config/locales/models/user/ja.yml

ja:
  activerecord:
    models:
      user:
    attributes:
      user:
        name: 名前

4.config/application.rbにディレクトリを読み込むよう正規表現で設定を追記する

config/application.rb

config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]

挙動を確認する前にサーバーを再起動するのを忘れない。

Railsのモデルと一対多のリレーションのまとめ

読んだもの

Rails4で1対多のリレーションをモデルに実装する - Rails Webook Active Record の関連付け (アソシエーション) | Rails ガイド

学んだこと

下記のようなUser has many Itemsという関係のモデルを考える。

user.rb

class User < ApplicationRecord
  has_many :items
end

item.rb

class Item < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
end

指定できるオプション

has_many / belongs_toメソッドには下記のオプションが指定できる。

  • class_name
    • 関連するモデルのクラス名を指定する。
    • リレーションの名前(has_manyの直後に指定する。上記の例ではitems)https://railsguides.jp/association_basics.htmlと参照先のクラス名を異なるものにできる。
  • foreign_key
    • 参照する外部キーの名前を指定する。
    • 指定しない場合、参照先のモデル名_idになる(上記の例ではuser_id)。

使えるようになるメソッド

has_many と belongs_to で一対多の関係を指定することで、下記のようなメソッドが使えるようになる。

userとitemの作成と保存に関するメソッド

# userを作成し、DBに保存する
user = User.create(name: "momo")

# item1を作成する
item1 = user.items.build(content: 'アイテム1') 

# item1をDBに保存する
item1.save

# item2をuserとの関係を示して作成し、保存する
item2 = user.items.create(content: 'アイテム2はuserのつくるitem')

userとitemのリレーションに関するメソッド

# itemオブジェクトの配列
user.items

# true # itemオブジェクトが存在するか判定する
user.items.present?

# userオブジェクト(itemを持っているuser)
item1.user

userの持つitemから検索するメソッド

# userの持つitemから条件を指定して検索する
user.items.find(...)

user.items.find_by(...)

user.items.where(...)

インスタンスメソッドの理解

ずっともやっとしてた部分を整理したらアハ体験があったのでまとめる。

こんなモデルがある場合。

models/user.rb

class User < ApplicationRecord
  has_many :items
end

models/item.rb

class Item < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
end

中間テーブルはLikeとする。

models/like.rb

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :item

  validates :user_id, presence: true
  validates :item_id, presence: true
end

上の関係は英文にするとこんな風に書ける。

User like item.

冠詞がないのと複数形じゃないのはご愛嬌。

モデル、メソッドのレシーバ・引数の関係を上記の英文を例に考えてみる。

  • モデルは主語を担う。(User)
  • レシーバは動詞を担う。(like)
  • 引数は目的語を担う。(item)

上の関係をメソッドで表す。

models/user.rb

class User < ApplicationRecord
  has_many :items

  has_many :likes
  has_many :like_items, through: :likes, source: :item

  def like(item)
    self.likes.find_or_create_by(item_id: item.id)
  end
end

主語はUserなので、Userモデルにメソッドを書く。
メソッドのレシーバが動詞なので、likeというインスタンスメソッドをつくる。
メソッドの引数は目的語なので、itemを引数にする。

Rails 5.1以降 でjqueryを使う

Rails5.1から、デフォルトではjqueryに依存しない仕様になった。

Ruby on Rails 5.1リリースノート | Rails ガイド

jqueryを使うためには下記の手順を踏むこと。

Gemの追加とbundle update

Gemfileに gem "jquery-rails"を追加し、bundle updateを実行する。

Gemfile.lockはこんな感じに変更される。

+    jquery-rails (4.3.3)
+      rails-dom-testing (>= 1, < 3)
+      railties (>= 4.2.0)
+      thor (>= 0.14, < 2.0)

-    sass (3.5.7)

+    sass (3.6.0)
       sass-listen (~> 4.0.0)
     sass-listen (4.0.0)
       rb-fsevent (~> 0.9, >= 0.9.4)

+  jquery-rails

application.jsに追加

application.jsに下記2行を追記する。

application.js

//= require activestorage
//= require turbolinks
//= require_tree .
# 以降追記
//= require jquery
//= require jquery_ujs