20200625のRubyに関する記事は16件です。

【初学者手順書2】Ruby on Rails :Railsの雛形作成

初学者手順書1の続きです。
今回は、Railsの雛形を作成していきます。

Rails雛形作成

Railsアプリケーションを作成
ターミナル上、指定のディレクトリで実行

% rails _Ver.指定_ new アプリ名 -d RDBMS名(mysqlなど)

ファイルの文字コードの設定(必要な場合)

config/database.yml
# m4を削除
encoding: utf8

データベース作成

% rails db:create

不要なファイルが作成されないように追記(必要な場合)
rails gでコントローラを作成する場合テストファイルなどを自動で作るため

config/application.rb
# 省略
module アプリ名
  class Application < Rails::Application
    config.load_defaults 6.0
    config.generators do |g|
      g.stylesheets false
      g.javascripts false
      g.helper false
      g.test_framework false
    end
  end
end

GitHubコミットされたくないファイルを記載

.gitignore
public/uploads/*

READMEに、データベース設計やアプリの情報を記載していく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorial 第6版 学習まとめ 第10章

概要

この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・

出典
Railsチュートリアル第6版

この章でやること

ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。

ユーザーを更新する

ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。

編集フォーム

editページはURLに対象のユーザーのIDが含まれる
ex) users/1/edit

これを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。

  def edit
    @user = User.find(id: params[:id])
  end

こうすることで次に作成するフォームでモデルオブジェクトに@userオブジェクトを指定する。

edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

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

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

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

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

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>

フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。

またgravatarのリンク部にtarget="_blank"とあるが、このように記述することで新しいタブでリンク先を表示できる。

さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。

このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。

もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?メソッドで新規か既存か判別できるからである。

>> new_user = User.new
   (1.3ms)  SELECT sqlite_version(*)
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil>
>> user1 = User.first
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/...">
>> new_user.new_record?
=> true
>> user1.new_record?
=> false
>> 

実際のerbではform_withを使った際モデルオブジェクトに対してnew_record?```の結果を見て
postかpatchかを判定する。

最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>

演習

1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!

2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。

_form.html.erb
   <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

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

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

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

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

      <%= f.submit yield(:btn_text), class: "btn btn-primary" %>
    <% end %>
new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:btn_text, "Create my account") %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
  </div>
</div>
edit.html.erb
<% provide(:title, "Edit user") %>
<% provide(:btn_text, "Save changes") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>
  </div>
</div>

編集の失敗

ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)

    else
      render 'edit'
    end
  end

現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。

演習

1.失敗する。
image.png

編集失敗時のテスト

ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit

users_edit_test.rb
require 'test_helper'

class UserEditTest < ActionDispatch::IntegrationTest
  def setup 
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user) , params:{user:{name: "",
                                           email: "foo@bar",
                                           password: "foo",
                                           password_confirmation: "bar"}}
    assert_template 'users/edit'
  end
end

1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。

の順のテストになる。

演習

1.assert_select 'div.alert', "The form contains 4 errors."

TDDで編集を成功させる

今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。

機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。

先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)

  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name = "foo"
    email = "foo@bar.com"
    patch user_path(@user) , params:{user:{name: name,
                                           email: email,
                                           password: "",
                                           password_confirmation: ""}}

    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end

り確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・

出典
Railsチュートリアル第6版

この章でやること

ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。

ユーザーを更新する

ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。

編集フォーム

editページはURLに対象のユーザーのIDが含まれる
ex) users/1/edit

これを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。

  def edit
    @user = User.find(id: params[:id])
  end

こうすることで次に作成するフォームでモデルオブジェクトに@userオブジェクトを指定する。

edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

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

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

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

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

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>

フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。

またgravatarのリンク部にtarget="_blank"とあるが、このように記述することで新しいタブでリンク先を表示できる。

さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。

このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。

もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?メソッドで新規か既存か判別できるからである。

>> new_user = User.new
   (1.3ms)  SELECT sqlite_version(*)
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil>
>> user1 = User.first
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/...">
>> new_user.new_record?
=> true
>> user1.new_record?
=> false
>> 

実際のerbではform_withを使った際モデルオブジェクトに対してnew_record?```の結果を見て
postかpatchかを判定する。

最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>

演習

1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!

2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。

_form.html.erb
   <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

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

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

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

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

      <%= f.submit yield(:btn_text), class: "btn btn-primary" %>
    <% end %>
new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:btn_text, "Create my account") %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
  </div>
</div>
edit.html.erb
<% provide(:title, "Edit user") %>
<% provide(:btn_text, "Save changes") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>
  </div>
</div>

編集の失敗

ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)

    else
      render 'edit'
    end
  end

現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。

演習

1.失敗する。
image.png

編集失敗時のテスト

ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit

users_edit_test.rb
require 'test_helper'

class UserEditTest < ActionDispatch::IntegrationTest
  def setup 
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user) , params:{user:{name: "",
                                           email: "foo@bar",
                                           password: "foo",
                                           password_confirmation: "bar"}}
    assert_template 'users/edit'
  end
end

1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。

の順のテストになる。

演習

1.assert_select 'div.alert', "The form contains 4 errors."

TDDで編集を成功させる

今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。

機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。

先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)

  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name = "foo"
    email = "foo@bar.com"
    patch user_path(@user) , params:{user:{name: name,
                                           email: email,
                                           password: "",
                                           password_confirmation: ""}}

    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end

もちろんテストは失敗する。
まず、flashメッセージを実装していないこと。リダイレクトの指定をしていないこと。この二つが引っかかる。
そして一番大切な部分。パスワードの値を空にしているため、バリデーションに引っかかり、正常に更新できない。

前者の二つはこの行で実装する。

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'

    end
  end

この時点では@user.updateはパスワードが空欄でバリデーションに引っかかり、else文に分岐するため、
テストもうまく動かない。
この対策としてパスワードが空の時の例外処理を加える。
このような際にはallow_nil: trueというオプションを使うと便利。
これで空欄でもバリデーションにひっかからなくなる。
このオプションで存在性の検証を通過してしまうが、has_secure_passwordメソッド側のオブジェクト生成時の
存在性のバリデーションが働くため、新規作成時はnilをはじき、更新時はnilならパスワードを変更しないという
動作を実現できる。
さらにこのallow_nil: trueオプションを追加したことでモデルに定義したバリデーションと
has_secure_passwordメソッドのバリデーションが重複して同じエラーメッセージが表示される不具合も解決する。

演習

1.成功する
image.png

2.デフォルトのGravatar画像が代わりに表示される。
image.png

認可

Webアプリにおける認証はユーザーを識別すること。認可はユーザーの実行可能な操作範囲を管理すること。
今まで実装してきたupdate,editアクションでは大きな欠陥があり、
現在の状態ではどのユーザーがログインしていようとすべてのユーザーを編集できてしまう。
ナビゲーションバーのSettingリンクはログインしているユーザーのeditページを表示するが
直接URLに様々なユーザーのeditアクションを指定してしまえばアクセス可能だし、更新もできてしまう。

これはまずいので動作を正しいものに変える。
具体的には
未ログイン時はログインページに転送する+メッセージを表示。
ログイン済みだが別のユーザーにアクセスしようとしている場合はルートURLに転送する。

ユーザーにログインを要求する

Usersコントローラでbeforeフィルターを使ってedit,updateアクションが実行される前に
必ずログインを強制するよう実装していく。

  before_action :logged_in_user, only:[:edit,update]
  .
  .
  .
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

このように実装することでedit,updateアクションの実行前に必ずlogged_in_userメソッドが実行され
ログインしていない際にはフラッシュメッセージでログインを促すメッセージを表示し、
ログインページにリダイレクトする。

そして今の段階ではログインしていない状態でeditビューにアクセスするとログインページに
飛ばされてしまうようになったためテストは失敗する。

テストが通るようにuser_edit_test.rbではeditアクションにアクセスする前にログインするようにする。
log_in_asメソッドをテスト用に定義しているためそれをつかう。

これでテストはパスするようになる。だがbefore_actionの行をコメントアウトしてもテストではじかれない。
これは重大なセキュリティホールでテストではじかれなければまずいので
しっかりテストではじくよう修正していく。

  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_path
  end

  test "should redirect update when not logged in" do
    patch user_path(@user), params:{ user: {name: @user.name,  
                                            email: @user.email }}
    assert_not flash.empty?
    assert_redirected_to login_path
  end 

このようにテストを追加していくことで
edit,updateアクションを実行する前にlog_in_userが実行されているかを必ずテストするため
セキュリティホールをテストではじいてくれるようになる。

演習

1.newページやsignupアクションが実行できなくなりエラーとなる。

 FAIL["test_should_get_new", #<Minitest::Reporters::Suite:0x00007f1d1cf4dab8 @name="UsersControllerTest">, 0.06502773099964543]
 test_should_get_new#UsersControllerTest (0.07s)
        Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/login>
        Response body: <html><body>You are being <a href="http://www.example.com/login">redirected</a>.</body></html>
        test/controllers/users_controller_test.rb:10:in `block in <class:UsersControllerTest>'

 FAIL["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1cff74c8 @name="UsersSignupTest">, 0.08553676799965615]
 test_invalid_signup_information#UsersSignupTest (0.09s)
        expecting <"users/new"> but rendering with <[]>
        test/integration/users_signup_test.rb:12:in `block in <class:UsersSignupTest>'

 FAIL["test_valid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1d0494d0 @name="UsersSignupTest">, 0.09624041300003228]
 test_valid_signup_information#UsersSignupTest (0.10s)
        "User.count" didn't change by 1.
        Expected: 2
          Actual: 1
        test/integration/users_signup_test.rb:20:in `block in <class:UsersSignupTest>'

  9/9: [================================================================] 100% Time: 00:00:01, Time: 00:00:01

正しいユーザーを要求する

次はログインしていても本人でなければ編集できないようにしていく。
TDDで進めていく。

まずは別のユーザーでログインする状況を作るためにfixtureに2人目のユーザーを追加する。

users.yml
archer: 
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

続いて、テストで@other_userとしてログインし、
@userの更新を行うテストを書く。

  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get edit_user_path(@user)
    assert flash.empty?
    assert_redirected_to root_path
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch user_path(@user), params:{ user: { name: @user.name,
                                             email: @user.email}}
    assert flash.empty?
    assert_redirected_to root_path
  end

flashメッセージは特に表示せずルートURLに飛ばすだけなのでこのようなテストになる。

テストを書き、もちろんパスしないので
テストをパスさせるようにコードを書く。

具体的にはcorrect_userメソッドを作成し、edit,updateアクション実行前にユーザーがあっていなかったら
ルートURLに飛ばす処理を書く。

users_controller.rb
  before_action :correct_user, only:[:edit,:update]



  private
    def correct_user
      @user = User.find(params[:id])
      redirect_to root_url unless @user == current_user
    end

これでテストがパスするようになる。

最後にcurrent_user?メソッドを定義し、先ほど定義したcorrect_userメソッドに組み込む

    def correct_user
      @user = User.find(params[:id])
      redirect_to root_url unless current_user?(@user)
    end
  def current_user?(user)
    user && user == current_user
  end
演習

1.updateアクションを保護しなかった場合、editアクション(editページ)を経由せずcurlコマンドなどで直接
値を送った場合に更新できてしまうから。

2.editアクションのほうが簡単にテストできる。(実際にログインして別のユーザのeditパスを表示すればいい)

フレンドリーフォワーディング

さらにこの更新の機能を便利にする。具体的には
logged_in_userメソッドで未ログインのユーザーのeditページへのアクセスをはじいてログインページに飛ばした際
そのままログインすると問答無用でユーザー詳細ページ(show)に飛ばされてしまうが、
editページにアクセスしたくてログインしたのにshowページが表示されてしまうのは少々不便である。
これを改良しログインしたらeditページに飛ばしてくれるようにする(フレンドリーフォワーディング)

テストもこの通り実装すればいいので
未ログインでeditページにアクセスし、ログインしたらeditページにリダイレクトすることをチェックする。

test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name = "foo"
    email = "foo@bar.com"
    patch user_path(@user) , params:{user:{name: name,
                                           email: email,
                                           password: "",
                                           password_confirmation: ""}}

    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end

現時点で失敗するテストが書けたのでつぎはこのテストがパスするようにコードを書いていく。
リクエスト時のページを保存しておき、ログイン時にそこへリダイレクトする処理を書く。

sessions_helperにメソッドを定義する。

sessions_helper.rb
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
一時セッションにリクエスト先のURLを保存する処理を書いている。
この時GETリクエストだけを保存するようにしないと

万が一ログインしてフォームページににアクセスし、意図的に保存されたログイン情報のcookiesを削除した上で
フォームの内容を送信するとpost,patchなどのURLが保存されてしまう。
その状態で```redirect_back_or```メソッドを使うとリダイレクトでpost,patchなどを期待するURLに対してGETリクエストが
送られてしまい、エラーが発生する可能性が高い。
GETリクエストに絞ることでこういったリスクを回避できる。

```logged_in_user```メソッドに```store_location```メソッドを入れて、リクエスト先URLを保存し、
sessions_controllerのcreateアクションに```redirect_back_orメソッド```を入れることで、
ログイン時、もしセッションに保存されたURLがあればそちらにリダイレクトするようにする。

```rb:sessions_controller.rb
  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user&.authenticate(params[:session][:password])
      log_in(@user)
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_back_or @user
    else
      flash.now[:danger] = "Invalid email/password combination"
      render 'new'
    end
  end
users_controller.rb
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

ちなみにreturnやメソッドの最終行の直接呼出しがない限りは
リダイレクトはメソッドの最後で行われる。

上記の内容でテストはパスする。

演習

1.ログインしてeditページにリダイレクトした時点で保存したURLが消えていることを確認すればいい。

user_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_nil session[:forwarding_url] #ここを追加
    assert_redirected_to edit_user_url(@user)
    name = "foo"
    email = "foo@bar.com"
    patch user_path(@user) , params:{user:{name: name,
                                           email: email,
                                           password: "",
                                           password_confirmation: ""}}

    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email
  end

2.

(byebug) session[:forwarding_url]
"https://12b7e3b6aec94b45960b81560e233372.vfs.cloud9.us-east-2.amazonaws.com/users/1/edit"
(byebug) request.get?
true

最後に

だんだん章の最後にやることが増えてきたので
とりあえずまとめておく。

rails t
git add -A
git commit -m "Finish user edit, update, index and destroy actions"
git co master
git merge updating-users
git push
rails t 
git push heroku
heroku pg:reset DATABASE
heroku run rails db:migrate
heroku run rails db:seed

本番用DBはpg:reset DATABASEで行う。なお、間違いを防ぐためにDBをリセットするアプリ名の
入力を求められるので入力してリセット
それか--confirmオプションを使って
heroku pg:reset DATABASE -c アプリ名
としてもいい。

あとはheroku上でマイグレートとサンプルの追加を行って終わり。

前の章へ

次の章へ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails初心者から中級者までに伝えたい、Controllerのアンチパターン3つ

tl; dr

controllerでは特に、インスタンス変数の濫用はやめよう!

すべてのはじまり

ほとんど触ったことないプロジェクトに移籍させられ早1ヶ月、そのコードを知る熟練の先輩も去った荒廃の地で、私は今日も一人、Railsと激闘を繰り広げていた!
というわけで(?)、今回はcontrollerでやってはいけないアンチパターンを紹介していきたい。

クソコード1. インスタンス変数に入れればええやろ

これは初心者がやらかしがちな印象
こういうやつ

sample_controller.rb
class SampleController < ApplicationController
  def show
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = hoge.sugoku_kuso
    @hoge_kuso3 = Hoge.fetch_kuso
  end
end

......

こ☆ろ☆す☆ぞ

まず第一に、Railsのコントローラーは一つのディレクトリに入るものをだいたい一つのクラスで固めるので、言い換えれば複数のアクションのロジックが一つのクラスにまとまっている。なので例えば次のようなコードは日常茶飯事である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
  end

  def action2
  end
end

この状態で2つのアクションがインスタンス変数をたくさん使ったとしよう。それが以下

sample_controller.rb
class SampleController < ApplicationController
  def action1
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  def action2
    hoge = Hoge.new
    @hoge_kuso1 = hoge.sugoku_kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  ...
end

ついでに Hoge クラスはこう

hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

この状態の何が悪いのかというと、インスタンス変数をたくさん使ったことにより、コード全体でどの変数がどこに所属しているのかが分かりにくいことだ。例えば @hoge_kuso1 はクラス内スコープの変数であるので、もしかしたら他のところの定義とコンフリクトしてバグるかもしれない。それにクラス内検索しても複数の全然関係ない奴が引っかかる可能性もあるので、補足もしづらい。あんまりメリットはないのである。

解決策

単純な話、極力インスタンス変数を使わないことだ。具体的にはこう。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = Hoge.new
  end

  def action2
    @hoge = Hoge.new
  end

  ...
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  # 要素をモデルからアクセスできるようにして、controller上でインスタンス変数を使う可能性をを極力排除する
  def fetched_kuso
    @fetched_kuso ||= self.class.fetch_kuso
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

どういうことかというと、インスタンス変数に入れるのはモデル程度にしておいて、詳細なデータはモデル内に内包してしまおう、ということだ。これのメリットは2つ

  1. コントローラーがスッキリする
  2. ロジックが複数の場所に依らない

正直メリットしかないので、やらない手はないと思う。ちなみにたとえば複数のモデルに対して一括で値を取得したいんだけど、その場合はどうするの?ということについては

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoges = 10.times.map { Hoge.new }
    Hoge.set_kusos(@hoges)
  end
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  attr_accessor :fetched_kuso
  # 専用のセッター
  def self.set_kusos(hoges)
    hoges.each do |hoge|
      hoge.fetched_kuso = self.fetch_kuso
    end
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

とするなどして、モデル内にデータを保持するといい。とにかくコントローラーで無闇にロジックを書かないようにすることは大事である。

クソコード2. set_〇〇って書いとけば安泰やろ。其の壱

ダメです
例えばこう書く人はほとんどだと思う

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge, only: :action1

  def action1
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end
end

絶対10人に9人はこう書いてる。なぜならscaffoldでもこう書くのを推奨してるから。
じゃあこれの何がダメなのかというと、以下の2つ

  1. コードが追いにくい
  2. 引数を渡しにくい

まず1について

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge,  only: :action1
  before_action :set_hoges, only: :action2

  def action1
  end
  def action2
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end

  def set_hoges
    @hoges = [Hoge.new, Hoge.new]
  end
end

こう書かれた場合に、action1を確認した瞬間、あ、これは @hoge を使うんだな、ってなる人はいないだろう。
少なくとも、
action1がなにも定義されていない -> あ、before_actionが定義されているということはつまり -> やっぱり @hoge を使っていたか
となるのではないだろうか?
これはアハ体験としては適切な体験かもしれないが、コーディングに脳科学を持ち込まないでほしい。はっきり言ってストレスである。

また、 クソコード1でも述べたように、コントローラー内で使用されるインスタンス変数は所在がわかりにくくなりがちである。よってインスタンス変数を無闇に使うのはよろしくない。

次に2について

sample_controller.rb
class SampleController < ApplicationController
  before_action(only: :action1) do
    set_hoge('fuga')
  end

  def action1
  end

  private
  def set_hoge(fuga)
    @hoge = Hoge.new(fuga)
  end
end

こうしないと変数が渡せない。逆にこれだったら渡せるじゃん?という諸君。確かに渡せるが、これだとせっかく便利なskip_before_actionが利用できない。そこまでしてset_〇〇を使う必要ある?という気持ちでいっぱいである。

解決策

答えは単純である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    Hoge.new
  end
end

これであれば、インスタンス変数がロジック内に組み込まれているので、見た瞬間に @hoge を使うことがわかる。また引数を渡すのも容易である。とってもスマート、故にハッピー。

また、これだとredirect_toを書くのに手間がかかるじゃん!そっちはどうすんのさ!?っていうみなさんのために、以下のコードを授けよう

sample_controller.rb
class SampleController < ApplicationController
  class DataNotFound < RuntimeError; end
  rescue_from DataNotFound do
    redirect_to :root_path, flash: { error: 'データが見つかりませんでした' }
  end

  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    hoge = Hoge.new
    raise DataNotFound unless hoge
    hoge
  end
end

これであれば元々の機能を維持できるだろう。

クソコード3. set_〇〇って書いとけば安泰やろ。其の弐

ダメだって言ってんだろ!!!
場合にもよるけど、例えばヘッダーで使う値をこうやって作る人いるよね

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_header_de_tukau_yatu

  def set_header_de_tukau_yatu
    @header_info = 'void'
  end
end

これの問題点は「@header_info を利用する」ためには、「:set_header_de_tukau_yatuを呼び出す」必要があることを知っていなきゃいけないことだ。この場合、viewがほしいのは @header_info だけ、なのになぜその設定方法までこっちが把握してなきゃいかんの?っていう話

解決策

こういうときにどうすればいいのかというと helper_methodを使う。例えば以下

sample_controller.rb
class SampleController < ApplicationController
  helper_method :header_info

  def header_info
    @header_info ||= 'void'
  end
end

こうすることで、viewは header_info を呼び出すと、情報が得られる。ということを知っているだけでいい。わざわざ内部のロジックまで知る必要もないし、インスタンス変数もロジックに近い場所にある。とっても素晴らしい。

おわりに

この記事ではアンチパターンを3つしか紹介していないが、多分世の中にはもっとたくさんのアンチパターンがあると思う。しかし多くの場合、それは「可読性が下がる」「使い勝手が悪い」のいずれかないし両方を満たしているのではなかろうか?そしてその二つを生み出しがちな諸悪の根源、それはインスタンス変数の濫用である(暴論。今回本当に伝えたかったことはただ一つ。みなさんご唱和ください。

インスタンス変数の濫用はやめよう!!!!!

読了ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails初心者から中級者までに伝えたい、アンチパターン3つ

tl; dr

controllerでは特に、インスタンス変数の濫用はやめよう!

すべてのはじまり

ほとんど触ったことないプロジェクトに移籍させられ早1ヶ月、そのコードを知る熟練の先輩も去った荒廃の地で、私は今日も一人、Railsと激闘を繰り広げていた!
というわけで(?)、今回はcontrollerでやってはいけないアンチパターンを紹介していきたい。

クソコード1. インスタンス変数に入れればええやろ

これは初心者がやらかしがちな印象
こういうやつ

sample_controller.rb
class SampleController < ApplicationController
  def show
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = hoge.sugoku_kuso
    @hoge_kuso3 = Hoge.fetch_kuso
  end
end

......

こ☆ろ☆す☆ぞ

まず第一に、Railsのコントローラーは一つのディレクトリに入るものをだいたい一つのクラスで固めるので、言い換えれば複数のアクションのロジックが一つのクラスにまとまっている。なので例えば次のようなコードは日常茶飯事である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
  end

  def action2
  end
end

この状態で2つのアクションがインスタンス変数をたくさん使ったとしよう。それが以下

sample_controller.rb
class SampleController < ApplicationController
  def action1
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  def action2
    hoge = Hoge.new
    @hoge_kuso1 = hoge.sugoku_kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  ...
end

ついでに Hoge クラスはこう

hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

この状態の何が悪いのかというと、インスタンス変数をたくさん使ったことにより、コード全体でどの変数がどこに所属しているのかが分かりにくいことだ。例えば @hoge_kuso1 はクラス内スコープの変数であるので、もしかしたら他のところの定義とコンフリクトしてバグるかもしれない。それにクラス内検索しても複数の全然関係ない奴が引っかかる可能性もあるので、補足もしづらい。あんまりメリットはないのである。

解決策

単純な話、極力インスタンス変数を使わないことだ。具体的にはこう。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = Hoge.new
  end

  def action2
    @hoge = Hoge.new
  end

  ...
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  # 要素をモデルからアクセスできるようにして、controller上でインスタンス変数を使う可能性をを極力排除する
  def fetched_kuso
    @fetched_kuso ||= self.class.fetch_kuso
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

どういうことかというと、インスタンス変数に入れるのはモデル程度にしておいて、詳細なデータはモデル内に内包してしまおう、ということだ。これのメリットは2つ

  1. コントローラーがスッキリする
  2. ロジックが複数の場所に依らない

正直メリットしかないので、やらない手はないと思う。ちなみにたとえば複数のモデルに対して一括で値を取得したいんだけど、その場合はどうするの?ということについては

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoges = 10.times.map { Hoge.new }
    Hoge.set_kusos(@hoges)
  end
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  attr_accessor :fetched_kuso
  # 専用のセッター
  def self.set_kusos(hoges)
    hoges.each do |hoge|
      hoge.fetched_kuso = self.fetch_kuso
    end
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

とするなどして、モデル内にデータを保持するといい。とにかくコントローラーで無闇にロジックを書かないようにすることは大事である。

クソコード2. set_〇〇って書いとけば安泰やろ。其の壱

ダメです
例えばこう書く人はほとんどだと思う

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge, only: :action1

  def action1
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end
end

絶対10人に9人はこう書いてる。なぜならscaffoldでもこう書くのを推奨してるから。
じゃあこれの何がダメなのかというと、以下の2つ

  1. コードが追いにくい
  2. 引数を渡しにくい

まず1について

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge,  only: :action1
  before_action :set_hoges, only: :action2

  def action1
  end
  def action2
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end

  def set_hoges
    @hoges = [Hoge.new, Hoge.new]
  end
end

こう書かれた場合に、action1を確認した瞬間、あ、これは @hoge を使うんだな、ってなる人はいないだろう。
少なくとも、
action1がなにも定義されていない -> あ、before_actionが定義されているということはつまり -> やっぱり @hoge を使っていたか
となるのではないだろうか?
これはアハ体験としては適切な体験かもしれないが、コーディングに脳科学を持ち込まないでほしい。はっきり言ってストレスである。

また、 クソコード1でも述べたように、コントローラー内で使用されるインスタンス変数は所在がわかりにくくなりがちである。よってインスタンス変数を無闇に使うのはよろしくない。

次に2について

sample_controller.rb
class SampleController < ApplicationController
  before_action(only: :action1) do
    set_hoge('fuga')
  end

  def action1
  end

  private
  def set_hoge(fuga)
    @hoge = Hoge.new(fuga)
  end
end

こうしないと変数が渡せない。逆にこれだったら渡せるじゃん?という諸君。確かに渡せるが、これだとせっかく便利なskip_before_actionが利用できない。そこまでしてset_〇〇を使う必要ある?という気持ちでいっぱいである。

解決策

答えは単純である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    Hoge.new
  end
end

これであれば、インスタンス変数がロジック内に組み込まれているので、見た瞬間に @hoge を使うことがわかる。また引数を渡すのも容易である。とってもスマート、故にハッピー。

また、これだとredirect_toを書くのに手間がかかるじゃん!そっちはどうすんのさ!?っていうみなさんのために、以下のコードを授けよう

sample_controller.rb
class SampleController < ApplicationController
  class DataNotFound < RuntimeError; end
  rescue_from DataNotFound do
    redirect_to :root_path, flash: { error: 'データが見つかりませんでした' }
  end

  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    hoge = Hoge.new
    raise DataNotFound unless hoge
    hoge
  end
end

これであれば元々の機能を維持できるだろう。

クソコード3. set_〇〇って書いとけば安泰やろ。其の弐

ダメだって言ってんだろ!!!
場合にもよるけど、例えばヘッダーで使う値をこうやって作る人いるよね

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_header_de_tukau_yatu

  def set_header_de_tukau_yatu
    @header_info = 'void'
  end
end

これの問題点は「@header_info を利用する」ためには、「:set_header_de_tukau_yatuを呼び出す」必要があることを知っていなきゃいけないことだ。この場合、viewがほしいのは @header_info だけ、なのになぜその設定方法までこっちが把握してなきゃいかんの?っていう話

解決策

こういうときにどうすればいいのかというと helper_methodを使う。例えば以下

sample_controller.rb
class SampleController < ApplicationController
  helper_method :header_info

  def header_info
    @header_info ||= 'void'
  end
end

こうすることで、viewは header_info を呼び出すと、情報が得られる。ということを知っているだけでいい。わざわざ内部のロジックまで知る必要もないし、インスタンス変数もロジックに近い場所にある。とっても素晴らしい。

おわりに

この記事ではアンチパターンを3つしか紹介していないが、多分世の中にはもっとたくさんのアンチパターンがあると思う。しかし多くの場合、それは「可読性が下がる」「使い勝手が悪い」のいずれかないし両方を満たしているのではなかろうか?そしてその二つを生み出しがちな諸悪の根源、それはインスタンス変数の濫用である(暴論。今回本当に伝えたかったことはただ一つ。みなさんご唱和ください。

インスタンス変数の濫用はやめよう!!!!!

読了ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails routes(ルーティング)の順番の解説

自分用のまとめ

https://qiita.com/gawach/items/4e7460d06f70e3013eea
にてまとめられているが、私は違う理解の仕方をしている。
それを説明します。

⑴
  1 get "posts/:id" => "posts#show"
  2 get "posts/new" => "posts#new"
⑵
  3 get "posts/new" => "posts#show"
  4 get "posts/:id" => "posts#new"
⑶(プロゲートに書いてあった正しいコード)
    get "posts/index" => "posts#index"
    get "posts/:id" => "posts#show" 

⑴は間違いであり、⑵は実は正しいと決める、それを下記に示す。

上から順にコードを優先していくため、
:id以外の特定のURLが:idより先にくるようにする。
そしてそれら以外の余ったものが:idの対象となる。
上記のようなルールがあるため、
:idはposts/newのようなURLより先に記述されているとエラーになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】lazy_high_chartsを用いてグラフを作成する方法

目標

本の月間登録推移を1日ごとに見れるグラフを作成します。
ezgif.com-video-to-gif.gif

開発環境

・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

前提

下記実装済み。

Slim導入
Bootstrap3導入
Font Awesome導入
ログイン機能実装
投稿機能実装

実装

1.Gemを導入

Gemfile
# 追記
gem 'lazy_high_charts'
ターミナル
$ bundle

2.application.jsを編集

application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require highcharts/highcharts // 追記
//= require highcharts/highcharts-more // 追記
//= require_tree .

3.コントローラーを編集

books_controller.rb
def index
  @book = Book.new
  @books = Book.all
  # 追記
  days = (Date.today.beginning_of_month..Date.today).to_a
  books = days.map { |item| Book.where(created_at: item.beginning_of_day..item.end_of_day).count }
  @graph = LazyHighCharts::HighChart.new('graph') do |f|
    f.title(text: '本 月間登録推移')
    f.xAxis(categories: days)
    f.series(name: '登録数', data: books)
  end
end

① 今月1日から今日までの日付を取得し、変数へ代入する。

days = (Date.today.beginning_of_month..Date.today).to_a

で取得した日付内に作成された本の数を取得し、変数へ代入する。

books = days.map { |item| Book.where(created_at: item.beginning_of_day..item.end_of_day).count }

③ グラフを作成する

@graph = LazyHighCharts::HighChart.new('graph') do |f|
  f.title(text: '本 月間登録推移') # タイトル
  f.xAxis(categories: days) # 横軸
  f.series(name: '登録数', data: books) # 縦軸
end

4.ビューを編集

books/index.html.slim
/ 追記
= high_chart('sample', @graph)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud9がメモリー足りないってよ: Railsチュートリアル備忘録

Cloud9がメモリー足りないってよ: Railsチュートリアル備忘録

Cloud9に以下のメッセージが出てコンソールが固まる事態に

This environment is running low on memory (132 MB of virtual memory left).
To free up memory, kill processes by choosing "Tools > Process List" in the menu bar.
To migrate this environment to one that has more memory, ...

解決法

EC2インスタンスを再起動する

経緯

Railsチュートリアル13章進行中のこと
トリガーもよくわからいのですがTestを走らせると

... Missing helper file helpers/microposts_helper.rb ...

といったエラーのが出るように

Ruby on RailsでAbstractController::Helpers::MissingHelperError:Missing helper file が発生したときの対処法 - Qiita

bundle updateするといいよとの記事があったため
試してみたところ

Cloud9上で上記通知がでるように...

ググってみてもあまり情報がなく
いっそ試してみたかったDockerを利用して環境再構築...なんて考えましたが

結局EC2インスタンスを再起動することで2つとも解決しました

インスタンスの再起動 - Amazon Elastic Compute Cloud

いまさらですがオンラインIDEであるCloud9は
AWSの一部であるEC2サービスで提供される
仮想サーバー(これがEC2インスタンス)上で動いているわけですね

インスタンスという言葉がすっと頭に馴染むようになってきたこととに喜び、
AWSの全体像について視野が広がったという学びでした

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gem simple calendarでのカレンダー実装と条件分岐

概要

現在DMMWEBCAMPというプログラミングスクールで学習しておりまして、
ポートフォリオ作成にあたりイベントテーブルを作成してカレンダーに表示させました。
これから作成する方のお役に立てればと思います。

完成図

スクリーンショット 2020-06-25 19.05.35.png

参考にさせて頂いた記事

【Rails】Simple Calendarを使ったカレンダー表示
https://qiita.com/mikaku/items/0971442a7308dc669122

github
https://github.com/excid3/simple_calendar

事前準備

イベントテーブル

スクリーンショット 2020-06-25 18.01.19.png

・data_and_timeがスタートする日時
・meetingfinishtimeが終了する日時

イベント参加テーブル

※投稿いいね機能の延長です。
スクリーンショット 2020-06-25 18.11.29.png

アソシエーション

models/event.rb
class Event < ApplicationRecord
 has_many :event_participates, dependent: :destroy
end
models/event_participate.rb
class EventParticipate < ApplicationRecord
 belongs_to :event
end

gem install

gem 'simple_calendar', '~>2.0'
$ bundle install

Simple calendarのViewインストール

$ rails g simple_calendar:views

・views/simple_calendar内に3種類のカレンダーの雛形が出来ます。

application.scss(CSS)への記載

stylesheets/applecation.scss
*= require simple_calendar
*= require_tree .
*= require_self

start_time end_timeの定義

models/event.rb
class Event < ApplicationRecord
  has_many :event_participates, dependent: :destroy

  def start_time
    self.date_and_time
  end

  def end_time
    self.meetingfinishtime
  end
end

このgemではstart_timeとend_timeの定義が必要です。(特にstart_time)
作成しているカラムが最初からその名前であれば良いのですが、違うカラム名なのであればモデルの方で定義しましょう。ただし日付と時刻を扱うのでデータ型は恐らくdatetime型でなければならないと思います。
最初定義せずに実装したところエラーが出ました。

githubではscaffoldを使用して最初から上記名称でカラムを作成しておりました。

カレンダーの表示

私のポートフォリオ

views/events/index.html.erb
</div>
        <div class= 'calender'>
            <%= month_calendar events: @events do |date, event| %>
                <%= date %>

                <% event.each do |event| %>
                <br>
                <% if user_signed_in? %>
                    <% if event.user_id == current_user.id %>
                        <i class="fa fa-circle notification-circle" style="color: red;"></i>
                    <% end %>
                <% end %>
                    <% if event.event_participates.where(user_id: current_user).exists? %>
                        <span class="participate">
                          <%= link_to event.title,event_path(event) %>
                        </span>
                    <% else %>
                         <span class="other">
                          <%= link_to event.title,event_path(event) %>
                        </span>
                    <% end %>
                <% end %>
            <% end %>
        </div>

カレンダーの表示とイベントのタイトルだけであれば以下のみで良いです。

<div class= 'calender'>
            <%= month_calendar events: @events do |date, event| %>
                <%= date %>

                <% event.each do |event| %>
                  <%= event.title %>
                <% end %>
            <% end %>
</div>
controllers/events_controller.rb
def index
    @events = Event.all
end

コントローラーで定義しているEvent.allを@eventsに格納しカレンダーで表示しています。
<%= month_calendar events: @events
この構文の中のevents:は恐らく変えるとエラーが出ますが、インスタンス変数は情報が格納されていればどんなものを使用しても大丈夫だと思います。

現在は先ほどインストールしたmonth_calendarを使用していますが、他に2つテンプレートが用意されているので3種類から使用することが出来るようです。

私は参加(participate)アクションを取っていればbackground-colorをオレンジに、
主催しているイベントであれば赤丸のアイコンを付けたかったので以下のような条件分岐を
カレンダー内で行いました。

  <% if event.user_id == current_user.id %>
     <i class="fa fa-circle notification-circle" style="color: red;"></i>
  <% end %>

イベントテーブルの中のuser_id(つまりイベント作成者)が自分であればイベントタイトルの前にfont awesomeのアイコンを表示というものです。インライン要素のものであればここは何でも良いと思います。

次に

 <% if event.event_participates.where(user_id: current_user).exists? %>
      <span class="participate">
        <%= link_to event.title,event_path(event) %>
      </span>
<% else %>
      <span class="other">
        <%= link_to event.title,event_path(event) %>
      </span>
<% end %>

1行目の <% if event.event_participates.where(user_id: current_user).exists? %>で
eventに参加(participete)するユーザーの中に自分(current_user)が存在しているかどうかを探しています。
見つかればclass participate、そうでなければclass otherとすることで、
表示する情報は同じでもclass名が違うという状態にすることが出来ました。

後はCSSで色を変えていくだけなので、皆さんのお好みのようにしていただければと思います。

ちなみにmonth_calenderのviewは以下です。

views/simple_calendar/_month_calendar.html.erb
<div class="simple-calendar">
  <p class="carendar-title"><%= "#{Date.today.month}月のイベントスケジュール" %></p>

  <div class="calendar-heading" data-turbolinks="false">
    <!--%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %-->
    <!-- <span class="calendar-title"><%= t('date.month_names')[start_date.month] %> <%= start_date.year %></span> -->
    <!--%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %-->
    <i class="fa fa-circle notification-circle" style="color: red;"><span>主催イベント</span></i>
    <span class="calendar-info">参加予定</span>
  </div>

  <table class="table table-striped calendar-table">
    <thead>
      <tr>
        <% date_range.slice(0, 7).each do |day| %>
          <th><%= t('date.abbr_day_names')[day.wday] %></th>
        <% end %>
      </tr>
    </thead>

    <tbody>
      <% date_range.each_slice(7) do |week| %>
        <tr>
          <% week.each do |day| %>
            <%= content_tag :td, class: calendar.td_classes_for(day) do %>
              <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
                <% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
              <% else %>
                <% passed_block.call day, sorted_events.fetch(day, []) %>
              <% end %>
            <% end %>
          <% end %>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

私のポートフォリオの仕様で上の部分をコメントアウトしています。
コメントアウトの場所に本来であれば次月や前月へのリンクが出てきます。
細かいところであれば柔軟に変えれそうです。

まとめ

最初はfullcalendarでの実装を目論んだのですが、理解が及ばず諦めて今回こちらのgemを使用しました。イベントタイトルがstart_timeとend_timeの間で毎回出てしまうことを改善できればと思ったのですが私の力不足で難しかったです。
要素のclass名に特に意味はありません。スペルを所々間違えてますが、、
このぐらい簡単なカレンダーでも見せ方次第で私のものよりもっといいものになると思うので是非やってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails MVC周りのリファクタリング方法例まとめ

何も考えずにやりたいように記述していると増えていくコード量、下がる可読性……
なるべく簡潔に纏められるよう、MVCそれぞれで使える手段を初学者なりにまとめました。

MVCの役割をおさらい

MVC 役割
Model アプリケーションで扱うデータの操作/加工
View ユーザへの情報表示、ユーザからの入力の受け取り
Controller ModelやViewとの情報のやり取りをする

この役割を理解し、それぞれの役割に相応しい処理を記述していく。
役割を考えず好きに各ファイルに記述していると、メンテナンスも難しくなってきてしまう。
例えば、Modelに記述すべき処理をControllerに記述してしまうとFat Controller
(コードが大量に書かれて膨れ上がったController)になるなどの問題が発生しやすくなる。

Controllerのコード量を減らす方法

Modelに記述する。

上記のMVCの各役割を考えて、データの処理・加工を担うメソッドはModelに記述する。

訂正前

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.order(created_at: :desc).limit(10)
  end
end

訂正後(Modelに移動)

Modelクラスに対して直接呼び出せるようクラスメソッドとして定義。

app/models/post.rb
class Post < ApplicationRecord
  def self.latest(number)
    order(created_at: :desc).limit(number)
  end
end

Controllerでメソッドを呼び出す

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.latest(10)
  end
end

クラスメソッドでなく、scopeを使うことも出来る。

app/models/post.rb
class Post < ApplicationRecord
  scope :latest, -> (number = 10){order(created_at: :desc).limit(number)}
end
app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.latest
  end
end

Modelのコード量を減らす方法

Concernを使う

複数のModelに同じ処理が繰り返し記述されている場合などに、Concernを使って共通処理を切り出すことが出来る。

app/models/post.rb
scope :latest, -> (number = 10){order(created_at: :desc).limit(number)}
def hoge
  puts "hogehoge"
end
app/models/comment.rb
scope :latest, -> (number = 10){order(created_at: :desc).limit(number)}
def hoge
  puts "hogehoge"
end

app/models/concernsにファイルを作成・共通処理を記述。

app/models/concerns/common_module.rb
module CommonModule
  extend ActiveSupport::Concern
  included do
    scope :latest, -> (number = 10){order(created_at: :desc).limit(number)}
    def self.hoge
      puts "hogehoge"
    end
  end
end

各Modelでincludeすることで共通処理が使用できる。

app/models/post.rb
include CommonModule
app/models/comment.rb
include CommonModule

バリデーションを切り出す

長くなりがちなバリデーションの記述をvalidatorに切り出すことが出来る。
appディレクトリ下にvalidatorsディレクトリを作成し、バリデーション用のファイルを作成する。

app/validators/post_validator.rb
class PostValidator < ActiveModel::Validator
  def validate(record)
    prohibited_words = ["baka","aho"]
    if prohibited_words.any?{ |word| record.content.include?(word) }
      record.errors.add(:content, "禁止ワードが含まれています。")
    end
  end
end

Model側に追加で記述する。

app/models/post.rb
validates_with PostValidator

コールバックを切り出す

コールバックもModelから切り出すことが出来る。
app/callbacksディレクトリを作成し、コールバック用のファイルを作成する。
(クラス名はコールバック名と同じにすること)

app/callbacks/post_callback.rb
class PostCallback
  def before_create(post)
    post.title = "No Title" if post.title.blank?
  end
end

Model側に追加で記述する。

app/models/post.rb
before_create PostCallback.new
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails×Heroku環境でSSL化したときhttp->httpsにリダイレクトさせる方法

config/environments/production.rb
  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true # ←ここがコメントアウトされてるので解除するだけ

公式ドキュメント
https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6〜の環境構築にて、データベースにMySQLを使いたいそんなあなたに。

そもそもMySQLとmysql2は、別物

MySQLは皆さんご存知、データベースなんですが、
mysql2ってのはRails専用のgemのことで、データベースとRailsを繋いでくれる架け橋と解釈しています。

mysql2のエラーについて

$ bundle install --path vendor/bundle

An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

$ gem install mysql2 -v '0.5.3'

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.

そして、さらにbundle installしても、、、という「お、俺のそばに近寄るなァァァ!!」と無限ループの住民になってしまったあなた。

私もそこの住人でしたが、安心してください、脱出できます笑

解決方法

結論、mysql2をインストールしたことを無かったことにすれば良いのです。

ここで、出てくるのがおなじみGemfileと、Gemfile.lockです。

Gemfileはいうならば、ゲームソフトを買いに行く時の「買うものリスト」で、
Gemfile.lockは、「領収書」です。

bundle installするときは、この買う物リストにあるソフトを見てgemをインストールするのですが、
今エラーの原因になってるのは、「領収書にあるゲームソフトをまた買いに行くの?」ってなってる状態なんです。

ゲームソフトを遊ぶ用、観賞用、保存用としている方なら別ですが笑
それは、おいときまして。

ということで、買ったことを無かったことにてして、新シリーズ版を買いなおしましょう。という話です。
とはいってもやることは、2つ。

$ gem uninstall mysql2
Gemfile.lock
mysql2と名のつくものを削除

そして、買いなおしましう!

$ bundle install --path vendor/bundle
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby Minitestを試す

目的

Railsのデフォルトのテスティングフレームについてアウトプットする。

テストコードのひな形

Minitestを使ったテストコードのひな形は下記の通り。

sample_test.rb
require 'minitest/autorun'

class SampleTest < Minitest::Test
  def test_sample
    assert_equal 5, 'こんにちは'.length
  end
end

・1行目でMinitestに必要なライブラリを呼び出す。
・SampleTestクラスにMinitest::Testクラスを継承させる。
・SampleTestクラス内に、実行対象となるテストメソッドを定義する(test_sample)
という流れで作成できる。

assert_equalは検証メソッドで、第1引数には予想される値、第2引数は検証する値を入れる。
したがって、test_sampleメソッドは、「'こんにちは'という文字列の文字数」が「5文字」であるかをテストしている。
結果として等しくなるので、テストはパスする。
test.png
・runs: 実行したテストメソッドの件数(test_sampleのみなので1件)
・assertions: 実行した検証メソッドの件数(assert_equalを1回使用)
・failures: 検証に失敗したテストメソッドの件数
・errors: 検証中にエラーが出たテストメソッドの件数
・skips: skipメソッドにより実行をスキップされたテストメソッドの件数
を表している。

検証メソッドの例

http://docs.seattlerb.org/minitest/Minitest/Assertions.html
MinitestのAPIドキュメントを参考にいくつか検証メソッドを使ってみる。

hello_test.rb
require 'minitest/autorun'

def hello(name)
  puts "#{name}さん、こんにちは!"
end

class HelloTest < Minitest::Test
  def test_hello
    assert_output("Bobさん、こんにちは!\n") { hello("Bob") }
  end
end

assert_outputを使うと、標準出力した内容をテストできる。

multiple_of_eleven_test.rb
require 'minitest/autorun'

def multiple_of_eleven?(number)
  number % 11 == 0
end

class MultipleOfElevenTest < Minitest::Test
  def test_true
    assert multiple_of_eleven? 121
  end
  def test_false
    refute multiple_of_eleven? 13
  end
end

assert,refuteメソッドを使うと、真偽をテストできる。

後書き

Minitestを使うと簡単にテストを実装できるので、活用してみる。
今度はRspecについてもアウトプットしたい。

参考文献

プロを目指す人のためのRuby入門
伊藤 淳一[著]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

slimの始め方

slimのインストール

アプリのフォルダ内でslimをインストールする。

gem install slim

これで拡張子が.html.slimのslimファイルを、みなさんが普段使っている.html.erbとして変換されるようになるそうです。

gem install html2slim

これでhtml.erb → html.slim に変換させることができるようになるそうです。

bundle exec erb2slim app/views app/views

これでエラーが出た場合は、gemfileに下記の2文を追加してbundle installします。

gem 'slim-rails'
gem 'html2slim'
bundle install

これでviewフォルダに元からあったerbファイルを削除するそうです。
※拡張子が.erbのファイルがない場合は、飛ばしてよし。

bundle exec erb2slim app/views app/views -d

今後自動的にslimファイルを作成されるようにするためには 、
config/application.rbにあるconfigを以下のようにslimを指定すればOK。

config/application.rb
module App
  class Application < Rails::Application
    config.generators.template_engine = :slim #slimに変更
  end                                                                                                                                                                     
end

slimファイルを表示させる前の準備

rails g controller tweets

viewフォルダのなかに、tweetsフォルダができています。
そのなかにindex.html.slimというファイルを新しく作ります。
そのファイルの中に分かりやすいように何か書いておきましょう。

views/tweets/index.html.slim
Hello, world!

コントローラーの中にindexアクションを追加しておきます。

controllers/tweets_controller.rb
class ArchivesController < ApplicationController
    def index
    end
end

次にルーティングです。

config/routes.rb
Rails.application.routes.draw do
  root "tweets#index"
end

サーバーを再起動して、ページを表示

サーバーの再起動を忘れないようにしましょう。
再起動しないままだと、「ArchivesController#index is missing a template for request formats: text/html」というようなエラーが出ます。

http://localhost:3000/

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【バリデーション】rails 今日の日付以降を指定する方法

【ゴール】

日付入力の際に、今日以降の日付しか入力できないように
バリデーションをかける

【メリット】

■ UXの向上
■ validate理解度向上

【開発環境】

■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7

【実装】

※任意のメソッド名でvalidateを作成
※メソッドの処理を追記
 ①「return if start_day.blank?」 で空かどうか判断
 ②「if start_day < Date.today」で今日の日付を取得 & 条件分岐 & 比較
 ③「errors.add」でエラー文もついでに定義
 

model/item.rb


validate :date_before_start
validate :date_before_finish



  def date_before_start
    return if start_day.blank?
    errors.add(:start_day, "は今日以降のものを選択してください") if start_day < Date.today
  end

  def date_before_finish
    return if finish_day.blank? || start_day.blank?
    errors.add(:finish_day, "は開始日以降のものを選択してください") if finish_day < start_day
  end

以上!!

【合わせて読みたい】

■RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c

■ 【ERROR メッセージ表示】rails 部分テンプレートでいつでも使える 簡易版
https://qiita.com/tanaka-yu3/items/63b189d3f15653cae263

■ 【Date.today】 今日だけの情報を取り出す方法 rails
https://qiita.com/tanaka-yu3/items/741711bd743b80eda51a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyのSeleniumでAttributeの値を変更したい

概要

RubyのSeleniumでスクレイピングの処理を書いているときにどうしても属性値の変更を行いたくなる場面があります。UIの振る舞いを忠実に追跡したい場合では使いませんが、UIの挙動がわかっている場合に面倒な処理を飛ばして目的のものをすぐ欲しいという場合に有効です。

方法

RubyでAttributeへのアクセスはGetAttributeがあるにも関わらずSetAttributeが見つかりません。(探し方の問題かもしれませんが)GetではDOMに変更を与えませんが書き込みでは予期せぬ振る舞いを起こす可能性から用意されていないのではと勝手に推測し、今回は「execute_script」を使用します。

execute_script.rb
#ドライバーの初期化
client = Selenium::WebDriver::Remote::Http::Default.new
driver = Selenium::WebDriver.for :chrome, http_client: client

#<div id="mw-content-text" lang="ja" dir="ltr" class="mw-content-ltr">
#jaをenに変えたい

#対象になる要素の取得
target_element = driver.find_element(:class_name, 'mw-content-ltr')
driver.execute_script("arguments[0].setAttribute('lang', 'en');", target_element)

参考

https://www.rubydoc.info/gems/selenium-webdriver/0.0.28/Selenium%2FWebDriver%2FDriver:execute_script

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで投稿編集時に予め所属カテゴリにチェックをつける方法

多対多の関係のPostとCategoryをもつテーブルに置いて、Post編集時に既存のカテゴリに予めチェックボックスをつける方法。form_with model: @postの形式を使わないと{checked:}オプションを使えないので少しハマった。

前提:多対多のテーブル構成を実現する

以下を参考にテーブル構成してる前提です。ただし投稿のtitleは本記事ではnameになってます。

Railsで投稿とカテゴリの紐付け機能を実装する

collection_check_boxesの引数に{checked:}オプションを追加

前段の多対多のテーブルを作成している前提だと、@post.category_idsで所属カテゴリを配列で取得できるらしい。それに.map(&:to_param)してやることでチェックが付く。この&:to_paramというのはいつも固定で、@postcategory_idsのようにテーブルやモデルの名称によって変わるものではない。

app/views/posts/edit.html.erb
<%= f.label :category, 'カテゴリ' %>
      <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name, { checked: @post.category_ids.map(&:to_param) }) do |category| %> 
        <%= category.label do %>
          <%= category.check_box %>
          <%= category.text %>
        <% end %>
      <% end %>

https://stackoverflow.com/questions/30766766/how-to-pre-populate-collection-check-boxes-in-an-edit-form

もしform_with model: @postで「undefined method 'post_path'」とエラーが出る時

form_with model: @post, url: posts_create_path doみたいな書き方をしていると{checked:}オプションは使えない。以下の回答のコメント欄にあるようにresoucesを使わないといけない。
https://teratail.com/questions/200474

本題:最終的なコード

routes.rb
Rails.application.routes.draw do
  resources :posts, only: [:new, :create, :edit, :update]
end

posts_controller.rb
class PostsController < ApplicationController

  def  new
    @post = Post.new
  end

  def create
    @post = Post.create(post_params)
    redirect_to('/posts/index')
  end

  def show
    @post = Post.find(params[:id])
  end

  def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id]).update(post_params)
    redirect_to("/posts/index")
  end

  private

  def post_params
    params.require(:post).permit(:name, category_ids: [])
  end

end
app/views/posts/edit.html.erb
<div class="main posts-index">
  <div class="container">
    <%= form_with model: @post do |f| %>

    <p>
      <%= f.label :name, 'name'%>
      <%= f.text_field :name %>
    </p>

    <p>
      <%= f.label :category, 'カテゴリ' %>
      <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name, { checked: @post.category_ids.map(&:to_param) }) do |category| %> 
        <%= category.label do %>
          <%= category.check_box %>
          <%= category.text %>
        <% end %>
      <% end %>
    </p>

    <%= f.submit '変更を保存' %>
  <% end %>
  </div>
</div>

その他

Railsはマイグレーションファイルの生成やdb:migrateを少しでもミスると、特に本番デプロイ時にDBがおかしくなったりするから気をつけてね。一度でもdb:migrateしたら、もうそのマイグレーションファイルは使えないし、schemaを直接書き換えたりしてもダメだよ。

こちらも参考になりそう?
【Rails】form_with/form_forについて【入門】

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む