ページネーションの実装(kaminari)
kaminari を使ってみたのでメモ。
will_paginate よりもこっちのほうがとっつきやすい?ような気がした。
gem をインストール(bundle install)
gem はこちら
https://github.com/kaminari/kaminari
controller でページ番号に対応する範囲のデータを取得
# app/controller/tasks_controller.rb def index @tasks = current_user.tasks.page(params[:page]) ... end
gemをインストールすると、表示するページ番号がparams[:page]でアクションに渡される。
page というスコープを使うだけで params[:page] に表示されるべきデータ範囲を検索できる。
view にページネーションのための情報を表示
# app/views/tasks/index.html.erb <div class="mb-3"> <%= paginate @task %> <%= page_entries_info @task %> </div>
以下の2つのヘルパーメソッドを使用する。
ヘルパーメソッドの定義はここ
kaminari/helper_methods.rb at master · kaminari/kaminari · GitHub
paginate
現在、どのどのページを表示しているかを取得・表示する。
また、他のページに移動するためのリンクを生成し、表示する。
page_entries_info
全データが何件あり、現在どのデータが表示されているかの情報を表示
(必要あれば)locales の設定
kaminari は内部に英語の翻訳しか持っていないので、ja の翻訳ファイルを追加する。
ja: views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "..." helpers: page_entries_info: one_page: display_entries: zero: "" one: "<strong>1-1</strong>/1件中" other: "<strong>1-%{count}</strong>/%{count}件中" more_pages: display_entries: "<strong>%{first}-%{last}</strong>/%{total}件中"
デザインの適用
$ bin/rails g kaminari:views bootstrap4
上記は bootstrap4 のデザインテンプレートを使用する例。
このコマンドで、app/view/kaminari 配下にビューテンプレートファイルが追加される。
他のテンプレートを使用するときはこちらを参照
GitHub - amatsuda/kaminari_themes
(必要あれば)表示件数の変更
3通りある。
1. perスコープで指定する。
# app/views/tasks/index.html.erb def index @tasks = current_user.tasks.page(params[:page]).per(50) ... end
2. modelに設定する
# app/models/task.rb class Task < ApplicationRecord paginates_per 50 ...
3. kaminari 用の config ファイルを作って設定する
$ bin/rails g kaminari:config
# config/initializers/kaminari_config.rb # frozen_string_literal: true Kaminari.configure do |config| config.default_per_page = 50 # config.max_per_page = nil # config.window = 4 # config.outer_window = 0 # config.left = 0 # config.right = 0 # config.page_method_name = :page # config.param_name = :page # config.max_pages = nil # config.params_on_first_page = false end ...
参考記事
https://github.com/kaminari/kaminari
https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Ruby-Rails-5%E9%80%9F%E7%BF%92%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E5%A4%A7%E5%A0%B4%E5%AF%A7%E5%AD%90/dp/4839962227
ruby で配列の最大値・最小値を取得する
配列から最大値、最小値を取得する際は、max, min が使える。
array = [110,21,13,24,15] # 要素の最大値を取得 p array.max # => 110 p array.max(3) # => [110, 24, 21] # 要素の最小値を取得 p array.min # => 13 p array.min(3) # => [13, 15, 21] # 文字列の場合 array = ["110","21","13","24","15"] # 文字列はアルファベット順に評価されてしまう p array.max # => "24" p array.max(3) # => ["24", "21", "15"] p array.min # => "110" p array.min(3) # => ["110", "13", "15"] # 数値が文字列になっている場合は変換する p array.map(&:to_i).max p array.map(&:to_i).max(3) p array.map(&:to_i).min p array.map(&:to_i).min(3)
条件付きの最大値・最小値の取得は、max_by, min_by を使う。
array = ["banana", "orange", "melon", "strawberry", "grape"] p array.max_by { |x| x.length } # => "strawberry" p array.min_by { |x| x.length } # => "melon"
ruby の標準入力と出力
久々に paiza で ruby の問題を解いたのだが、結構忘れていたので振り返られるようにメモ。
ruby
1行に1要素だけ存在する場合
# 標準入力 Tokyo
line = gets p line
# 出力結果 "Tokyo"
1行に複数要素が存在する場合
# 標準入力 Tokyo Nagoya Osaka
line = gets.split(" ") p line
# 出力結果 ["Tokyo", "Nagoya", "Osaka"]
gets は一行読み込んで、読み込みに成功した時にはその文字列を返す。
split は指定されたセレクタで分割し、配列で返す。
複数行に一つずつ要素が存在する場合
# 標準入力 Tokyo Nagoya Osaka
line = readlines.map(&:chomp)
p line
# 出力結果 ["Tokyo", "Nagoya", "Osaka"]
readlines はすべての行を読み取り、1行ごと配列に格納する。
また、map を使って配列のすべての要素に chomp で改行なしにしている。
複数行に複数要素が存在する場合
# 標準入力 Tokyo Nagoya Osaka Japan Korea China
lines = [] while line = gets lines << line.chomp.split(' ') end p lines
# 出力結果 [["Tokyo", "Nagoya", "Osaka"], ["Japan", "Korea", "China"]]
while line = gets ですべての行を取得するまで繰り返し…している。
参考記事
Ruby 標準入力から値を受け取る方法 - Qiita
IO#gets (Ruby 2.7.0 リファレンスマニュアル)
IO.readlines (Ruby 2.7.0 リファレンスマニュアル)
haml記法まとめ
今週からお試しで働く会社が haml で書いているということで、erbファイルを haml に書き換えた。
Rails での使い方
基本的に、各タグは 「%」 が先頭
!!! %html %title これはテストです %meta %body %header = yield %footer
- 「!!!」で「docutype html」になる。
- 各タグの頭に「%」をつける。閉じタグいらない。
- インデントで書く。(スペースずれなど一個でもあると作動しない)
- 上のコードを html に直すとこうなる↓
<!DOCTYPE html> <html> <title>これはテストです</title> <meta></meta> <body> <header></header> <!----- ※ 各ページ内容が入る -----> <footer></footer> </body> </html>
divは省略が可能。 class と id の指定方法
/ コメント .collapse.navbar-collapse#head-menu %h1 メニュー %ul.language_list %li Ruby %li Rails %li HTML %p これは、わかりやすいサイトです。 %br これは、わかりやすいサイトです。
- コメントは「/」を先頭につける。
- divのみ「div」省略できる。
- 各タグにプラスして「.」で class 指定、「#」で id 指定できる。
- 複数 class がある場合は、「.」でつなげる。
ruby を使う場合は、「-」または「=」で
= render "ayouts/header", record: @record = link_to "コラム", articles_path, class: "nav-link" / if - if user_signed_in? = render "layouts/login_user_header", record: record - elsif admin_signed_in? = render "layouts/admin_user_header" - else = render "layouts/no_login_user_header" / form_for = form_for(@user, url: users_path) do |f| .field = f.label :name = f.text_field :name .field = f.label :email = f.email_field :email .field = f.submit "ログイン"
通常の文字列と変数を1行で表す場合
%p= "#{current_user.name}さん、こんにちは"
- タグ名の後にスペース空けず「=」を使用する。
provide / yield の使い方
- provide(:h1, "コラム") %h1.head_title = yield(:h1)
aria, data などの接頭辞がつく属性など
.modal#modal-login{ aria: { hidden: :true, labelledby: "exampleModalLabel" }, role: :dialog, tabindex: "-1" } .modal-dialog{ role: :dialog } .modal-content %table.table.table-hover.table-bordered %thead.thead-light %tr %th.text-center{ rowspan: 2 } 年月日 %th.text-center{ colspan: 3 } 朝<i class="fas fa-cloud-sun"></i>
- class や id 以外の属性は、{}で囲う
- aria, data など共通の接頭辞がつく属性が複数ある場合、その接頭辞同士でまとめることができる。
javascript の書き方(.js.haml)
$("#recordModal").html("<%= escape_javascript(render 'form') %>")
<%= ... %> の部分を #{} に変更する
$("#modal-record").html("#{escape_javascript(render 'form', record: @record)}")
普通に html ファイル内にスクリプトを記述するときは、%script 以下に書けば良いらしい。
haml のリンターについて
haml_lint という gem を使えば、記法のチェックができる。動作しなくてどこが間違っているかわからないというときに使えば便利そう。
使い方は github を参照。
参考記事
【Rails】hamlの書き方をマスターしよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト
RubyonRails:脱.erb、Hamlのはじめの一歩 - Madogiwa Blog
hamlの基本とよくあるエラー集 - Qiita
hamlで改行コードを入れたい - ハリーかつき・旧技術ブログ
ruby on rails - hamlでのprovide関数の書き方 - スタック・オーバーフロー
HAML にて attributes をスッキリ書く - Qiita
html - Merged Table Headers in Haml - Stack Overflow
Hamlを使っているならhaml-lintを使ってコードレビューを楽にしよう - Sider Blog
Ruby - ruby(rails)でhamlを使っている時に、コード中の改行をしたい|teratail
【超基本】railsで部分テンプレートを使ってみる(haml) - Qiita
[Rails] haml-railsがあればerb2hamlは不要です! - Qiita
Docker の image を軽くする
Docker に無事既存の web アプリを載せることはできたものの、めちゃ重い。web アプリのイメージだけで 5GB もあった。
イメージを軽くするには、ベースのイメージを alpine Linux なるものに変えると軽くなるということだったので、変えてみた。
# Node.js & Yarn FROM node:10.15.1-alpine as node RUN apk add --no-cache --virtual .ruby-builddeps bash curl \ && curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.4 # Ruby & Bundler & postgresql-client FROM ruby:2.6.5-alpine COPY --from=node /usr/local/bin/node /usr/local/bin/node COPY --from=node /opt/yarn-* /opt/yarn RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn ENV RUN_PACKAGES="nodejs postgresql postgresql-dev tzdata" \ DEV_PACKAGES="build-base curl-dev gcc libc-dev libxml2-dev linux-headers make" \ CHROME_PACKAGES="chromium chromium-chromedriver dbus mesa-dri-swrast ttf-freefont udev wait4ports xorg-server xvfb zlib-dev" RUN apk --update --no-cache add ${RUN_PACKAGES} \ && apk --update --no-cache add ${DEV_PACKAGES} \ && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ && apk del --purge tzdata ENV ENTRYKIT_VERSION 0.4.0 # Entrykit & ChromeDriver RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && mv entrykit /bin/entrykit \ && chmod +x /bin/entrykit \ && entrykit --symlink \ && apk --update --no-cache add ${CHROME_PACKAGES} # 作業ディレクトリの作成、設定 RUN mkdir /ketsuatsu_app WORKDIR /ketsuatsu_app # bundle install & Delete Unnecessary Packages_and_Cache & Copy File COPY Gemfile /ketsuatsu_app/Gemfile COPY Gemfile.lock /ketsuatsu_app/Gemfile.lock RUN gem install bundler --version 2.1.2 \ && bundle install --path vendor/bundle \ && find vendor/bundle/ruby -path '*/gems/*/ext/*/Makefile' -exec dirname {} \; | xargs -n1 -P$(nproc) -I{} make -C {} clean \ && apk del ${DEV_PACKAGES} \ && apk del .ruby-builddeps COPY . /ketsuatsu_app ENTRYPOINT [ \ "prehook", "bundle install -j3 --path vendor/bundle", "--", \ "prehook", "ruby -v", "--", \ "prehook", "node -v", "--" \ ]
# Node.js & Yarn FROM node:10.15.1-alpine as node RUN apk add --no-cache --virtual .ruby-builddeps bash curl \ && curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.4
最初に Node.js 10.15.1 のベースイメージを読み込み、nodeという名前をイメージにつけている。
次に alpine では使えない bash と curl コマンドをイメージに加えてコマンドを使えるようにして、
Yarn 1.22.4 をインストールしている。
このイメージには、下記のフォルダに Node.js と Yarn がインストールされている。
/usr/local/bin/node /opt/yarn-v1.21.1
# Ruby & Bundler & postgresql-client FROM ruby:2.6.5-alpine COPY --from=node /usr/local/bin/node /usr/local/bin/node COPY --from=node /opt/yarn-* /opt/yarn RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn
次に、ruby 2.6.5 のベースイメージを読み込み、先ほど作ったnodeイメージの Node.js と Yarn を ruby のイメージにコピーしている。
そして、ln -s コマンドでシンボリックリンクを作成している。
Node.js は /usr/local/bin/node に、 Yarn は /usr/local/bin/yarn にコピーされたことになる。
ENV RUN_PACKAGES="nodejs postgresql postgresql-dev tzdata" \ DEV_PACKAGES="build-base curl-dev gcc libc-dev libxml2-dev linux-headers make" \ CHROME_PACKAGES="chromium chromium-chromedriver dbus mesa-dri-swrast ttf-freefont udev wait4ports xorg-server xvfb zlib-dev"
複数のパッケージを変数に格納してあとで使いやすくしている。
RUN apk --update --no-cache add ${RUN_PACKAGES} \ && apk --update --no-cache add ${DEV_PACKAGES} \ && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ && apk del --purge tzdata
先ほど変数に格納した必要なパッケージをインストール。
RUN と DEV に分けているのは、 RUN の方はそのまま Docker 上で使用するが、 DEV の方は環境構築が終わったら必要なくなるパッケージなので、あとで削除するためにこの分け方をしている。
apk del --purge パッケージ名 でそのパッケージに関連するファイルもまとめて削除できる。
# Entrykit & ChromeDriver RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && mv entrykit /bin/entrykit \ && chmod +x /bin/entrykit \ && entrykit --symlink \ && apk --update --no-cache add ${CHROME_PACKAGES}
Entrykit と ChromeDriver のインストール。
前回は ChromeDriver のインストールのために直接 URL を叩いていたが、 apt-get から apk に変わったとき、ChromeDriver インストール時に必要になる apt-key というコマンドが使用できないことが判明。これを何に置き換えれば良いかわからず苦戦したが、別の記事で必要なパッケージを読み込む方法があったので、そちらを採用した。
# bundle install & Delete Unnecessary Packages_and_Cache & Copy File COPY Gemfile /ketsuatsu_app/Gemfile COPY Gemfile.lock /ketsuatsu_app/Gemfile.lock RUN gem install bundler --version 2.1.2 \ && bundle install --path vendor/bundle \ && find vendor/bundle/ruby -path '*/gems/*/ext/*/Makefile' -exec dirname {} \; | xargs -n1 -P$(nproc) -I{} make -C {} clean \ && apk del ${DEV_PACKAGES} \ COPY . /ketsuatsu_app
この箇所はあまり変わりないが、最後にファイルコピーの前に必要ないパッケージとキャッシュを削除している。
また、ここでは割愛するが、docker-compose.yml の方でもdbのイメージに alpine を採用している。
以上でビルドした結果、5GBあったのが2.5GBくらいまで削減できた。
しかし、エラーが多数発生。今回はローカル環境で作った既存アプリを dockerに乗せて軽量化しようとしたが、Docker のイメージのほとんどは Ubuntu 環境であり、ローカル(Mac OS / つまり unix)環境のものを乗せようとするとうまく表示されないとのこと。
やるなら、最初から Docker 上で作るときのみ alpine は使った方が良さそう。
参考記事
Dockerのマルチステージビルドを使う - Qiita
Docker イメージサイズを抑えながら Ruby on Rails + PostgreSQL の開発環境を作成する - bitA Tech Blog
Rails 6.0 × MySQL8でDocker環境構築(Alpineベース) - Qiita
RailsのDockerイメージを一番小さくする方法 - Qiita
未来日・過去日の判定(カスタムvalidation)
class Record < ApplicationRecord ... validate :cannot_be_in_the_future ... # 日付に未来日は設定不可 def cannot_be_in_the_future if date.present? && date > Date.today errors.add(:date, :cannot_be_future_date) end end # 日付に過去日は設定不可 def cannot_be_in_the_past if date.present? && date < Date.today errors.add(:date, :cannot_be_past_date) end end end
これでも良いが、rails側に未来日を判定する Date.future?、過去日を判定する Date.past? があるので、これの方がわかりやすい。
class Record < ApplicationRecord ... validate :cannot_be_in_the_future validate :cannot_be_in_the_past ... # 日付に未来日は設定不可 def cannot_be_in_the_future if date.present? && date.future? errors.add(:date, :cannot_be_future_date) end end # 日付に過去日は設定不可 def cannot_be_in_the_past if date.present? && date.past? errors.add(:date, :cannot_be_past_date) end end end
このままだと英語がそのまま表示されてしまうので、ja.yml に対応する日本語を登録する。
# config/locales/ja.yml ja: activerecord: errors: models: record: attributes: date: cannot_be_future_date: "未来の日付は記録できません。" cannot_be_past_date: "過去の日付は記録できません。"
参考記事:
Ruby on Rails:過去日・未来日を判定する - Madogiwa Blog
Ruby on Rails:モデルに独自のバリデーションを実装する - Madogiwa Blog
migration ファイルの null: false について
コードレビューにて、migration ファイルに null: false をつけたほうが良いという指摘を受けたので、メモ。
結論としては、null: false も presence: true も両方設定したほうが良い。
null: false
指定したカラムがデータベースにカラの状態で保存されることを防ぐ。
class CreateUsers < ActiveRecord::Migration def change create_table :reviews do |t| t.string :name t.string :email null: false t.timestamps null: false end end end
presence: true
ActiveRecord のバリデーション。(Railsアプリケーションの判定)
指定したカラムが空の状態で保存されることを防ぐ。
class User < ApplicationRecord validates :name, presence: true end
presence: true は Rails アプリケーションの判定であるが、直接DBにデータを登録しようと思えばできてしまう。そこで、DB へ空のデータが保存できないようにするために、null: false が必要になってくる。