現在のページに応じてactiveクラスを付与する

メニューリストとかで現在のページに応じて色がついてたらサイトが見やすくなりますよね。

Boardsページにいる時

Image from Gyazo

Usersページにいる時

Image from Gyazo

こんな感じにしたいです。

今回はこれを実装していきます。
前回作ったアプリにBootstrapを導入してBoardのCRUD機能をつけておきました。
viewも最低限整えてます。

どう実装するかですが、現在のページに応じて、というところが肝になりそうです。
では何で判断すればいいか。
そのページがどのコントローラを通っているか、を判断できるようなメソッドを作れば実装できそうです。

ヘルパーメソッドを作成

app/helpers/application_helper.rbにヘルパーメソッドを定義します。

module ApplicationHelper
  def add_active(controller_name)
    if controller_name == params[:controller]
      return 'active'
    end
end

もしparamsで受け取っているcontrollerが引数で渡したcontroller_nameと一致していたら、文字列'active'を返す。
というメソッドです。

viewに記述

_menu.html.erb

<ul class="nav nav-pills">
  <li>
    <%= link_to 'User List', root_path, class: "nav-link #{add_active('users')}" %> <%# users_controllerを通っていたらadd_activeメソッドを適用 %>
  </li>
  <li>
    <%= link_to 'Board List', boards_path, class: "nav-link #{add_active('boards')}" %> <%# boards_controllerを通っていたらadd_activeメソッドを適用 %>
  </li>
</ul>

こんな感じです。
細かいですが変数展開を使っているのでclassの要素はダブルクォーテーション("")で囲みます。
僕はこれで30分程溶けました。

Image from Gyazo

できました。
ログと検証ツールでも

users_controllerを通っている時

Image from Gyazo

Image from Gyazo

boards_controllerを通っている時

Image from Gyazo

Image from Gyazo

想定通りになりました。

最後に

こういう便利ヘルパーメソッドを作ったり、リファクタリングしたりが苦手です。今回もいろいろ参考にさせてもらいました。引き出しを増やして最適なものを組み合わせる、という方向でも伸ばしたいです。
では今回は以上です。ありがとうございました。

参考サイト

Railsでアクティブなページに対応するタブのスタイル変える場合 - なんかの備忘録

コントローラ(controller) | Railsドキュメント

ransackの検索機能

今回はgem ransackで検索機能を実装します。
以前の記事でフリーワード検索を実装したのですが、今回は作成日での検索とセレクトボックスでの検索を実装していきます。
1つ前の記事で作ったscaffoldアプリを元にやっていきます。
このアプリではi18nenumenum_helpを導入済みです。
ではいってみましょう。

ransackをインストール

gemfileに

gem 'ransack'

として

bundle install

します。

コントローラー編集

コントローラーで検索結果を受け取れるようにします。

  def index
    # @users = User.all これを削除
    @q = User.ransack(params[:q]) #この2行を
    @users = @q.result(distinct: true) #追加します
  end

ransackメソッド送られてきたパラメーターを元にテーブルからデータを検索するメソッド
(params[:q])検索パラメータを取得
resultメソッドransackメソッドで取得したデータをオブジェクトに変換するメソッド
(distinct: true)検索結果の重複を取り除く

viewの編集

ついでにviewをちょっと見やすく編集します。

users/index.html.erb

<p id="notice"><%= notice %></p>

<h1>Users</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Gender</th>
      <th>Created_at</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <%= render @users %>
  </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>
users/_user.html.erb

<tr>
  <td><%= user.name %></td>
  <td><%= user.gender_i18n %></td>
  <td><%= user.created_at %></td>
  <td><%= link_to 'Show', user %></td>
  <td><%= link_to 'Edit', edit_user_path(user) %></td>
  <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

ユーザー情報部分を部分テンプレートとして切り出して、作成日時も表示されるようにしました。

Image from Gyazo
こんな感じです。

検索フォームの作成

セレクトボックスで検索

まずはセレクトボックスで、性別で検索できるようにします。

users/index.html.erb

<p id="notice"><%= notice %></p>

<h1>Users</h1>

<%= search_form_for @q do |f| %>
  <%= f.select :gender_eq, User.genders_i18n.invert.map{|key, value| [key, User.genders[value]]} %>
  <%= f.submit %>
<% end %>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Gender</th>
      <th>Created_at</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <%= render @users %>
  </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>

search_form_forransackが提供するメソッド
f.selectのところがややこしいので1つ1つ見ていきます。
まず第一引数の:gender_eqですが、選択した性別と等しいものを探すという意味です。eqはイコール(equal)の略ですね。
そしてややこしい第二引数。
User.genders_i18n.invertまでは前回の記事を参照してください。

irb(main):001:0> User.genders_i18n.invert
=> {"男性"=>"male", "女性"=>"female", "秘密"=>"secret"}
irb(main):002:0> User.genders_i18n.invert.map{|key,value|[key,User.genders[value]]}
=> [["男性", 0], ["女性", 1], ["秘密", 2]]

この2つを比べてみます。
違いは

  • ハッシュから配列に変わっている
  • male, female, secretから0, 1, 2に変わっている

の2点、違いがあります。
ハッシュから配列に変わっているのはmapメソッドによるものです。
mapメソッドは、配列orハッシュ.map {|変数名| 実行する処理 }で戻り値を配列で返します。
今回のややこしいところに当てはめてみると、

irb(main):001:0> User.genders_i18n.invert
=> {"男性"=>"male", "女性"=>"female", "秘密"=>"secret"}

これを配列の形になおして欲しい。
配列の中身は|key, value|という形にしてくれ。
keyはそのままkey("男性"=>"male"の"男性")、
valueはUser.genders[value]つまり

irb(main):003:0> User.genders
=> {"male"=>0, "female"=>1, "secret"=>2}

valueの部分("male"=>0の0)
つまり最終的な形が

irb(main):002:0> User.genders_i18n.invert.map{|key,value|[key,User.genders[value]]}
=> [["男性", 0], ["女性", 1], ["秘密", 2]]

になる、という流れです。
これでセレクトボックスによる検索が実装できました。
Image from Gyazo

最後に指定なしでも検索できるように

<%= f.select :gender_eq, User.genders_i18n.invert.map{|key, value| [key, User.genders[value]]}, include_blank: '指定なし' %>

include_blankを設定しておきます。
複数条件で検索する時に必要です。

作成日で検索

次は作成日での検索です。
例えば5/1の00:00から5/10の23:59で検索したいとします。

<%= search_form_for @q do |f| %>
  <%= f.select :gender_eq, User.genders_i18n.invert.map{|key, value| [key, User.genders[value]]}, include_blank: '指定なし' %>
  <%= f.date_field :created_at_gteq %>
  <span></span>
  <%= f.date_field :created_at_lteq %>
  <%= f.submit %>
<% end %>

date_field日付の入力欄を生成
gteqgreater than equalの略
lteqless than equalの略
created_at_gteqで指定した日付以降
created_at_lteqで指定した日付まで

Image from Gyazo

こんな感じで日付で検索できます。
しかし実際に検索してみると

Image from Gyazo

5/10の15:00:00に作成したyamadaさんが表示されていません。
created_at_lteqだと、その日の00:00までの範囲で検索してしまうからです。
これを23:59までにするにはpredicateのカスタマイズが必要です。

predicateのカスタマイズ

まずconfig/initializers/ransack.rbを作成します。

Ransack.configure do |config|
  config.add_predicate 'lteq_end_of_day', # 名前を付ける
                       arel_predicate: 'lteq', # 'lteq'をカスタマイズしますよ
                       formatter: proc { |v| v.end_of_day } # end_of_dayメソッドを実行
end

これでカスタマイズできたので検索フォームを修正してみます。

<%= search_form_for @q do |f| %>
  <%= f.select :gender_eq, User.genders_i18n.invert.map{|key, value| [key, User.genders[value]]}, include_blank: '指定なし' %>
  <%= f.date_field :created_at_gteq %>
  <span></span>
  <%= f.date_field :created_at_lteq_end_of_day %>
  <%= f.submit %>
<% end %>

これで

Image from Gyazo

想定通りの検索ができました。

最後に

自分的には難しい内容でした。コンソールを使ったりして流れを追って、なんとかやってることはわかった、ぐらいの感じです。
このロジックを自分で考えるのはちょっと今の理解度では厳しいな、と思いました。アウトプットを続けて知識を定着させていきたいです。
あと参考にしたサイトとかいつも載せてなかったんですが載せないとダメですよね、忘れてました。すいません。
今日は疲れたので次回から載せます…
最後まで読んでいただきありがとうございました。間違いなどありましたらコメントいただけますと幸いです。

enum_help導入

制作中の掲示板アプリにenum_helpを導入しました。
ちゃんと定着させるためにもう一度実際に手を動かしながらやってみようと思います。

今回やりたいのは

フォームにセレクトボックスを作り選択肢を日本語で表示する

です。

Image from Gyazo
こんな感じの作ります。
i18nenumenum_helpを使って実装したいと思います。
rails newからやったので順番にいきましょう。

新規アプリ作成

rails new sample_app

名前は適当にsample_appで作成します。

cd sample_app

で移動して

rails g scaffold user name:string gender:integer

scaffoldで雛形を作り、userモデルを作成。カラムは名前と性別を作ります。
性別(gender)はinteger型にしておきます。
enum用のカラムはinteger型かboolean型かのどちらかです。
integerなら整数、booleanなら真偽値(true, false)でDBに保存されます。
選択肢が2択ならboolean、それより多い、今後増える、とかの場合はintegerがいいのかなと思います。
マイグレーションファイルを

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.integer :gender, default: 0, null: false

      t.timestamps
    end
  end
end

このように編集して

rails db:migrate

します。

gemのインストール

必要なgemをインストールしましょう。
gemfileに

gem 'rails-i18n'
gem 'enum'
gem 'enum_help'

として

bundle install

これでインストールできたかと思います。

enumの定義

userモデルでenumの定義をします。

# user.rb

enum gender: { male: 0, female: 1, secret: 2 }

i18nの設定

config/application.rbに必要な設定を記述します。

require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module SampleApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # これを追加
    config.i18n.default_locale = :ja #デフォルト言語を日本語に設定
  end
end

config/locales/ja.ymlを作成し、

ja:
  enums:
    user:
      gender:
        male: 男性
        female: 女性
        secret: 秘密

翻訳情報を作成します。

viewを整形

viewを整えます。

users/_form.html.erb

<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :gender %>
    <%= form.number_field :gender %> <%#この行を削除 %>
    <%= form.select :gender, User.genders_i18n.invert %> <%# この行を追加 %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

User.gendersでuserモデルのgenderの値をハッシュで取得します。

irb(main):001:0> User.genders
=> {"male"=>0, "female"=>1, "secret"=>2}

これをUser.genders_i18nとすると

irb(main):002:0> User.genders_i18n
=> {"male"=>"男性", "female"=>"女性", "secret"=>"秘密"}

ja.ymlファイルで設定した日本語が適用されました。
このカラム名(複数形)_i18nenum_helpで使えるようになります。
しかしこれだと
Image from Gyazo
選択肢は英語のままになっています。
ハッシュのkeyの方が取得されていますね。これに

irb(main):003:0> User.genders_i18n.invert
=> {"男性"=>"male", "女性"=>"female", "秘密"=>"secret"}

invertメソッドを付けるとkeyとvalueを入れ替えることができます。
これで再度見てみると
Image from Gyazo
日本語になりました。

最後に

無事実装できました。使用頻度が高そうな気がするのでしっかり押さえたいです。
以上となります。読んでいただきありがとうございました。

Admin-LTEで管理者用ページを実装

今回はAdmin-LTEを使って管理者用ページを作りたいと思います。
Admin-LTEとは管理画面に特化したBootstrapベースのCSSフレームワークです。
管理者用なので凝ったデザインにする必要はないですよね。なのでフレームワークを使ってサクッと作ってしまいたいです。

インストール・設定

yarnでインストールしました。

yarn add admin-lte

インストールするとnode_modulesというディレクトリが作成されます。
今回はこの中のadmin-lte/starter.htmlを見本に作ってみます。
Admin-LTEのページでいろんなテンプレートが見れるのでここから好みのデザインを選ぶのもいいと思います。
Demoでテンプレートを開いて検証ツールを開きます。
CSSheadタグの中 Image from Gyazo JavaScriptbodyタグの中 Image from Gyazo
にあるので、app/assets内で必要なものを読み込みます。
管理者用ページとユーザーが使うページのデザインが異なるのでapplication.jsやapplication.scssではなく、新たに管理者用ページのマニフェストファイルadmin.jsadmin.scssを作成し、そこに記述します。

// app/assets/javascript/admin.js

//= require jquery3
//= require rails-ujs
//= require admin-lte/plugins/bootstrap/js/bootstrap.bundle.min.js
//= require admin-lte/dist/js/adminlte.min.js
/* app/assets/stylesheets/admin.scss */

@import 'font-awesome-sprockets';
@import 'font-awesome';
@import 'admin-lte/plugins/fontawesome-free/css/all.min.css';
@import 'admin-lte/dist/css/adminlte.min.css';

application.js内の//= require_tree .は全てのjsファイルを読み込んでしまうので、view毎にデザインを変えたい場合は削除します。

また、application.js、application.scssはデフォルトで読み込んでくれますが、それ以外のマニフェストファイルは設定しないといけないので

# config/initializers/assets.rb

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w[admin.js admin.css]  # コメントアウトを外す

とします。

Controllerを作成

管理機能を持つコントローラを作ります。
管理用に階層を分けた方が管理しやすそうです。

rails g controller Admin::Base

でApplicationControllerを継承したAdmin::BaseControllerを作成し、Admin系コントローラの基幹とします。
さらにこのAdmin::BaseControllerを継承したコントローラで管理者ページの機能を作ります。
Image from Gyazo
階層はこんな感じです。
admin/user_sessions_controller.rbとadmin/dashboards_controller.rbは

class Admin::UserSessionsController < Admin::BaseController
 略
end
class Admin::DashboardsController < Admin::BaseController
 略
end

として基幹となるAdmin::BaseControllerを継承します。
また、一般ユーザー用のページとは異なるデザインを当てるため、

class Admin::BaseController < ApplicationController
  layout 'admin/layouts/application'
 略
end

layoutメソッドで使用するlayoutファイルを指定します。

Viewを作成

viewの中身は省略しますが、前述の通りnode_modules/admin-lte/starter.htmlをコピペして不要な部分を削除しました。 layoutファイルでは

<%= stylesheet_link_tag 'admin', media: 'all' %>
<%= javascript_include_tag 'admin' %>

読み込むCSSJavaScriptadminと指定します。

ルーティングの設定

管理者用ページのpathはadmin/loginのようにしたいので

  namespace :admin do
    root 'dashboards#index'
    get 'login', to: 'user_sessions#new'
    post 'login', to: 'user_sessions#create'
    delete 'logout', to: 'user_sessions#destroy'
  end

namespaceを使います。すると
Image from Gyazo
ルーティングは/admin/○○のようになります。

enumの定義・管理者かどうかの判別をするカラムを作成

enumとは簡単に言うと名前に数値を割り当てることです。
user.rbでUserモデルにenumの定義をしておきます。

enum role: { general: 0, admin: 1 }

これだけです。

Userモデルで管理者かそうでないかの判別をするroleカラムを作ります。roleとは役割という意味です。

rails g migration add_role_to_users

で作られたマイグレーションファイルに

class AddRoleToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :role, :integer, default: 0, null: false
  end
end

としてrails db:migrateします。
enumの定義で数字を割り当てたので型はintegerです。
一般か管理者か、必ずどちらか割り当てるためnull: false
デフォルトは一般にしたいのでdefault: 0
とします。
僕はnullとdefaultのオプションを付けずに作ってしまって、後から

rails g migration change_column_to_users
class ChangeColumnToUsers < ActiveRecord::Migration[5.2]
  def change
    change_column_null :users, :role, false
    change_column_default :users, :role, from: nil, to: "0"
  end
end

としようとしたらエラーが出て追加できませんでした。
レコードを全削除したら無事追加できたんですが良い手ではないのでまた調べたいです。
まだ機能が全然ないですがとりあえず管理者用ページができました。

最後に

change_columnできなかったところや管理ツール(yarnなど)のこと、アセットパイプラインなどやればやるほど分からないことが出てきて沼です。
ブログのネタができて嬉しいなぁ…1つ1つ、潰していきたいです。

以上です、ありがとうございました。

パスワードリセット機能の実装

ユーザー登録ができるサービスには必ず付いていると言っても過言ではないであろう機能、パスワードリセット機能を実装していきます。

今回はgem sorceryのモジュールであるreset_passwordを導入します。sorceryの導入を前提で進めていきます。

パスワードリセットの流れ

  • パスワードリセット申請ページからメールアドレスを送信
  • メールを受信しリンクをクリック
  • パスワードリセットページでパスワードを更新

という感じです。
開発環境ではメールを実際には送らないようletter_opener_webというgemを使ってますがそちらは割愛させていただきます。
にではいってみましょう。

reset_passwordモジュールのインストール

$ rails g sorcery:install reset_password --only-submodules

でインストール。
マイグレーションファイルが作成されます。
トークンは一意でなければいけないのでreset_password_tokenカラムにindex制約をかけておきます。 トークンとはワンタイムパスワードのようなものです。

class SorceryResetPassword < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :reset_password_token, :string, default: nil
    add_column :users, :reset_password_token_expires_at, :datetime, default: nil
    add_column :users, :reset_password_email_sent_at, :datetime, default: nil

    add_index :users, :reset_password_token # これを追加
  end
end

として

rails db:migrate

DBに反映。

モデルの方でもvalidationを設定しておきます。

# user.rb

validates :reset_password_token, uniqueness: true, allow_nil: true

allow_nilオプションは、対象の値がnilの場合にバリデーションをスキップします。
パスワードが変更される際にreset_password_tokenは削除されnilになります。
allow_nil: trueがないとユニーク制約にに引っかかってしまうのでこちらも設定します。

Mailerの作成

メイラーとはRailsからメールを送信する機能です。
まずは

$ rails g mailer UserMailer reset_password_email

でUserMailerを作成します。
rails g mailer メーラー名 メソッド名です。
共通の設定

# application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  default from: 'example@example.com' # メールの送信元
  layout 'mailer' # mailer.html.erb、mailer.text.erbをメールのレイアウトとして使用する
end


メール送信のメソッドを定義します。

# user_mailer.rb

class UserMailer < ApplicationMailer
  def reset_password_email(user)
    @user = User.find(user.id)
    @url = edit_password_reset_url(@user.reset_password_token)
    mail to: user.email,
         subject: 'パスワードリセット'
  end
end

mailメソッド送信先、メールのタイトルを設定しています。

sorcery.rbの設定もしておきます。

Rails.application.config.sorcery.submodules = [:reset_password] # サブモジュールとしてreset_passwordを設定

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.reset_password_mailer = UserMailer # パスワードリセット用のメーラーにUserMailerを指定
  end
end

メール本文を作成

views/user_mailer/reset_password_email.text.erbviews/user_mailer/reset_password_email.html.erbに実際のメールの内容を記述します。
一部のメールソフトではhtml形式のメールが受け取れない(ガラケーとか?)などがあるようで、その対策としてtext形式とhtml形式の2つで作成します。
Railsではマルチパートメールという仕組みでtext形式とhtml形式の両方をメール送信して、メールクライアント側で自動判別してメールを表示してくれるそうです。
また、メンテナンス等の際、両方ともメンテしなくてもいいようにactionmailer-textというgemもあるようです。
ここでは取り扱わないので興味のある方は調べてみてください。

コントローラーの設定

# app/controllers/password_resets_controller.rb

class PasswordResetsController < ApplicationController
  skip_before_action :require_login
    
  def create 
    @user = User.find_by_email(params[:email])
        
    @user.deliver_reset_password_instructions! if @user
        
    redirect_to(root_path, :notice => 'Instructions have been sent to your email.')
  end
    
  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    if @user.blank?
      not_authenticated
      return
    end
  end
      
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    if @user.blank?
      not_authenticated
      return
    end

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password(params[:user][:password])
      redirect_to(root_path, :notice => 'Password was successfully updated.')
    else
      render :action => "edit"
    end
  end
end

フォームの作成

パスワードリセット申請フォーム

<%= form_with url: password_resets_path, local: true do |f| %>
    <%= f.label :email, 'メールアドレス' %>
    <%= f.email_field :email, class: 'form-control' %>

  <%= f.submit class: 'btn btn-primary' %>
<% end %>

パスワードリセットフォーム

<%= form_with model: @user, url: password_reset_path(@token), local: true do |f| %>
    <%= f.label :email %><br>
    <%= @user.email %>

    <%= f.label :password %>
    <%= f.password_field :password, class: 'form-control' %>

    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit class: 'btn btn-primary ' %>
<% end %>

ルーティングの設定

resources :password_resets, only: %i[create edit update]

を追加。 以上です。

最後に

今まで使ったことなかったMailerとかが出てきて難しかったです。
githubからコピペしたのでコントローラ内のコードの流れが正直まだわかってません。 また振り返って流れを追いたいと思います。
間違いなどありましたらコメントいただけると幸いです。
読んでいただきありがとうございました。

form_withと仲良くなろう

form_withってよく使うけど曖昧なままきてるなあ、と思ったので振り返ってみます。以前にも一度記事にしたので2回目です。
色々やってくれる優秀なform_withですが、model渡したりurl渡したり、結局どんな動きしてるの?

form_withとは

情報を送信するためのヘルパーメソッドです。

urlを渡す

urlはフォームの情報の送信先のURLを指定するためのオプションです。
urlを渡すと、そのpathに対してデフォルトではPOSTメソッドでリクエストが送られます。

<!-- ログインフォーム -->

<%= form_with url: login_path, local: true do |f| %>
  <%= f.label :email %>
  <%= f.text_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.submit  class: 'btn btn-primary' %>
<% end %>

ログインの情報はユーザー照合するだけでDBに保存しないのでmodelは渡しません。(と思ってるのですが自信ないので間違ってたらご指摘ください。)

modelを渡す

modelはDBに保存する時に指定します。

<!-- ユーザー登録、更新フォーム -->

<%= form_with model: @user, local: true do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.text_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.label :password_confirmation %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>
        
  <%= f.submit  class: 'btn btn-primary' %>
<% end %>

modelに指定している@userはコントローラで定義したインスタンスです。
このインスタンス
値が入ってるか
で処理が変わります。

空の場合は

def new
  @user = User.new. # 空のインスタンスを作成
end

def create
  @user.create(user_params)
end

private

def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
end

空ってことは新規作成したいんだな
 ↓
createアクション

値が既に入ってる場合は

def edit
  @user = User.find(params[:id]) # ユーザー情報をfindで取得して@userに代入
end

def update
  @user.update(user_params)
end

private

def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
end

値が既に入ってるから更新したいんだな
 ↓
updateアクション

という感じで判断してくれます。

最後に

form_with然り、render然り、redirect_to然り、Railsはその「よしなに力」の高さ故、初学者にとっては逆に何をやってるのかわからない、となりがちだと思います。
なんか動いてるラッキー、ではなく何が省略されてるのか、どんな処理が走っているのか、しっかり理解しながら進めていきたいですね。

ransackで検索フォームを実装

今回は検索フォームを実装したいと思います。

Image from Gyazo

こちらが完成品です。
掲示板のタイトルか本文にワードが含まれていたら検索結果を一覧で表示させるようにします。

ransack

ransackとは簡単に検索機能を実装できるgemです。

インストール

まずはGemfileに

gem 'ransack'

を加えてbundle installします。

コントローラーを編集

class BoardsController < ApplicationController
  def index
    @q = Board.ransack(params[:q])
    @boards = @q.result(distinct: true)
  end

ransackメソッド送られてきたパラメーターを元にテーブルからデータを検索するメソッド
params[:q])検索パラメータを取得
resultメソッドransackメソッドで取得したデータをオブジェクトに変換するメソッド
(distinct: true)検索結果の重複を取り除く

ビューを作成

検索フォームは他のページにも使いたいので部分テンプレート化しておきます。

<!-- _search_form.html.erb -->

<%= search_form_for q, url: url do |f| %>
  <div class='input-group'>
    <%= f.search_field :title_or_body_cont, 
                        class: 'form-control',
                        placeholder: '検索ワード',
                        type: 'search' %>
    <div class='input-group-append'>
      <%= f.submit class: 'btn btn-primary' %>
    </div>
  </div>
<% end %>

ransackで用意されているsearch_form_forでフォームを作りましょう。
フォーム部分はf.search_fieldとすることで検索用のフォームとして作ることができ、type='search'が付与されます。
こうすることでフォームにxボタンが付き、表示内容を削除できます。

Image from Gyazo

あとは表示させたいところでrenderしてあげるだけです。

<!-- app/views/boards/index.html.erb -->

<%= render 'search_form', q: @q, url: boards_path %>

q:@qurl: urlの部分は、ローカル変数に値を渡すことで汎用的に使えるようにしています。

_contもransackで用意されているメソッドで、検索したワードが含まれているレコードを取得するものです。
title_or_body_contのように_or_で繋ぐことでtitleカラムとbodyカラムにワードが含まれているか検索してくれます。

検索の仕方は他にも色々メソッドがありそれぞれ出来ることが違うので気になる方は調べてみてください。

最後に

検索機能はほぼ必須というくらい、どんなサービスでも見かけるものだと思います。
複雑なものになってくるとちょっとどうかわかりませんが、これくらいの検索機能ならransackを使えば簡単に実装できますね。
この記事が参考になれば幸いです。