君が代

基本的にワイの忘備録(コードとかで誤りやもっと良い方法があるぞというのはコメントしてくれたらうれC)

【Rails忘備録】content_forを用いてページ毎にタイトルを出力するの巻

今まで作成したページタイトルを動的に出力する方法をまとめていく。

yieldとは

content_forとは

Railsにデフォで用意されているメソッドで、ページ毎で異なる内容を出力したい際に使用するものです。

手順

app/helpers/application_helper.rb

module ApplicationHelper
   def page_title(page_title = '')
     base_title = 'ジロリアス'
 
     page_title.empty? ? base_title : page_title + ' | ' + base_title
   end
 end

まずhelperに共通化する内容をまとめます。

page_title.empty? ? base_title : page_title + ' | ' + base_title

こちらに関しては三項演算子でページタイトルがなければベースタイトルの「ジロリアス」が出力され、ページタイトルがあれば「ページタイトル|ジロリアス」という表記になります。

application.html.slim

doctype html
html
  head
    title
      = page_title(yield(:title))
    = csrf_meta_tags
    = csp_meta_tag

new.html.slim

- content_for(:title, t('.title'))

あとは上記のようにtitle = page_title(yield(:title))と各ページにcontent_forを追加すれば完了。

【Rails忘備録】画像のアップロード機能を追加するの巻

今回は店舗情報の新規登録に画像をアップロードできるようにする。

セットアップ

まずMiniMagickを利用するためにImageMagickをインストール。

$ brew install imagemagick

このImageMagickは画像のサイズ調整やその他の編集などが行える画像処理のライブラリです。

次に画像をアップするためのGemをインストールします。

gem 'mini_magick'
gem 'carrierwave'
$ bundle

このMiniMagickはImageMagickRuby用に使いやすくしたものの一つのようです。

MiniMagick is a Ruby wrapper for ImageMagick command line. MiniMagick gives you convenient access to all the command line options ImageMagick supports.

imagemagick.org

Carrierwaveはファイルをアップロードするためのgemです。

続いて画像投稿用のアップローダーを作成します。

$ rails g uploader image

実装

ここからアップローダーをモデルに紐付けていきます。

以下を実行してstoreにimageを追加します。

$ rails g migration AddImageToStores image:string
$ rails db:migrate

店舗情報への画像追加なのでstoreモデルに今回は追記します。 app/models/store.rb

class Store < ApplicationRecord
   mount_uploader :image, ImageUploader

stores/_form.html.slim

= form_with model: store, local: true do |f|
  = render 'shared/error_messages', object: f.object
  .form-group
    = f.label :name
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :address
    = f.text_field :address, class: 'form-control'
  .form-group
    = f.label :business_hours
    = f.text_field :business_hours, class: 'form-control'
  .form-group
    = f.label :regular_holiday
    = f.text_field :regular_holiday, class: 'form-control'
  .form-group
    = f.label :description
    = f.text_area :description, class: 'form-control', rows: 10
  .form-group
    = f.label :image
    = f.file_field :image, class: 'form-control'
    = f.hidden_field :image_cache
  .mt-3.mb-3
    = image_tag store.image.url, id: 'preview', size: '300x200'
  = f.submit class: 'btn btn-primary'

= f.hidden_field :image_cacheに関して

こちらについてはバリデーションエラー等で画像のアップロードに失敗した時に、再度画像を添付してなくても済むように画像を保持することができるようにするものです。

次に画像の値を送れるようにストロングパラメーターを更新します。 stores_controller.rb

def store_params
    params.require(:store).permit(:name, :address, :business_hours, :regular_holiday, :description, :image, :image_cache)
 end

最後に店舗一覧画面にアップした画像を表示できるようにします。 stores/_store.html.erb

.col-sm-12.col-lg-4.mb-3
  .card
    = image_tag store.image_url,  class: 'card-img-top', size: '300x200'
    .card-body
      h4.card-title
        = link_to store.name, store_path(store.id)
      .mr10.float-right
        a[href="#"]
          = icon 'fas', 'trash', class: 'pr-1'
        a[href="#"]
          = icon 'fa', 'pen'
      p.card-text
        = store.description

また画像が無い場合のデフォルトの画像の設定も行います。 こちらはviewにelseの処理を記載するのではなく、default_urlを設定します。 uploaders/image_uploder.rb

def default_url
  'sample.jpg'
end

以上です。

【Rails忘備録】フォームの入力エラーメッセージを個別表示させるの巻

今回はフォームの入力時にバリデーションエラーが発生した際に、どの箇所でエラーが出たのかを項目ごとに表示させるようにします。

手順

エラーメッセージを複数の箇所で利用するため、formのテンプレートに記載して特定のモデルのみに依存させるのではなくパーシャル化してsharedに格納する。

app/views/shared/_error_messages.html.slim

- if object.errors.any?
  .alert.alert-danger
    ul.mb-0
      - object.errors.full_messages.each do |msg|
        li
          = msg

あとはrenderの記載をユーザーと店舗の新規登録のページにそれぞれ行えば完了です。

app/views/users/new.html.slim

.container
  .row
    .col-md-10.offset-md-1.col-lg-8.offset-lg-2
      h1
        = t '.title'
      = form_with model: @user, local: true do |f|
        = render 'shared/error_messages', object: f.object
        .form-group
          = f.label :name
          = f.text_field :name, class: 'form-control'
        .form-group
          = f.label :email
          = f.email_field :email, class: 'form-control'
        .form-group
          = f.label :password
          = f.password_field :password, class: 'form-control'
        .form-group
          = f.label :password_confirmation
          = f.password_field :password_confirmation, class: 'form-control'
        = f.submit (t 'defaults.register'), class: 'btn btn-primary'
      .text-center
        = link_to (t '.to_login_page'), login_path

app/views/stores/_form.html.slim

= form_with model: store, local: true do |f|
  = render 'shared/error_messages', object: f.object
  .form-group
    = f.label :name
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :address
    = f.text_field :address, class: 'form-control'
  .form-group
    = f.label :business_hours
    = f.text_field :business_hours, class: 'form-control'
  .form-group
    = f.label :regular_holiday
    = f.text_field :regular_holiday, class: 'form-control'
  .form-group
    = f.label :description
    = f.text_area :description, class: 'form-control', rows: 10
  = f.submit class: 'btn btn-primary'

【Rails忘備録】formヘルパーを用いた店舗情報の新規登録を行うの巻

今回は前回の一覧機能に続き新規登録機能の実装についてまとめていく。

手順

まずコントローラーの記載から。

stores_controller.rb

class StoresController < ApplicationController
  def index
    @stores = Store.all.includes(:user).order(created_at: :desc)
  end

  def new
    @store = Store.new
  end
  
  def create
    @store = current_user.stores.build(store_params)
    if @store.save
      redirect_to stores_path
    else
      render :new
    end
  end

  def show
    @store = Store.find(params[:id])
  end

  private

  def store_params
    params.require(:store).permit(:name, :address, :business_hours, :regular_holiday, :description, :image, :image_cache)
  end
end

アソシエーションに関して

前回の記事でuserモデルにhas_many :storesを設定したため、userオブジェクトにてstoresというメソッドが使えるようになってる。
current_user.stores.newを設定することで、user_idを登録したstoreオブジェクトを初期化できる。   buildはnewの別名であるためどちらで記載しても挙動に変化は無いが、アソシエーションで関連したオブジェクトを初期化する際は通常の初期化と区別してnewではなくbuildで記載するケースがあるそう。
なので今回はcurrent_user.stores.buildとしている。

  # GOOD アソシエーションを活用
  @store = current_user.stores.build(store_params)

  # BAD 初期化した後に値を代入
  @store = Store.new(store_params)
  @store.user_id = current_user.id

  # BAD 初期化する際のパラメータをmerge
  Store.new(store_params.merge(user_id: current_user.id))

ストロングパラメーターに関して

端的に説明すると入力された値を制限することで、指定したキーを持つパラメーターのみを受け取り不正なパラメーターの混入を防ぐ仕組み。 requireでPOSTで受け取る値のキーを設定し、permitで許可するバリューを設定します。

次にviewについて。 new.html.slim

.container
  .row
    .col-lg-8.offset-lg-2
      h1
        = t('.title')
      = render 'form', { store: @store }

_form.html.slim

= form_with model: store, local: true do |f|
  .form-group
    = f.label :name
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :address
    = f.text_field :address, class: 'form-control'
  .form-group
    = f.label :business_hours
    = f.text_field :business_hours, class: 'form-control'
  .form-group
    = f.label :regular_holiday
    = f.text_field :regular_holiday, class: 'form-control'
  .form-group
    = f.label :description
    = f.text_area :description, class: 'form-control', rows: 10
  = f.submit class: 'btn btn-primary'

【Rails忘備録】店舗一覧機能(外部キー追加によるReferences型の使用)を作成するの巻

今回は店舗一覧機能ついてまとめる。

手順

まずStoreのモデルを作成する。 UserをStoreの外部キーとして設定する場合、以下のようにして外部キーを加えることができるようになります。

$ rails g model Store ... user:references

create_stores.rb

class CreateStores < ActiveRecord::Migration[5.2]
  def change
    create_table :stores do |t|
      t.string :name, null: false
      t.string :address, null: false
      t.text :business_hours, null: false
      t.string :regular_holiday, null: false
      t.text :description
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

店舗情報については管理ユーザーのみが編集できるため、ユーザーと店舗情報の関係は1対多となる。 その場合の紐付けは下記のように設定する。

user.rb

has_many :stores, dependent: :destroy

store.rb

belongs_to user

上記のアソシエーションを定義すればStoreモデルのレコード(=@store)から、紐づくユーザーモデルのレコード(=@user)を取得したい場合に、記述を簡素化することができます。

# アソシエーション無し
@user = User.find(@store.user_id)
---------------------------------------------
# アソシエーションあり
@user = @store.user

viewに関しては下記のようになります。

index.thml.slim

.container.pt-3
  .row
    .col-lg-10.offset-lg-1
      form
        .input-group.mb-3
          input.form-control[placeholder="検索ワード" type="search"]
          .input-group-append
            input.btn.btn-primary[type="submit" value="検索"]
  .row
    .col-12
      .row
        - if @stores.present?
          = render @stores
        - else
          p
            = t('.no_result')

_store.html.slim

.col-sm-12.col-lg-4.mb-3
  .card
    = image_tag store.image_url,  class: 'card-img-top', size: '300x200'
    .card-body
      h4.card-title
        = link_to store.name, store_path(store.id)
      .mr10.float-right
        a[href="#"]
          = icon 'fas', 'trash', class: 'pr-1'
        a[href="#"]
          = icon 'fa', 'pen'
      p.card-text
        = store.description

【Rails忘備録】フラッシュメッセージを導入するの巻

今回は下のようなフラッシュメッセージを表示させるための手順をまとめる。

f:id:kimigayo-technologies:20201120094911p:plain

手順

application_controller.rb

add_flash_types :success, :info, :warning, :danger

フラッシュメッセージはデフォルトではnoticeとalertのみが設定されています。なので上記の設定を追加しBootstrapで他のキーを使えるようにします。

またこの設定を行っておくことでフラッシュメッセージの記入量を減らすこができます。

# before
redirect_to login_path, flash: { success: 'yattaze' }

# after
redirect_to login_path, success: 'yattaze'

次にフラッシュメッセージの雛形のファイルを追加する。 views/shared/_flash_message.html.slim

- flash.each do |message_type, message|
  div class="alert" class="alert-#{message_type}"
    = message

変数展開の時はslimでもdiv使うんやな。(今回初めて知った)

上記のファイルはapplication.html.slimのyieldの上に配置。

    - if logged_in?
      = render 'shared/header'
    - else
      = render 'shared/before_login_header'
    = render 'shared/flash_message'
    = yield
    = render 'shared/footer'

そして翻訳ファイルとコントローラーに下記を記入して完了。

config/locales/views/ja.yml

user_sessions:
     new:
       title: 'ログイン'
       to_register_page: '登録ページへ'
       password_forget: 'パスワードをお忘れの方はこちら'
     create:
       success: 'ログインしました'
       fail: 'ログインに失敗しました'
     destroy:
       success: 'ログアウトしました'

user_sessions_controller.rb

def create
    @user = login(params[:email], params[:password])
    if @user
      redirect_back_or_to stores_path, success: t('.success')
    else
      flash.now[:danger] = t('.fail')
      render :new
    end
end

【Rails忘備録】i18nによる日本語化を行うの巻

公式Github

github.com

手順

gem 'rails-i18n'
$ bundle

インストール後は下記を記載。 これが無いと日本語化ができない。

appilication.rb

config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config/locales/**/*.{rb,yml}').to_s]

次にActiverecoredとViewでそれぞれ翻訳ファイルを作成します。

config/locales/activerecord/ja.yml

ja:
   activerecord:
     models:
       user: 'ユーザー'
       store: '店舗'
     attributes:
       user:
         email: 'メールアドレス'
         password: 'パスワード'
         password_confirmation: 'パスワード確認'
         name: '名前'

config/locales/views/ja.yml

ja:
   defaults:
     login: 'ログイン'
     register: '登録'
     logout: 'ログアウト'
   users:
      new:
        title: 'ユーザー登録'
        to_login_page: 'ログインページへ'
   user_sessions:
     new:
       title: 'ログイン'
       to_register_page: '登録ページへ'
       password_forget: 'パスワードをお忘れの方はこちら'
   boards:
     index:
       title: '掲示板一覧'
     new:
       title: '掲示板作成'
     bookmarks:
       title: 'ブックマーク一覧'
   profiles:
     show:
       title: 'プロフィール'

実際の現場ではサービスが大きくなればそれに伴い、翻訳項目も多くなるため管理を簡素化するためにコントローラーごとに分けて翻訳ファイルを作成することもあるようです。

new.html.slim

container
  .row
    .col-md-10.offset-md-1.col-lg-8.offset-lg-2
      h1
        = t '.title'
      = form_with url: login_path, local: true do |f|
        .form-group
          = f.label :email, User.human_attribute_name(:email)
          = f.text_field :email, class: 'form-control'
        .form-group
          = f.label :password, User.human_attribute_name(:password)
          = f.password_field :password, class: 'form-control'
        .actions
          = f.submit (t 'defaults.login'), class: 'btn btn-primary'
      .text-center
        = link_to (t '.to_register_page'), new_user_path
        a[href="#"]
          = t '.password_forget'

モデルでの多言語化は下記の記事が参考になった。

qiita.com