resourcesの:member, :collection, :newオプション/form_tagの:multitypeオプション/HTML placeholder

resourcesの:member, :collection, :newオプションについて

各状況のリソースをプリフィックスとして階層的なURLを生成することができるオプションのこと。 具体例を用いていうと、Taskモデルのリソースを扱う場合。

  • :memberが/tasks/3などの登録済みの1つのリソース。

  • :collection/tasksという集合のリソース。

  • :new/tasks/newという新たに生成する1つのリソース。

上記のようなプリフィックスとして階層的なURLで生成します。

下記の様に記述した場合のルーティングはこの様になる。

  resources :tasks do
    post :confirm, action: :confirm_new, on: :new
    post :import, on: :collection
  end
$ bundle exec rails routes
            root GET    /                                        tasks#index
confirm_new_task POST   /tasks/new/confirm(.:format)             tasks#confirm_new
    import_tasks POST   /tasks/import(.:format)                  tasks#import
           tasks GET    /tasks(.:format)                         tasks#index
(省略)              

参照:ルーティングにアクションを追加

form_tagの:multitypeオプション

画像投稿用のオプションです。

https://doruby.jp/users/katsuo_on_rails/entries/rails_

HTML placeholder

placeholderを使うことで、ユーザーはそのフォームにどんな内容を記入すれば良いのかがわかりやすくなります。 名前入力項目などによくある「(例)山田太郎」などの灰色の文字のこと。

HTML placeholderとは?初心者向けに解説&使い方もわかる!

foreachメソッド/attributesメソッド/to_hashメソッド, sliceメソッド/CSVデータのインポート機能

foreachメソッド

foreachメソッドを使ってCSVファイルを1行ずつ読み込ませます。

CSVライブラリには、ファイル全体を一度に読み込むreadメソッド、一行ずつ読み込むforeachメソッド、CSV形式の文字列から読み込むparseメソッドがあります。

大量のデータをCSVから一度に取得してしまうと、メモリに大量のデータが展開されてしまい、パフォーマンスが低下してしまいます。 この場合 foreach メソッドを使って分割して取得することでパフォーマンスの低下を防ぐことができます。

【参照ページ】

RubyでCSVファイルを読み込む方法を現役エンジニアが解説【初心者向け】

Railsでパフォーマンスを低下させないために気をつけること

foreachメソッド

Rubyリファレンスマニュアル IO.foreach

attributesメソッド

attributesメソッドは、特定のattributeを変更(更新)するメソッドです。ちなみに、このattributesメソッドはオブジェクトの変更をしただけで、DBには保存されていません。

> @task
=> #<Task:0x00007fd2a6fb60c8
 id: 1,
 name: "ラグビー観戦",
 description: "日本 vs 南アフリカ">

> @task.attributes = { name: "Hoge", description: "Fuga vs Fuga"}
=> {:name=>"Hoge", :description=>"Fuga vs Fuga"}

> @task
=> #<Task:0x00007fd2a6fb60c8
 id: 1,
 name: "Hoge",
 description: "Fuga vs Fuga">

参照:Active Recordのattributesの更新メソッド

to_hashメソッド, sliceメソッド

> hash = { a: 1, b: 2, c: 3, d: 4 }
=> {:a=>1, :b=>2, :c=>3, :d=>4}
[25] pry(main)> hash.to_hash
=> {:a=>1, :b=>2, :c=>3, :d=>4}
[26] pry(main)> hash.slice(:a, :d)
=> {:a=>1, :d=>4}

【参照ページ】

to_hash (Hash)

ハッシュスライス(Hash#slice)

CSVデータのインポート機能

  def self.csv_attributes
    %w(name description created_at updated_at)
  end

  def self.import(file)
    CSV.foreach(fale.path, headers: true) do |row|
      task = new
      task.attributes = row.to_hash.slice(*csv_attributes)
      task.save!
    end
  end

csv_attributesメソッドの戻り値の配列ないの要素をそれぞれ引数に指定する書き方で、下記の書き方で記述しているのと同じ意味になる。

slicd("name", "description", "created_at", "update_at")

ブロック「do...end」と「{}」/CSVを扱う/sendメソッド/respond_to/strftimeメソッド/send_dataメソッド

ブロックdo...end{}

ブロックの文法の種類を忘れていたので復習しました。

numbers = [1, 2, 3, 4]
sum = 0

(i)標準的なブロック文
numbers.each do |n|
  sum += n
end

(ii)改行しないブロック文
numbers.each do |n| sum += n end

(iii)「do...end」を使う代わりに「{}」を使ったブロック文
numbers.each { |n| sum += n }

(i), (ii), (iii)のどれも出力は同じ。
sum #=> 10

この使い分けとしては、

  • 改行を含む長いブロックを書く場合は → do...end

  • 1行でコンパクトに書きたい場合は → {}

(iii)の「{}」でブロックを実装する方法が多いのですが、この書き方を忘れていてつまづいた。

CSVを扱う

Rubycsvライブラリを利用する。(config/application.rb)に記述します。

require 'csv'
csv_data = CSV.read('member.csv', headers: true)

headers: trueオプションを使うと、CSVの初めの1行はheaderとして出力の際に無視されます。今回はこのオプションを使っていますが、headerがないときはこのオプションはいりません。

sendメソッド

sendとは、レシーバの持っているメソッドを呼び出してくれるメソッドのこと。 レシーバの持っているメソッドを呼び出し、そのメソッドの戻り値を返します。 以下はメソッドを定義したときの例です。

class User
  def name
    puts "taro"
  end
end

user = User.new
#定義したメソッドを呼び出す
user.name  # => taro

# sendを使った書き方
user.send(:name) # => taro
user.send("name")  # => taro

CSVをエクスポートするときに使った。

CSV.generate(headers: true) do |csv|
  csv << csv_attributes
  all.each do |task|
    csv << csv_attributes.map{ |attr| task.send(attr) }
end

respond_toにできること

def destroy
    @tweet = Tweet.find(params[:id])
    respond_to do |format|
      if @tweet.destroy
         format.html { redirect_to root_path }
         #root_pathへリダイレクトする
         format.js { render :action => "index" }
         #.index.html.erbで作成されたものをjson形式で表示する。
      end
    end  
end

destroy action が呼び出された時、formatがhtmlで指定されていたらroot_pathにリダイレクト、jsonで指定されていたらindex.html.erbをindex.html.jsで返す

strftimeメソッド

Rubyのstrftimeメソッドは、日付や日時のデータを編集して文字列に変換するためのメソッドです。

使い方はstrftimeメソッドの第1引数に書式を指定して次のように記述します。

> Time.zone.now
=> Tue, 12 Nov 2019 13:57:27 UTC +00:00
> Time.zone.now.strftime('%Y%m%d%S')
=> "2019111242"
> Time.zone.now.strftime("%Y年%-m月%-d日 %-H時%-M分%-S秒")
=> "2019年11月12日 14時11分17秒"

send_dataメソッド

CSV出力機能を実装したかったので、このsend_dataメソッドを使ってレスポンスを送り出し、送り出したデータをブラウザからファイルとしてダウンロードできるようにします。

send_data(data、options = {})

第一引数:生成するCSVデータ

オプション:filename -使用するブラウザのファイル名を提案します。

今回は下記の様に実装した。

def index
  @q = current_user.tasks.ransack(params[:q])
  @tasks = @q.result(distinct: true)
  
  respond_to do |format|
    format.html
    format.csv { send_data @task.generate_csv,
filename: "tasks-#{Time.zone.now.strftime('%Y%m%d%S')}.csv"}
  end
end

参照:send_data - Rails API

ポリモーフィック関連/Active Storage/ActiveStorage::Blob/ActiveStorage::Attachment/has_one_attached

ポリモーフィック関連

ポリモーフィック関連とは、ある1つのカラムが複数のテーブルを参照しているようなパターンの関連を表したもので、

例)Q&Aアプリなどのcommentsテーブルをイメージするとよい

同じようなカラムを持っているテーブルが複数ある場合、

例)teacher_id, student_id 等をイメージ

それらを1つのテーブルで管理してしまうことができる。複数のクラスのインターフェースを統一し、同じように扱えるようにする。 一つのモデルを同じインターフェースを持ったものが扱う関連の仕方。 ダックタイピングというコード概念からの考えで、インタフェースを統一することで冗長なコードがなくなり、スケーラビリティを担保できる。

(例)ポリモーフィック関連を使わなかった場合

class Commment < ApplicationRecord
  belongs_to :teacher
  belongs_to :student
  # メッセージを送るモデルが増えたら、それに従いbelongs_toの数も増えていく
end

class Teacher < ApplicationRecord
  has_many :comments
end

class Student < ApplicationRecord
  has_many :comments
end
(実行例)
teacher = Teacher.find(params[:teacher_id])
teacher.comments 
=> #select * from comments where teacher_id = ?

comment = Comment.find(params[:comment_id])
comment.teacher
=> #select * from teachers where teacher_id = ?

(例)ポリモーフィック関連を使用した場合

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
  # メッセージを送るモデルが増えてもbelongs_toの数は増えない
end

class Teacher < ApplicationRecord
  has_many :comments, as: :commentable
end

class Student < ApplicationRecord
  has_many :comments, as: :commentable
end
(実行例)
# commentableを通すことで、TeacherとStudentのどちらであるかを意識する必要がない。
Comment.find(params[:id]).commentable

# コメント送信者モデル側からは通常のhas_many関連として扱える
Teacher.find(params[:id]).comments
Student.find(params[:id]).comments

commentableなモデルを追加する時にも上記のように追加モデルを書くだけでよく、既存コードをいじる必要はありません。

ダックタイピングとかポリモーフィック関連ってなんのこっちゃわからなかったけど久しぶり読んでみたら、すごくコードがすっきりできた。

参照:Railsのポリモーフィック関連とはなんなのか

Active Storageで画像を添付する

Taskモデルに画像を添付したかったので、Active Storageを使用することにした。

$ bundle exec rails active_storage:install
$ bundle exec rails db:migrate

コマンドを実行しインストールすると、

Active Storageで使用する

  • active_storage_blobs テーブル
  • active_storage_attachments テーブル

二つのテーブルを生成するマイグレーションファイルができます。

# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs do |t|
      t.string   :key,        null: false
      t.string   :filename,   null: false
      t.string   :content_type
      t.text     :metadata
      t.bigint   :byte_size,  null: false
      t.string   :checksum,   null: false
      t.datetime :created_at, null: false

      t.index [ :key ], unique: true
    end

    create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end
  end
end

ActiveStorage::Blob

ActiveStorage::Blobモデルは添付されたファイルに対応していて、active_storage_blobsテーブルに添付画像のデータが管理されていると考えてください。 またファイルの実体をデータベース外で管理することを前提にしていて、識別keyファイル名コンテンツタイプメタデータサイズなどを管理している。

ActiveStorage::Attachment

ActiveStorage::Attachmentモデルは 「①アプリ内の画像添付するモデル(ここでいうと、Taskモデル)」と「②ActiveStorage::Blobモデル」との中間テーブルの役割を果たしているテーブルのモデルです。

①とはFKカラム名をFK値として保持しており、ポリモーフィック関連となっていて、一方②は直接的なidのみで紐付けされている。

勝手なイメージ

それぞれのモデルとActiveStorage::Attachmentモデルで外部キーの関連で画像の関連付けをして、画像のデータなどの細かい情報に関してはActiveStorage::Blobモデルを別に作成して、そっちで管理するようにしているんだろうなぁ。と思っています。

本来、ActiveStorage::AttachmentモデルActiveStorage::Blobモデルを別々にしなくてもいいけど、一緒にするとテーブル構造がカラム が多くなって見づらいから、別々にしたよ。みたいな感じで考えて落ち着きました。

Taskモデルに画像添付できるようにする

# Taskモデルにファイルを添付するためにimageを関連付けする
class Task < ApplicationRecord
  has_one_attached :image
end

# 添付の有無を確認する
task.image.attached? # => true

returnメソッド/[%w]で文字列の配列を作る/[%i]シンボルの配列を作る/ransackが提供するsort_linkヘルパー

returnメソッド

returnはメソッドの途中で脱出する場合に使われることが多い。

(例)
def greeting(country)
  # countryがnilならメッセージを返してメソッドを抜ける
  return 'contryを入力してください' if country.nil?

  if country == 'japan'
    'こんにちは'
  else
    'hello'
  end

[%w]で文字列の配列を作る

[%w]を使用することで、「,」,「""」を使用しなくてよくなり、コードが短くなる。

array = %w(a b c)
=> ["a", "b", "c"]

ary = %w[a b c]
=> ["a", "b", "c"]

[%i]でシンボルの配列を作る

 array = %i(name age)
=> [:name, :age]
 array.class
=> Array

ransackが提供するsort_linkヘルパー

sort_linkヘルパーは見出し部分のソート機能を実装することができるヘルパーです。 第1引数にコントローラーから呼び出されたRansack::Searchオブジェクト。 第2引数にソートを行う対象のカラム してします。

<%= sort_link(@q, :name, default_order: :desc) %>

※デフォルトで昇順ソートができますが、まず降順ソートしたかったので、第3引数に[default_order: :desc]を指定して、クリックしたときに降順から始まるようにしています。

ransackが提供するsort_linkヘルパー

sort_linkでは、複合的な条件のソートにも対応ができます。 「まずは名称だけをソートして、同じ名称に関しては登録日時順にソートしたい」という場合。

<%= sort_link(@q, :name, [:name, "created_at desc"]) %>

複数の[default_order]もハッシュでしてできる。

この例では、両方のフィールドのソート方向を切り替えます。デフォルトでは、最初はlast_nameフィールドを昇順でソートし、first_nameフィールドを降順でソートします。

<%= sort_link(@q, :last_name, %i(last_name first_name),
  default_order: { last_name: 'asc', first_name: 'desc' }) %>

RSpec/フラッシュメッセージをRspecで検証する/「@」の全角と半角表記

RSpec

FactoryBotのcreateとbuildの違い

フラッシュメッセージをRspecで検証する

(例)
<h1 class="information" id="information">大事なお知らせ</h1>

expect(page).to have_selector 'h1', text: '大事なお知らせ'

特定のタグやCSS要素に特定の文字列が表示されていることを検証する

「@(半角)」を「@(全角)」で書いていて、エラーになっていた。

f:id:ginokin:20191107223822p:plain

全角の「@」を半角に変えたら直った。

ぼっち演算子「&.」/scopeの活用

ぼっち演算子「&.」

&.(ぼっち演算子)はレシーバーであるオブジェクトに対してあるメソッドを実行した時、そのオブジェクトがnilの場合、nilを返すことでエラーを出さなくしています。&.(ぼっち演算子)とはレシーバーであるオブジェクトがnilでなければそのまま結果を返し、nilの場合はnilを返すメソッドなのです。

scopeの活用

railsのモデルに追加。コントローラでのクエリ検索のメソッドをきれいに実装すすときに使った。 参照: Railsのモデルのscopeを理解しよう