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を使うことで、ユーザーはそのフォームにどんな内容を記入すれば良いのかがわかりやすくなります。 名前入力項目などによくある「(例)山田太郎」などの灰色の文字のこと。
foreachメソッド/attributesメソッド/to_hashメソッド, sliceメソッド/CSVデータのインポート機能
foreachメソッド
foreachメソッド
を使ってCSVファイルを1行ずつ読み込ませます。
CSVライブラリには、ファイル全体を一度に読み込むreadメソッド、一行ずつ読み込むforeachメソッド、CSV形式の文字列から読み込むparseメソッドがあります。
大量のデータをCSVから一度に取得してしまうと、メモリに大量のデータが展開されてしまい、パフォーマンスが低下してしまいます。 この場合 foreach メソッドを使って分割して取得することでパフォーマンスの低下を防ぐことができます。
【参照ページ】
RubyでCSVファイルを読み込む方法を現役エンジニアが解説【初心者向け】
Railsでパフォーマンスを低下させないために気をつけること
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}
【参照ページ】
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を扱う
Rubyのcsvライブラリを利用する。(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
ポリモーフィック関連/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なモデルを追加する時にも上記のように追加モデルを書くだけでよく、既存コードをいじる必要はありません。
ダックタイピングとかポリモーフィック関連ってなんのこっちゃわからなかったけど久しぶり読んでみたら、すごくコードがすっきりできた。
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]を指定して、クリックしたときに降順から始まるようにしています。
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
フラッシュメッセージをRspecで検証する
(例) <h1 class="information" id="information">大事なお知らせ</h1> expect(page).to have_selector 'h1', text: '大事なお知らせ'
特定のタグやCSS要素に特定の文字列が表示されていることを検証する
「@(半角)」を「@(全角)」で書いていて、エラーになっていた。
全角の「@」を半角に変えたら直った。