20191009のRailsに関する記事は24件です。

【50日目】コメントの削除機能、多対多のアソシエーションの設定

コメントの削除機能

destroyアクションの作成

コメントコントローラーにdestroyアクションを記載していきます。

comments_controller.rb
def destroy
  comment = Comment.find(params[:id])
  comment.delete
  redirect_to comment.board, flash: {notice: 'コメントが削除されました' }
end

コメント削除ボタンの作成

コントローラーでアクションを定義したら、viewを作成します。
link_toの第二引数に渡すパスを調べるために、http://localhost:3000/rails/info/routesを確認しますと、
deleteアクションのパスはcomment_pathとなっています。
よって下記のようにリンクを作成します。

_comments.html.erb
<span><%= link_to '削除', comment, method: :delete, data: { confirm: '削除してもよろしいでしょうか?'} %></span>
# 「link_to 'リンク名', リンク先へのURL又はパス, method: :メソッド名」で指定したメソッドの指定したパスに飛べる'リンク'を作成します。
# methodオプションを指定しない場合、第二引数のパスはGETメソッドのものを参照します。
# data-confirm属性を設定すると、リンクを踏んだ際に確認のダイアログボックスを表示させます。

編集機能にもエラーメッセージを表示する。

まず前提として、updateアクションには下記のとおりフィルタリングされています

boards_controller.rb
before_action :set_target_board, only: %i[show edit update destroy]

# 中略

private

def set_target_board
  @board = Board.find(params[:id])
end

これによって、@boardには該当のidのパラメータが渡されております。

その上でupdateアクションを下記のようにします。

boards_controller.rb
def update
  if @board.update(board_params)
    flash[:notice] = "「#{@board.title}」の掲示板を編集しました。"
    redirect_to @board
  else
    redirect_to edit_board_path, flash: {
      board: @board,
      error_messages: @board.errors.full_messages
    }
  end
end

これによって、パラメータが保存できた場合は編集したことを示すメッセージが表示されて、掲示板のshowにリダイレクトします。
バリデーションエラーでboardが保存できなかった場合は、編集画面にリダイレクトしてエラーメッセージのリストを表示します。
又この時、リダイレクト後も掲示板のフォームには編集時に入力したパラメータは保持されます。

多対多のリレーションの考え方

例えば掲示板にタグをつけることを考えます。
ここでは簡略化するため、各テーブルはidとnameのカラムのみを持ちます。

boardsテーブル

id name
1 1日目
2 2日目
3 3日目

tagsテーブル

id name
1 ruby
2 SQL
3 初心者

このとき、例えば
1日目はruby, 初心者
2日目はSQL
3日目はruby, SQL, 初心者
とタグ付けしたかったりした場合、この関係を表そうとすると下記のようになります。

id board tag tag tag
1 1日目 ruby 初心者
2 2日目 SQL
3 3日目 ruby SQL 初心者

でもこれではカラムの数が事前に定められない他、カラムに空白が生まれてしまいます。

そこで「中間テーブル」を用いて、各id同士を繋げて表します。

board_id tag_id
1 1
1 3
2 2
3 1
3 2
3 3

このように表現することで、例えば
board_id = 1 である「1日目」には、
tag_id = 1 である「ruby」と
tag_id = 2 である「初心者」がタグづけされていることがわかります。

では以下でこのアソシエーションをどのように実装するかを見ていきます。

多対多アソシエーションの実装

まず最初に中間テーブルとtagsテーブルのモデルを作成します。

テーブルの作成

tagsテーブル

docker-compose exec web bundle exec rails g model tag name:string

中間テーブル

docker-compose exec web bundle exec rails g model board_tag_relation board:references tag:references

tagがnullであることを

db/migrate/xxxxxx_create_tags.rb
t.string :name, null: false

書き換えたら、マイグレーションを実行します。

docker-compose exec web bundle exec rake db:migrate

モデルのアソシエーションの設定

中間モデルの確認

中間モデルは最初からboardモデルとtagモデルにbelongs_toによってassociateされています。
これによって中間モデルはboard_idとtag_idをカラムに持つことになり、上記で示したような2つのidを結びつけることが可能になります。

app/model/board_tag_relations.rb
class BoardTagRelation < ApplicationRecord
  belongs_to :board
  belongs_to :tag
end

tagモデルの修正

タグから掲示板を関連づける設定をする。

tag.rb
class Tag < ApplicationRecord
  has_many :board_tag_relations
  has_many :boards, through: :board_tag_relations # throughオプションによって中間テーブルを経由して複数のboardを持つことを示している。
end

boardsモデルの修正

掲示板からタグを関連づける設定をする。

board.rb
class Board < ApplicationRecord
has_many :comments
has_many :tag_board_relations
has_many :tag, through: :tag_board_relations

validates :name, presence: true, length: { maximum: 10 }
  validates :title, presence: true, length: { maximum: 30 }
  validates :body, presence: true, length: { maximum: 1000 }
end

次回はアソシエーションのdependentオプションを作成していきます。

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

Rails datetime_selectのエラー対応

datetime_selectエラーがでた

new.html.erb
<div>新規プロジェクト</div>
<%= form_for(@project) do |f| %>
  <div><%= f.label :title %>
  <div><%= f.text_field :title %></div>
  <div><%= f.label :state %></div>
  <div><%= f.text_field :state %></div>
  <div><%= f.label :limit_date %></div>
  <div><%= f.datetime_select :released_at,
              start_year: 2000, end_year: Time.current.year + 1,
              use_month_numbers: true %></div>
  <div><%= f.submit %></div>
<% end %>

上記のnewのviewを実装後にlocalhost:3000/projects/newをリクエストしたら下記のエラーが出た。

ターミナル
ActionView::Template::Error (undefined method `map' for "translation missing: ja.date.order":String
Did you mean?  tap):
     5:   <div><%= f.label :state %></div>
     6:   <div><%= f.text_field :state %></div>
     7:   <div><%= f.label :limit_date %></div>
     8:   <div><%= f.datetime_select :released_at,
     9:               start_year: 2000, end_year: Time.current.year + 1,
    10:               use_month_numbers: true %></div>
    11:   <div><%= f.submit %></div>

解決策

ja.ymlを作成すると解決する

config/locales/ja.yml
ja:
  date:
    order:
      - :year
      - :month
      - :day

もしくはi18nを無効化しても良いということが調べたら載ってました。

参考: https://jangajan.com/blog/2014/09/12/perfect-rails-i18n/

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

A server is already running の対処法

Railsでアプリを作成中にa server is already runningが出てlocalサーバが起動しなくなってしまった。

already runningってことはサーバが閉じられてないってこと?

調べてみると
Railsプロジェクト/tmp/pids/server.pidのserver.pidを削除して直るらしい。

server.pidはサーバを起動毎に作成されているようなのでサーバが閉じている時にserver.pidファイルがpidsファイルの中にあるのがそもそもおかしいみたい。

server.pidを削除して新たにサーバを立ち上げ直し。

server.pidファイルが新たに作られる。

解決!

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

routingでtypoしてるとrspecでActionController::UrlGenerationErrorがでる件

概要

routingでtypoしていると、railsは無視して解釈してアクセスできるようにしてくれますが、rspecはそのコントローラーのテストでエラーを返します。

例えば

config/routes.rb
resources :examples, excpet: %i[new update destroy] 

exceptexcpetのように書く。

こんな感じで解決される。

ちなみにrails routesはこんな感じになる

examples      GET         /examples(.:format)            examples#index {:excpet=>[:new, :update, :destroy]}
              POST        /examples(.:format)            examples#create {:excpet=>[:new, :update, :destroy]}
new_example   GET         /examples/new(.:format)        examples#new {:excpet=>[:new, :update, :destroy]}
edit_example  GET         /examples/:id/edit(.:format)   examples#edit {:excpet=>[:new, :update, :destroy]}
example       GET         /examples/:id(.:format)        examples#show {:excpet=>[:new, :update, :destroy]}
              PATCH       /examples/:id(.:format)        examples#update {:excpet=>[:new, :update, :destroy]}
              PUT         /examples/:id(.:format)        examples#update {:excpet=>[:new, :update, :destroy]}
              DELETE      /examples/:id(.:format)        examples#destroy {:excpet=>[:new, :update, :destroy]}

routingではtypoは解釈されず、resourcesは全て解釈される。

そしてcontrollerを作りアクセスすると

examples_controller.rb
class ExamplesController < ApplicationController

  def index
  end
{"method":"GET","path":"/examples","format":"html","controller":"ExamplesController","action":"i
ndex","status":200,"duration":4429.18,"view":4345.9,"db":3.32,"backtrace":null,"host":"localhost
","message":"[200] GET /terms_of_uses (TermsOfUsesController#index)"}

そして普通にアクセスできてしまう。

しかしこの状態でrspecのcontroller_specを作成して実行すると

require "rails_helper"

RSpec.describe ExamplesController, type: :controller do

  describe "GET #index" do
    it "returns a 200 status code" do
      get :index
      expect(response.status).to eq(200)
    end
  end
end
spec/controller/exmaples_spec.rb
  1) AnnouncementsController GET #index responds successfully with an HTTP 200 status code
     Failure/Error: get :index

     ActionController::UrlGenerationError:
       No route matches {:action=>"index", :controller=>"announcements"}
     # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:35:in `block in process'
     # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:102:in `catch'
     # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:102:in `_catch_warden'
     # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:35:in `process'
     # ./spec/controllers/youtube_channel_platform_histories_controller_spec.rb:58:in `block (3 levels) in <top (required)>'

このように解釈してくれない。

結論

typoするやつ許すまじ(ブーメラン)

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

何をやってもRails Serverが切れなかった

何をやってもRails Serverが切れなかった話

いつも通りrails severを立ち上げると

=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Exiting

と表示される。

とりあえずkillコマンドをググる

定番のコマンドを試しまくってもダメ...
ターミナルを停止して立ち上げ直したり、エディターを再起動してもダメ...

(killコマンド一覧)
lsof -wni tcp:3000
ps ax | grep puma + kill -9 PID
ps ax | grep rails + kill -9 PID
kill -9 $(lsof -i tcp:3000 -t)

結果

ただのRoute設定ミスでした...

よくエラー文を見てみると下にplease check your routes の文が書かれていました。

=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
Exiting

(中略)

in `check_part': Missing :controller key on routes definition,
 please check your routes. (ArgumentError)

まさかRouteの設定ミスでもserverがExitingと表示されるとは思わなかった。
長いエラー文でもしっかり見ないといけないなと勉強になりました。

色々試してダメな方はエラー文見直すかルーティング確認するといいかもしれません。

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

メモ(form・パスワード・redirectとrenderの違い)

form helperについて

form_forとform_tagの二種類ある
特定のモデルを編集したいときform_for
編集の前提となるモデルが存在しないときform_tagを使う(検索フォーム)

DBに入る要素を限定した場合、データの検証は行わなくても良いのか?

パスワード関連の開発

gemfileにbcryptの記載・bundle install
+
password_digestモデルの設置

has_secure_passwordが使用可能に
→password_digestに暗号化したパスワードを保存できるように
→仮想属性passwordと、password_confirmation が使えるように。この二つが一致しているか?を検証するためのバリデーションも追加
→authenticateメソッドが使用可能に。(authenticateは上記のpasswordとpassword_confirmationで入力されたパスワードが、password_digest一致しているとUserオブジェクトを渡す、間違っているとfalse)

redirectとrenderの違い

redirectは呼び出すViewのみ指定

redirect_toはHTTPリクエストを送る(基本はGET)

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

RailsのmigrateをAWS Fargateに移行した時の話

前提

  • ECSでRailsアプリが起動されていること
  • migrateはfargateでやってなかった
  • テスト環境でやること

作業内容

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/ecs_run_task_fargate.html
これ見ながら作業する。実際は、コマンドでやると何をしているのかよくわからないので、まずはGUIでやる。把握した後にコード化するというのが良い。そうすることで、全てのオプションやらパラメータの世界が見えてくる。

念の為、新規DBを作成

t2.microなどで良い
何らかのエラーが出たときの切り分けにしたい。
インスタンスが作れたら、アプリケーションデプロイ用のタスクにDBの環境変数を設定する(System Managerが使えるなら ECS containerから valueFrom で呼び出せるのでjson自体をgit管理できる。AWSでマネージドされているのが良い)

taskを定義する

実際は、コマンドなどを付与したりするのでENTRYPOINTで設定している。
これは、Initial,Migrate,Seedそれぞれリビジョンで分けるだけで良い。Initial,Seed,Migrateの順で作る。
※なお、実際の運用ではInitialやらSeedは使わない。これは、今回新規でRDSを立ち上げたので必要になっただけ。LATESTリビジョンだけがあれば良い。

key value
ネットワークモード awsvpc
互換性が必要 FARGATE
タスクメモリ(GB) 0.5GB
タスクCPU(vCPU) 0.25 vCPU

containerの環境設定(Initial)

key value
エントリポイント bundle,exec,rails,db:create
作業ディレクトリ /app(アプリがある場所)
  "containerDefinitions": [
    {
      "entryPoint": [
        "bundle",
        "exec",
        "rails",
        "db:create"
      ],
      "workingDirectory": "/app"
    }
  ]

containerの環境設定(Migrate)

key value
エントリポイント bundle,exec,rails,db:migrate
作業ディレクトリ /app(アプリがある場所)
  "containerDefinitions": [
    {
      "entryPoint": [
        "bundle",
        "exec",
        "rails",
        "db:migrate"
      ],
      "workingDirectory": "/app"
    }
  ]

containerの環境設定(Seed)

key value
エントリポイント bundle,exec,rails,db:seed,a=b
作業ディレクトリ /app(アプリがある場所)
  "containerDefinitions": [
    {
      "entryPoint": [
        "bundle",
        "exec",
        "rails",
        "db:seed",
        "a=b"
      ],
      "workingDirectory": "/app"
    }
  ]

taskを実行する

  • VPCを設定、subnetを設定
  • ネットに繋がるものを設定しておく

2つエラーが発生した

  • メモリ不足エラー → メトリクスをチェックして、スペックの問題ならスペックを上げる
    • コンテナインスタンスのリソースから、メモリ使用量を見る
  • ECRからイメージをpullしてこれず、エラー → インターネットにつながるネットワークを設定する

結果を見る

  • PROVISIONING→PENDING→RUNNING→STOPのようなフローだったような気がする
  • タスクのStoppedの中でログを確認する。
  • Migrateタスクならば、以下のようなログが吐かれているだろう。
2019-10-09 18:16:45D, [2019-10-09T09:16:45.290941 #1] DEBUG -- : [1m[35m (572.8ms)[0m [1m[35mCREATE DATABASE "test_database" ENCODING = 'unicode'[0m
2019-10-09 18:16:45Created database 'test_database'
  • 実際にアプリケーション側でも確認する。
  • 問題がなければCodeDeployやCIなどで連携する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル 第5章<復習>

第5章の復習メモです。
個人的に重要と思ったことを書きます。
調べたことや、知っていたことも含めて書きます。

Bootstrap

Twitterが作成したフロントエンドのフレームワーク。
デザインが予め用意されているので、いい感じのデザインを素早く実装できる。

railsで使うには、Gemfileにbootstrap-sassを追加する。

source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bootstrap-sass', '3.3.7'  # ←追加
.
.
.

追加後、$ bundle installを忘れずに実行する。

また、Bootstrapを読み込ませるため、
CSSファイルには以下を追記する。

~.scss
@import "bootstrap-sprockets";
@import "bootstrap";

補足
railsでは、generate controllerした時、
ビューと一緒に、対応するCSSファイルが作られる。
今回は、簡略化のため、全てのCSSを一つにまとめた物として、
app/assets/stylesheets/custom.scssを作成した。
(カスタムCSSというらしい)

部分テンプレート

ビューファイルのコードの冗長化を防ぐため、
共通する部分を、別ファイルに記載できる。
これを部分テンプレートと呼ぶ。

ファイル名は、
app/views/layouts/_<任意の名前>.html.erb
の形式にする必要がある。

呼び出す際は、renderメソッドを用いて、
<%= render 'layouts/<任意の名前>' %>
と記載する。

<例>
部分テンプレート

app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

呼び出す側

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %>  # ←ここで呼び出している
  </head>
・
・

以下も参考にさせていただきました。
https://qiita.com/taca10/items/dd4a0eae6864e2bdbf3a

アセットパイプライン、Sass

アセットパイプライン

以下を参考にさせていただき、学習しました。
https://qiita.com/hogehoge1234/items/9a94ebc93c5f937502cd

Sass

CSSの強化版(?)で、

  • ネスト
  • 変数の使用

ができるみたい。書き方等、詳細は省略。

リンクの貼り方

ビューファイルでリンクを貼る場合、link_toメソッドを使う。

<%= link_to "文字列", "URL" %>

ルーティングの変更、名前付きルート

ルーティングの変更

generate controllerコマンドでコントローラ等を作成すると、
ルーティングが自動生成される。

config/routes.rb
Rails.application.routes.draw do
  get  'static_pages/about'
end

この場合、ルート/static_pagesにGETリクエストが来ると、
static_pagesコントローラの、aboutアクションにルーティングされる。
即ち、コントローラ名とアクション名に対して、URLが固定されている。

これを、以下のように書き換えることで、URLを自由に決めることができる。

config/routes.rb
Rails.application.routes.draw do
  get  '/about',   to: 'static_pages#about'
end

上の例では、ルート/aboutにGETリクエストが来ると、
static_pagesコントローラの、aboutアクションにルーティングされる。

名前付きルート

railsでは、config/routes.rbに記載したURLに対して、
ルート以降の相対パス、ルートを含めた絶対パスを表す変数が
それぞれ用意され、格納される。
<例>

config/routes.rb
Rails.application.routes.draw do
  get  'static_pages/about'
end

の場合

ルーティングを変更すると、変数名も更新される。
<例>

config/routes.rb
Rails.application.routes.draw do
  get  '/about',   to: 'static_pages#about'
end

の場合

統合テスト

テストについては、以下を参考にさせていただき、学習中です。
https://qiita.com/duka/items/2d724ea2226984cb544f

復習記事を書いてみると、理解不足がとても実感できます・・・

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

Railsのバリデーションで特定の値が入って欲しくない場合

はじめに

3回目の投稿です。
分からないことだらけで勉強中なので、アウトプットの練習にQiitaで投稿しています。
何か間違いなどあれば教えてください。

Validation機能

 エンドユーザーから入力された値は、まず「正しくないこと」を前提に、アプリは実装されるべきです。善意であるか悪意であるかに関わらず、ユーザーとは間違える生き物であるからです。
 (中略)もっとも、このような検証機能を一から実装するのは、なかなか面倒なことです。
しかし、Active ModelのValidation機能を利用することで、(たとえば)必須検証や文字列検証、正規表現検証のように、アプリでよく利用するような検証処理をシンプルなコードで実装できるようになります。

format

formatは、Active Modelで利用できる検証機能のうちの一つで、「正規表現パターンに合致しているか否か」を検証できるものです。

with

VALID_EMAIL_REGEX =  /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i #メールアドレスフォーマットの検証(完璧な正規表現ではない)
validates :email, format: { with: VALID_EMAIL_REGEX }

「正規表現パターンに合致しているか」を検証する

without

validates :email, format: { without: <入ってほしくない値の正規表現> }

without以下の正規表現パターンに合致していないこと」を検証する

おわりに

withoutの方はRailsのリファレンスに載っていませんでした。フォームのバリデーションで、特定の文字をはじく機会があったので存在を知りました。
最初はwithに正規表現の^を使っていたのですが上手くいかず、withoutを使うことでイメージ通りの実装が出来ました。

参考

Rails 4でモデルのバリデーションまとめ
Ruby on Rails5 アプリケーションプログラミング(書籍)

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

【Rails5】ドロップダウンリストの実装(コントローラー編)メルカリクローンサイトの作成

背景

カリキュラムの過程でご存知メルカリのクローンサイトを作りました。
その際に、ヘッダーメニューにあるドロップダウンリスト(ハンバーガーメニュー?)を実装したのでその記録です。

仕上がりはこんな感じです。
Image from Gyazo

DB構造など

今回、メルカリのサイトをクローンするにあたり、カテゴリーは、単一テーブル継承にすることで実装しています。

例えば、以下のような構造をしています。

id category parent_id
1 レディース
2 メンズ
13 トップス 1
14 ジャケット 2
115 Tシャツ 13
116 ダウンジャケット 14

この例だと、レディース>トップス>Tシャツ、 メンズ>ジャケット>ダウンジャケットという階層になります。
ちなみにビューで値を呼び出すときは、@category.childrenや@category.parentで子要素や親要素を呼び出すことが可能。

むむっ、カテゴリーの数多くない??

さて、実装に入りましょう。
色々な記事を参考にさせていただくと、ドロップダウンはCSSだけでいけることが判明。(後日別記事書きます)
じゃぁhtmlでカテゴリー名を全部入れればOKでしょ?

いや、待て待て。

カテゴリー名何個あんのよ
Image from Gyazo

親、子、孫合わせて
1,325!!

うん、多いね。

クラス名とかも一緒に書くから全部書くと4,000行の大作になっちゃうじゃない。

スマートじゃないね。
これ地道にやるならプログラミングやってる意味ないじゃん。

DBにデータはあるんだし、なんとかそこから引っ張ってこれないんか??

面倒だし繰り返し処理しちゃおうぜ

じゃぁ該当するカテゴリー名を変数に入れてrenderをeachさせればいいんじゃね?

まずは親カテゴリ。
親カテゴリはDBから呼び出さずにビューに記載。そして子カテゴリの呼び出し(@category_children1)

views/product/_header.html.haml
 %ul.header__menu-box--left__parents
    %li.header__menu-box--left__parents__parent
      = link_to "レディース", category_path(id: 1)
      %ul.header__menu-box--left__children
         = render partial: "header_child", collection: @category_children1, as: "category"

続いて子カテゴリ。そして孫カテゴリーの呼び出し(category.children)

views/product/_header_child.html.haml
%li.header__menu-box--left__children__child
  = link_to "#{category.category}", category_path(id: category.id)
  %ul.header__menu-box--left__grand-children
    = render partial: "header_grand-child", collection: category.children, as: "children"

さらに孫カテゴリ

views/product/_header_grand-child.html.haml
%li.header__menu-box--left__grand-children__grand-child
  = link_to "#{children.category}", category_path(id: children)

また、コントローラー側ではこのような処理がされています。

products_controller.rb
def set_categories
    @categories = Category.where(params[:id])
    @category_children1 = Category.where(parent_id: 1)
    @category_children2 = Category.where(parent_id: 2)
    @category_children3 = Category.where(parent_id: 3)
    @category_children4 = Category.where(parent_id: 4)
    @category_children5 = Category.where(parent_id: 5)
    @category_children6 = Category.where(parent_id: 6)
    @category_children7 = Category.where(parent_id: 7)
    @category_children8 = Category.where(parent_id: 8)
    @category_children9 = Category.where(parent_id: 9)
    @category_children10 = Category.where(parent_id: 10)
    @category_children11 = Category.where(parent_id: 11)
    @category_children12 = Category.where(parent_id: 12)
    @category_children13 = Category.where(parent_id: 13)
  end

よし、早速動かしてみよう!!!

おやおや、、、
確かに動くんだけど、リンクをクリックしたらエラーが。。。
読み込むテンプレートないじゃん!って怒られている
Image from Gyazo

部分テンプレートはちゃんとあるし、Missingな訳ないんだけどな

あれ、よーく見ると、ビューを読み込もうとしてるのってcategory_controllerじゃん
product_controllerじゃないのね。。。

そうか、部分テンプレートが同じcategoryフォルダにないから読めないのね。
次のように変更しましょ。

= render partial: "products/header_child"

こうすれば違うフォルダの部分テンプレートも読めるようになるね。

じゃ更新して、、、
Image from Gyazo
あれれ? あれれ?
今度は子と孫のカテゴリーが出なくなったぞ。。。

ページごとで読み込むコントローラーが違う

ページごとに読み込むコントローラーが違うため、それぞれのコントローラーで変数の設定が必要になるのか。。。
全部のコントローラーに上のあれを書くのは流石に可読性が悪くなるし、
DRYの法則に逆らうね

だるいわ〜

なんとか共通化したりできないの??

共通のメソッドにしてしまう

ありました、ちゃんとそういう機能。

参考記事
Ruby on RailsでConcernsを利用してControllerの処理を共通化する

早速以下のように書きます。

controllers/concerns/common_actions.rb
module CommonActions

  extend ActiveSupport::Concern

  def set_categories
    @categories = Category.where(params[:id])
    @category_children1 = Category.where(parent_id: 1)
    @category_children2 = Category.where(parent_id: 2)
       #中略
    @category_children12 = Category.where(parent_id: 12)
    @category_children13 = Category.where(parent_id: 13)
  end

end
必要なコントローラー.rb
  include CommonActions
  before_action :set_categories, only: [必要なアクション]

  #最初に読み込む必要があるため、最初に追記します。

でもこれ、コントローラー全部に書き込むのもどうかと思うのでこうしてみました。

application_controller.rb
class ApplicationController < ActionController::Base
  include CommonActions
  before_action :set_categories
   #中略
end

こうすることで、全部のコントローラーに記述する必要がなくなりました!!

少しはいいの作れたかな〜
もっといい方法があればご教授くださいまし。

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

【個人開発】至高の技術選定

はじめまして、ダンと申します。

日々情報収集をしている中で、
「エンジニアとしての市場価値を高めるために重要となる技術」だったり、
「最先端のweb系企業で使われている注目の技術」に関する情報が多く、
「良いプロダクトを速く作る技術」を求めている私向きの情報ではないと感じることが多くなってきました。

個人開発や、小規模プロジェクトの際に利用すると良い技術について、現在の雑感をまとめていこうと思います。

前提

本来、技術選定は作りたいサービスが明確にあってこそのものです。
本記事は「webアプリの個人開発や小規模プロジェクトで力を発揮しやすい技術は何か」という基準で書いていきます。

私はいわゆるサービス志向エンジニアで、技術そのものよりも「その技術を使って何ができるか」の方に強い関心があります。

また技術力は他のみなさんと比較するとかなり低いです。
プログラミング歴はもうすぐ2年になりますが、本格的な実務経験はありません。
PHPで書いたサイトをレンタルサーバーに公開して1年半ほど運用したり、コーディングやWordPress関係の仕事をいくつか受けたことがある程度です。
その他、いろんな技術のチュートリアル的なものにちょこちょこ手を出してます。
あと、最近AWS SAAを取得しました。

今後は自分で本格的なWebサービスを作って、販売して行きたいと考えていて、技術情報はある程度追っている、といった状態です。

前置きが長くなりました。
以下、私が積極的に使っていこうと思っている技術になります。

HTML、CSS

HTML、CSSが書けなければ始まりません。
CSSは直接書くのではなく、Sassで書いてコンパイルする方が良いです。すごく書きやすくなります。
はじめてSassを使った時は感動しました。学習コストもかなり低いので、使わない手はありません。

また、CSSフレームワークはBootstrapを使うことが多いです。
定番で情報量も多いですし、これといった不満もないので。ついでに名前も好きです。

Javascript

Javascriptの基本的な文法に関しても、必ずどこかのタイミングで必要になってくるので、学習する必要があります。
フレームワークに関しては、jQueryを使うか、ReactやVueなどを使うかの選択があります。
この点については色々と考えましたが、現状ではjQueryを使うのが良いのではないかと考えています。
理由としては以下の通りです。

SPAに興味が持てない

SPAのメリットとして、サクサク動く、表現が豊かになるなどがあげられますが、実装できる機能自体が大幅に増えることはなさそうだと感じています。
表示速度は通信技術の発達に伴ってどんどん上がっていくでしょうし、表現に関してもHTMLの進化で改善されていく気がします。(限界があるとは思いますが)
また、サーバーサイドレンダリング(SSR)をしっかりやらないと、SEOが弱くなってしまうとの話を聞いたことがあります。
普通のSEOを実現するために、工数を増やすのはあまり気持ちの良いことではありません。
しかもSSRはなかなか学習コストが高いみたいです。

学習コスト

前述のSSRの件もそうですが、全体的にjQueryの方がサクッと使える感じがします。
React等のメリットとして「複雑なコードをわかりやすい形で書ける」というのがあるようですが、それが特に力を発揮するのは人数の多いプロジェクトのような気がしています。
逆に、シンプルな動きをつける時のコードを見比べると、jQueryの方がスッキリしている気がしました。

情報量、ライブラリの数

歴史が違う分、情報量やライブラリの数ではjQueryが圧倒しています。
React等の情報はこれからどんどん増えていくと思われますが、React、Vue、Angularと3つの人気フレームワークが乱立している状況では、増え方もこれまでのjQueryと比較すると緩やかなのかな、と思っています。

ネイティブアプリについて

Reactが書けるようになると、ReactNativeというフレームワークを使って、スマホ用のネイティブアプリが比較的簡単に(元のコードを活かしながら)作れるというメリットがあります。
これについて、個人的にスマホのネイティブアプリにそこまで興味がないので、それだけのためにReactを使うのは違うな、と思っています。
あとスマホのネイティブアプリについてはあんまり調べてないので、よくわかってません。
webviewがどんどん進化してるらしいですね。

Ruby on Rails

サーバーサイドはやはりRuby on Railsかなって結論です。
そもそもRails自体が少人数のスタートアップでのプロダクト開発用に作られたフレームワークらしいです。
疎結合を犠牲に、速さと綺麗さの両立を追求して作られたとのこと。

最近はすごい勢いで人気が落ちているらしいですが、言語自体の問題というより
- 他のモダンな技術の登場
- 大規模サービスのRails離れ(Railsは大規模サービスにはあまり向いていないらしい)
- Railsに対する飽き
などから、「イケてる感」がなくなってきたことが原因なんじゃないかと個人的に推測しています。

Ruby on Railsは、ライブラリや情報量、学習コストなど、どこを取っても小規模プロジェクトにはもってこいの技術なんじゃないかと思っています。

ちなみに、Node.jsを使ってサーバーサイドもフロントエンドもJavaScriptを使うと良いのでは?って考えもありましたが、別にSPAを作りたい訳ではないので、JavaScriptをゴリゴリ書くことにこだわる必要もないと思ってやめました。

大半のロジックはRuby on Railsで書いて、必要な時にだけJavaScript及びjQueryを使うのが良いのではないかと思っています。

Heroku

インフラはもうHerokuかなと思ってます。インフラに工数をかけたくないです。多少割高になっても、パフォーマンスが落ちても、コスト、リスク、時間とかを総合的に考えたらHerokuだろうと。
個人的にインフラ周りの勉強が好きじゃないので、EC2とかで自前のインフラを構築することが必須になるサービスはそもそもやりたくないと思ってます。

Lambda、SQSなど

実際にほとんど使ったことがないのですが、LambdaとかSQS、SES、CloudFront、S3のような便利な外部サービスは積極的に活用したいと思っています。
AWS SAAを取得して1ヶ月ということもあり、AWSのサービスばかり挙げてしまいましたが、AWS以外のサービスでも便利なものはどんどん活用していきたいです。
どんなサービスがあって、何ができるのかをある程度頭に入れるようにして、必要に応じて勉強していきます。

必要かどうかわからないこと

Docker

Docker、使うのが常識になっているみたいですけど、どうなんですかね。
ドットインストールのチュートリアルを半年くらい前にやったっきりなので、Dockerに対する理解がかなり浅いです。
- 複数人で開発する時に、開発環境のセットアップが楽になる
- 開発環境と本番環境の差分を減らすことができる
というあたりはざっくりと理解しているのですが、
- Herokuを使う場合にも利用するのが一般的なのか
- Gitのように、開発環境をそのまま丸ごと本番環境にデプロイできたりしないのか
- アプリケーションとDBは別のコンテナにするもの?判断の基準は?
などなど、「何言ってんだこいつw」と思われてしまうかもしれないレベルの疑問がたくさんあります。
1つでもわかる方がいらっしゃいましたら教えてください!

CD/CI

HerokuとGithubを連携させて、Githubの更新を検知して自動でデプロイする、という動きは超簡単に作れます。
CircleCIやGithubActionsを使うと、何が嬉しいのでしょうか。
テストというものを書いたことがないため、その辺りを学習するとわかるのかもしれません。勉強します。

終わりに

以上が、個人開発の技術選定に関する現状の考えです。

少しでも違和感を感じた箇所があれば、どんどん指摘してください。
ちょっとした感想も、否定的なコメントも大歓迎です。
みなさんのご意見、感想を受けて理解を深めていきたいと考えています。
よろしくお願いいたします!

参考

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

Rails6 のちょい足しな新機能を試す92(config.disable_sandbox 編)

はじめに

Rails 6 に追加された新機能を試す第92段。 今回は、 config.disable_sandbox 編です。
Rails 6 では、 rails console --sandbox を実行したときに、エラーメッセージを表示して終了するオプション config.disable_sandbox が追加されました。
本番環境などで、 rails console --sandbox したときにトランザクションログが大きくなりすぎて、メモリが不足してサービスがダウンしてしまうことを防ぐ目的で導入されたようです。

Ruby 2.6.4, Rails 6.0.0 で確認しました。

$ rails --version
Rails 6.0.0

プロジェクトを作る

$ rails new rails_sandbox
$ cd rails_sandbox

config.disable_sandbox を設定する

今回は、 config/environments/development.rb で設定します。

config/environments/development.rb
Rails.application.configure do
  ...
  config.disable_sandbox = true
end

rails console を実行する

--sandbox オプションつきで、 rails console を実行してみます。

$ bin/rails c --sandbox
Running via Spring preloader in process 48
Error: Unable to start console in sandbox mode as sandbox mode is disabled (config.disable_sandbox is true).

エラーメッセージが表示されて実行できないことが確認できます。

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try092_console_sandbox

参考情報

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

Pryでpbcopy的なことをする

経緯

pryって便利ですよねえ。
と言いつつ「pryのコピーってどうするの・・・?」とずっと思ってました。
まあ、もちろん普通のコピペのやり方であれば無難に出来るんですけど長文の結果が出てきたときにコピーするの大変じゃないですか・・・。
pbcopyどこなの・・・という気持ちを抑えながら(´ε` )
なので、とりあえず簡易的にコピペできるコマンドを書きました!

やりたいこと

  • 最後に実行したコマンドをコピー
  • 最後に実行したコマンドの結果をコピー
  • 最後に実行したコマンドと結果をコピー

コマンド

  • ということで、下記コマンドを .pryrc にぺぺっと貼ってください。
  • それで、pryを立ち上げてaliasに設定したコマンドを実行するとうまくいくと思います。
def pbcopy(str)
  IO.popen('pbcopy', 'r+') {|io| io.puts str }
  _pry_.output.puts text.green("--- Copy to ClipBoard ---")
  _pry_.output.puts str
end

Pry.config.commands.command "copy-history", "History copy to clipboard" do |n|
  pbcopy _pry_.input_ring[n ? n.to_i : -1]
end

Pry.config.commands.command "copy-result", "Last result copy to clipboard" do
  pbcopy _pry_.last_result
end

Pry.config.commands.command "copy", "Copy to clipboard" do |str|
  unless str
    str = "#{_pry_.input_ring[-1]}#=> #{_pry_.last_result}\n"
  end
  pbcopy str
end

Pry.config.commands.alias_command 'co', 'copy'
Pry.config.commands.alias_command 'ch', 'copy-history'
Pry.config.commands.alias_command 'cr', 'copy-result'

実行結果

pry(main)> 1+1+1
=> 3
pry(main)> ch
--- Copy to ClipBoard ---
1+1+1
# ペーストすると 1+1+1 がコピーされていることが分かる

pry(main)> 1+1+1
=> 3
pry(main)> cr
--- Copy to ClipBoard ---
3
# ペーストすると 3 がコピーされていることが分かる

pry(main)> 1+1+1
=> 3
pry(main)> co
--- Copy to ClipBoard ---
1+1+1
#=> 3

# ペーストすると 
# 1+1+1
# #=> 3
# がコピーされていることが分かる

ちなみに

_pry_.input_ring の中身はこうなっているらしいです。bufferから値を取得してるのかな。

pry(main)> 1+1+1
=> 3
pry(main)> 2+2+2
=> 6
pry(main)> 3+3+3
=> 9
pry(main)> _pry_.input_ring
=> #<Pry::Ring:0x00007fe7b36b0308 
@buffer=[nil, "1+1+1\n", "2+2+2\n", "3+3+3\n", "_pry_.input_ring\n"], 
@count=5, 
@max_size=100, 
@mutex=#<Thread::Mutex:0x00007fe7b36b02e0>>

一応、chをこう使うこともできます。引数を与えてあげるとindexに基づいた値を取得してくれる。
最新の値は -1 から始まって -1 ずつ古くなっていきます。
(Enter押すと "" 空文字がBufferに入っていく)

# 上記の状態から
pry(main)> ch -2
--- Copy to ClipBoard ---
3+3+3

Pry#input_array is deprecated

input_array を使ってたんですけど推奨されてないようなので input_ring を使用しています。

warning: method Pry#input_array is deprecated. Use Pry#input_ring instead

pryのwikiはこちらです。

まとめ

これでめちゃくちゃ長い値も簡単にコピーできるようになって嬉しい・・・!

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

君はRailsの「Yay! You’re on Rails!」の画面がどこで記述されているか知っているか?

完全にあおりタイトルでごめんなさい。

結論

ここにあります。
「Yay! You’re on Rails!」で全文検索したら出てきました。
僕にとっては意外な場所でした。

/Users/hoge/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのテスト環境でのいろいろなエラーでつらい

railsのテスト環境でやったときのいろいろなエラーの詰め合わせの記事です。
タイトルが抽象的ですみません。

rails s -e test

2019-10-06 18:13:28 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
=> Booting Puma
=> Rails 5.2.3 application starting in test 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: test
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

http://0.0.0.0:3000/
へアクセス。

Puma caught this error: No route matches [GET] "/" (ActionController::RoutingError)
# 以下、略

こんな、エラがー起こる。

config/routes.rb
root 'rails/welcome#index'

を追加する。
「Yay! You’re on Rails!」のおなじみの画面が出て安心する。

ちなみにこのページのファイルはこのパスにあるようだ。

/Users/hoge/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb

curlで叩いてみる

curl localhost:3000/blogs/index/

Puma caught this error: PG::UndefinedTable: ERROR:  relation "blogs" does not exist
LINE 1: SELECT "blogs".* FROM "blogs"
                              ^
: SELECT "blogs".* FROM "blogs" (ActiveRecord::StatementInvalid)
# 以下、略。

なんだかすごい。エラーが出ている。
落ち着いてみる。

冒頭に
Puma caught this error: PG::UndefinedTable: ERROR: relation "blogs" does not existとある。
データベースがなんかおかしい?

単にrails sだと正常に返ってくる。

DBがないのか?
test用のDBを作成する。

rails db:create RAILS_ENV=test

2019-10-06 17:55:03 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
Database 'todo_slim_test' already exists

DBはあるようだ。
test用にmigrateしなくてはいけないのか?

 $ rails db:migrate RAILS_ENV=test

2019-10-06 17:55:36 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
== 20190818133341 CreateBlogs: migrating ======================================
-- create_table(:blogs)
   -> 0.0645s
== 20190818133341 CreateBlogs: migrated (0.0646s) =============================

ついでにseedsもやっておく。

 $ rails db:seed RAILS_ENV=test
2019-10-06 17:55:50 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

再度やる。

$ rails s -e test
2019-10-06 17:55:58 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
=> Booting Puma
=> Rails 5.2.3 application starting in test 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: test
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
2019-10-06 17:56:05 +0900: Rack app error handling request { GET /blogs/index }
#<AbstractController::ActionNotFound: The action 'show' could not be found for BlogsController>

なんだか、まだなにかしなくてはいけないようだ。
developではこんなことを言われないので、DBがおかしいのかと疑ってみる。
テスト環境のDBの状態を見に行く。

rails db RAILS_ENV=test

DEPRECATION WARNING: Passing the environment's name as a regular argument is deprecated and will be removed in the next Rails version. 

Please, use the -e option instead. (called from <top (required)> at /Users/hoge/product/ruby_sandbox/rails-blog-slim/bin/rails:9)
config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:

  * development - set it to false
  * test - set it to false (unless you use a tool that preloads your test environment)
  * production - set it to true

-eオプションつけてやってと言われた。

dbを初期化する。
migrateもする。

rails db:migrate:reset
rails db:migrate
rails db -e test

つないで、DBができていることを確認した。

$ curl localhost:3000/blogs/index
[{"id":1,"title":"今日のできごと","article":"ごはんを食べて寝た","created_at":"2019-10-06T09:02:16.207Z","updated_at":"2019-10-06T09:02:16.207Z"}][

値が返ってきた。
いろいろ試した結果、たぶんDBが良くなかったんだろうという結論。

雑なポエム的なまとめ

test環境はdevelopと結構違ってつらい。
動きがおかしかったらDBをresetしてtest用に作り直す。

参考サイト
Ruby - rails db:migrate RAILS_ENV=test ができない|teratail

Ruby - rails s -e testでlocalhost:3000を見るとエラーが出てしまいます。エラーを消したいです。|teratail

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

Ruby、Rails を学ぶ上でのおすすめの本まとめ

これは何?

Ruby、Rails に関して何によってどう学んできましたか?というのをよく聞かれるので、まとめた。
あくまで個人の経験に基づく感想です。

(書籍へのリンクはアフィリエイトではありません)

対象

とりあえず Ruby でちょっとしたスクリプトは書けるし、Rails も軽く触ったことはあるよ、という方。
(全くRuby触ったことない人向けではないと思います)

Rubyの言語仕様を学ぶ

効能

  • Ruby っぽい書き方ってどんな書き方なんだろう?を学べる(map 使うとか)。
  • Rails を読む上でのおまじないが減る。
    • なぜ attr_accessor を書くとセッター/ゲッターが生えるのかとか、モデルがActiveRecord を継承していることの意味とかがわかるようになり不安が減る。

紹介

  • Effective Ruby
    • リファレンス的な本ではなく、もっと現実的に使われる文脈を意識して書かれているように感じた。読みやすかった。
    • 写経しやすいのが良い。書いて身につけることができる。
  • メタプログラミングRuby第2版
    • メタプログラミングという切り口で Ruby の言語仕様を教えてくれる本だと思った。
    • クラス定義の話(継承ツリーとか特異クラスとかinclude/extendとかの話)はこの本が一番分かりやすかった。
    • 「ブロックがクロージャである」という表現を直感的に理解できるようになった。

設計力を高める

効能

  • 以下のような疑問に対する自分なりの意見を持てるようになる。
    • model/controller/view 以外にも app ディレクトリ以下に service とか作ることがあるっぽいけど、どこに何を書くのが美しいの?
    • controller/model が肥大化してしまってつらいんだけどどうすればいいの?
  • 実装に関して戦略レベルで大きなヘマをすることが減る。したがってコードレビューで大きくちゃぶ台返しされることが減る。

注意事項

  • ちょっと読んでみて「難しくてほとんど全部よくわからん、」「言っている意味はわかるがこれを学んでなにか意味があるの?」となったら今読むべきではないと考えたほうが良いかもしれません。
    • 知識というか考え方を学ぶ本だと私は解釈しており、理解できていない状態で読んでもほとんど学びはないのではないか?となっている。
    • 具体的に開発を通じて悩んだ経験があると、「なるほどこういうふうに書けるのね!」となりやすいが、経験があまりないとピンとこないので記憶に残らない、ということもありそう。

紹介

  • リファクタリング:Rubyエディション
    • 神本。しかし絶版っぽい?
    • 写経しやすいのが良い。書いて身につけることができる。
    • リファクタリングの本なので、before の状態から after の状態に帰る方法としてどのような方法があるのかを学ぶことができる。
    • before の状態を見てその問題点を列挙する力、after の候補を列挙する力、after の候補から最も良いものを選び出す力、を身につけるとアウトプットの質が大幅に改善される。
  • Ruby によるデザインパターン
    • 絶版っぽい?
    • デザインパターンを Ruby で実装してみた、みたいなもの。
    • 『リファクタリング:Ruby』 でしっかり学んでいると、だいたい「まぁそうだよね」、となるかもしれない。
    • 「デザインパターンって言葉よく聞くけど何なのだろう?ちょっと学んでみたいな」みたいな人には特に向いているかと思う。
  • オブジェクト指向設計実践ガイド―Rubyでわかる進化しつづける柔軟なアプリケーションの育て方(サンディメッツ本)
    • 絶版じゃない!!!
    • 上の2つの本で学んだこととかぶるところが多いので途中で読むのをやめたが、良本。
    • 登場する具体例が自転車なので、自転車好きなら良いかも。個人的には特に興味も知識もないので、オブジェクトの例としてギアとかそういうの出てきてもピンとこなくてちょっとつらかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【devise】confirmableを利用しているとき、メールアドレスを変更する際のメールによる確認作業をスキップする

config/initializers/devise.rbを以下のように変更して保存。

config/initializers/devise.rb
  ...

  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
- config.reconfirmable = true
+ config.reconfirmable = false

  ...

この時点でサーバーを起動していたなら再起動する。これでユーザー情報を変更する際に、確認作業なくメールアドレスを変更できる。

もしusersテーブルにunconfirmed_emailカラムが残っているなら、不要なので削除する。

$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string
$ rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【devise】confirmableを適用しているとき、メールアドレスを変更する際のメールによる確認作業をスキップする

config/initializers/devise.rbを以下のように変更して保存。

config/initializers/devise.rb
  ...

  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
- config.reconfirmable = true
+ config.reconfirmable = false

  ...

この時点でサーバーを起動していたなら再起動する。これでユーザー情報を編集する際に、メールアドレスを確認作業なく変更できる。

もしusersテーブルにunconfirmed_emailカラムが残っているなら、不要なので削除する。

$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string
$ rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【devise】confirmableを適用時の、メールアドレスを変更する際のメール確認をスキップする

config/initializers/devise.rbを以下のように変更して保存。

config/initializers/devise.rb
  ...

  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
- config.reconfirmable = true
+ config.reconfirmable = false

  ...

この時点でサーバーを起動していたなら再起動する。これでユーザー情報を編集する際に、メールアドレスをメールによる確認作業なしで変更できる。

もしusersテーブルにunconfirmed_emailカラムが残っているなら、不要なので削除する。

$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string
$ rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deviseでログイン機能を追加・日本語化・Bootstrap適用まで

Deviseの日本語化やBootstrapの導入方法の記事はよく見かけます。ところが……

  • ビューファイルを作成するときに,結局どのコマンドを使えばよいか分からない:sweat:
$ rails g devise:views
$ rails g devise:i18n:views
$ rails g devise:views:bootstrap_templates
  • 全て導入したはずなのにメール文が日本語化されない:cry:

など,順序を間違えると問題が発生します。そこで,ログイン画面を最低限のスタイルで実装するところまでをまとめてみました。

開発環境

  • macOS Mojave 10.14.6
  • Ruby 2.6.4
  • Rails 5.2.3
  • Bootstrap 4.3.1
  • Devise 4.7.1 (confirmableなどの導入過程は記載していません)

0. 準備

以下の前提で進めていきます。(-d postgresqlはお好みで)

$ rails _5.2.3_ new devise_sample -d postgresql
$ rails g controller homes index
config/routes.rb
Rails.application.routes.draw do
  root 'homes#index'
end
app/views/layouts/application.html.erb
(略)
  <body>
    <%= render 'shared/header' %>
    <%= yield %>
  </body>
app/views/homes/index.html.erb
<%= render 'shared/flash_messages' %>
app/views/shared/_flash_messages.html.erb
<% flash.each do |name, msg| %>
  <div class="alert alert-<%= name %>" role="alert" id="alert">
    <a href="#" class="close" data-dismiss="alert">×</a>
    <%= msg %>
  </div>
<% end %>
app/views/shared/_header.html.erb
<header>
  <nav class="navbar navbar-expand navbar-light">
    <%= link_to "Deviseサンプル", root_path, class: 'navbar-brand' %>
    <div id="Navber">
      <ul class="navbar-nav">
        <% if user_signed_in? %>
          <li class="nav-item active">
            <%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: 'nav-link' %>
          </li>
      <% else %>
          <li class="nav-item active">
            <%= link_to "新規登録", new_user_registration_path, class: 'nav-link' %>
          </li>
          <li class="nav-item active">
            <%= link_to "ログイン", new_user_session_path, class: 'nav-link' %>
          </li>
        <% end %>
      </ul>
    </div>
  </nav>
</header>

1. Gemの追加

  • Gemfileに以下を追加して$ bundle install
gem 'bootstrap', '~> 4.3.1'
gem 'devise'
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'devise-bootstrap-views', '~> 1.0'
gem 'jquery-rails'
gem 'rails-i18n'

2. Bootstrapの導入

  • 【公式】twbs/bootstrap-rubygem

  • application.cssの拡張子をscssに変更

  • application.scssから,*= require_tree .*= require_selfを削除

  • application.scss@import "bootstrap";を追加

  • スタイルも追加

app/assets/stylesheets/application.scss
@import "bootstrap";

// ログイン画面

.container-login {
  @extend .container-fluid;
  max-width: 576px;
  padding: 2rem;
}

// 「ログインしました」などのフラッシュ用スタイル

.alert-notice {
  @extend .alert-info;
}

.alert-alert {
  @extend .alert-danger;
}
  • application.jsに3つ追加
app/assets/javascripts/application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets

3. Deviseの導入

  • 【公式】plataformatec/devise

  • Deviseをインストール(userの箇所は,任意のモデル名でOKです)

rails g devise:install
rails g devise user
rails db:create db:migrate
  • 問題がなければ,$ rails sの後,http://localhost:3000からログインボタンを押せば,ログイン画面が表示されます。

スクリーンショット 2019-10-08 9.37.05.png

  • gem 'devise-bootstrap-views'を追加しているので, Bootstrapもある程度適用されています。
    • 公式(hisea/devise-bootstrap-views
    • 横幅一杯表示されるのはちょっと……と思われるかもしれませんが,さらに見た目を整える作業は最後にします。

4. Deviseの日本語化

config/application.rb
module AssociationTutorial
  class Application < Rails::Application
    # 以下を追加すれば日本語に
+   config.i18n.default_locale = :ja
    # タイムゾーンも変更するなら,以下を追加
+   config.time_zone = 'Asia/Tokyo'
  end
end
  • サーバーを落として$ rails sで再起動すれば日本語に変更されます。

スクリーンショット 2019-10-08 10.04.53.png

5. メールを送信できるようにする(Gmailかつ開発環境のみ対応)

  • 送信元の表示名を変更しておきます。
    • タイトル名はこの設定が反映されますが,メールアドレスの箇所は実際の送信するアドレスになります。
config/initializers/devise.rb
Devise.setup do |config|
-  config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
+  config.mailer_sender = ''タイトル名 <noreply@example.com>'
end
  • Gmailから送信する場合,安全性の低いアプリのアクセスを有効にする必要があります。

  • development.rbconfig.action_mailer.raise_delivery_errors = falseの箇所を置き換えます。

    • メールアドレスやパスワードをgitの管理下に入れるのを避け,credentials管理にしておきます。本番環境で無駄にエラーが出ないようダミー情報を入れておきます。
config/environments/development.rb
  # 削除 config.action_mailer.raise_delivery_errors = false
  # 以下に置き換え
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

  if Rails.application.credentials.gmail.present?
    mail_address = Rails.application.credentials.gmail[:address]
    password = Rails.application.credentials.gmail[:password]
  else
    mail_address = 'admin@example.com'
    password = 'password'
  end

  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
      enable_starttls_auto: true,
      address: "smtp.gmail.com",
      port: 587,
      user_name: mail_address,
      password: password,
      authentication: "plain"
  }
  • $ EDITOR=vi rails credentials:editで,credentialsの中に,メールアドレスとパスワードを記載します。
gmail:
  address: 送信に使用するメールアドレス
  password: 送信に使用するパスワード
  • メール送信の確認をするには,あらかじめ新規登録をした上で,ログイン画面のパスワードを忘れましたか?をクリックしてパスワード再設定メールを送信すればOKです。

スクリーンショット 2019-10-08 12.59.31.png

 日本語にはなっているもののどうも不自然ですね……これは次の作業で解決します。

6. 日本語訳を変更

日本語訳を変更したい場合は,次のコマンドでconfig/locales/devise.views.ja.ymlを作成し,編集すればOKです。

rails g devise:i18n:locale ja
  • なお,このコマンドの時点で,パスワード再設定メールの本文が自然なものに変わります。

スクリーンショット 2019-10-08 13.15.17.png

  • 例えばアカウント登録新規登録に変更したい場合は,devise.views.ja.ymlの該当文字を置換すればOKです。

7. ログイン画面などの変更

  • まず,次のコマンドでビューファイルを作成します。
rails g devise:i18n:views
rails g devise:views:bootstrap_templates -f
  • 【参考】それぞれのコマンドの最後に例えばuserをつけることで,usersディレクトリ内にファイルを作成することもできますが,その場合は,次の3つの作業を行わないと反映されません。

    • devise.views.ja.yml30行目のdeviseusersに変更
    • config/initializers/devise.rbにあるconfig.scoped_views = falseのコメントアウトを外してtrueに変更
    • サーバーを落として$ rails sで再起動
  • 例えば以下のファイルを<div>で囲むと,無駄に横長になっている状態を改善できます。

    • ここから先はお好みで。
ファイル名
app/views/devise/confirmations
app/views/devise/passwords
app/views/devise/registrations
app/views/devise/sessions
app/views/devise/unlocks
<div class="container-login">
# 元のプログラム
</div>

スクリーンショット 2019-10-08 14.54.38.png

app/helpers/devise_helper.rb
module DeviseHelper
  def bootstrap_devise_error_messages!
    return "" if resource.errors.empty?

    html = ""
    messages = resource.errors.full_messages.each do |errmsg|
      html += <<-EOF
      <div class="alert alert-danger alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert">
          <span aria-hidden="true">&times;</span>
          <span class="sr-only">close</span>
        </button>
        #{errmsg}
      </div>
      EOF
    end
    html.html_safe
  end
end

〜〜〜〜〜変更前〜〜〜〜〜
スクリーンショット 2019-10-08 15.13.37.png
〜〜〜〜〜変更後〜〜〜〜〜
スクリーンショット 2019-10-08 15.13.06.png

  • ログイン画面だけエラーメッセージが表示されないので,追加しておきます。
app/views/devise/sessions/new.html.erb
 <div class="container-login">
   <h1><%= t('.sign_in') %></h1>
+  <%= render 'shared/flash_messages' %>

   <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

スクリーンショット 2019-10-08 15.18.45.png

  • app/views/deviseディレクトリ内のファイルのbtn btn-primarybtn btn-primary btn-blockに置換すればボタンの横幅が自然になります。

〜〜〜〜〜変更前〜〜〜〜〜
スクリーンショット 2019-10-08 15.26.43.png
〜〜〜〜〜変更後〜〜〜〜〜
スクリーンショット 2019-10-08 15.26.51.png

  • バリデーションはフロント側にも簡単に入れられます。
    • f.email_fieldf.password_fieldrequired: trueを入れることで空欄投稿できなくなります。
    • 新規登録(アカウント登録)画面では,例えば,f.password_fieldrequired: true, minlength: '6', maxlength: '30'を追加すれば,文字数のバリデーションも追加できます。
app/views/devise/registrations/new.html.erb
     <div class="form-group">
       <%= f.label :password %>
-      <%= f.password_field :password, autocomplete: 'current-password',
-                                      class: 'form-control' %>
+      <%= f.password_field :password, autocomplete: 'current-password',
+                                      class: 'form-control',
+                                      required: true,
+                                      minlength: '6',
+                                      maxlength: '30' %>

スクリーンショット 2019-10-09 10.04.34.png

  • _links.html.erbを編集して,一番下のリンクをボタンにしてみます。
app/views/devise/shared/_links.html.erb
<hr class="border-dark my-5">
<div class="form-group">
  <%- if controller_name != 'sessions' %>
    <%= link_to t(".sign_in"), new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to t(".sign_up"), new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    <%= link_to t(".forgot_your_password"), new_password_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    <%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    <%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.omniauthable? %>
    <%- resource_class.omniauth_providers.each do |provider| %>
      <%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), class: 'btn btn-info btn-block' %><br />
    <% end -%>
  <% end -%>
</div>

スクリーンショット 2019-10-08 15.36.29.png

  • さらに,リンクのログイン新規登録(アカウント登録)を次のように変更してみます。
# 上2つを次に置き換え

  <%- if controller_name != 'sessions' %>
    <%= link_to "アカウントをお持ちの方", new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to "アカウントをお持ちでない方", new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

スクリーンショット 2019-10-08 15.41.51.png

単純なスタイルですが,ログイン画面がだいぶ整ったのではないでしょうか。

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

アンチパターン:Railsで外部キーとassociation名は同じにしない

要約

  • belongs_to :nameforeign_key :nameが同じだと、その逆のhas_manyをとるときにどうがんばってもうまく取れなかった
  • 同じにしない
  • 外部キーには_idつける

詳細

  • 以下のコードだとto_messagesfrom_messagesがとれない
class User < ApplicationRecord
  has_many :to_messages, class_name: 'Message', foreign_key: 'to', dependent: :destroy
  has_many :from_messages, class_name: 'Message', foreign_key: 'from', dependent: :destroy
end

class Message < ApplicationRecord
  has_many :to, class_name: 'User', foreign_key: 'to'
  has_many :from, class_name: 'User', foreign_key: 'from'
end

>Message.last.to
=>#<User id: 373...>
>User.last.to_messages
=>[]
  • has_manyの名前を変えたらいけた
class Message < ApplicationRecord
  has_many :to_user, class_name: 'User', foreign_key: 'to'
  has_many :from_user, class_name: 'User', foreign_key: 'from'
end
  • そもそもforeign_key_idがいいね
  • 自分では慣例的にやらないけどレビューであがってくることはあるので覚えておく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイグレーションファイルの命名規則

目的

マイグレーションファイルを作成する際に
作成のみでなく閲覧者などにもわかりやすい命名にするため規則などを記載していく

taskテーブルを作成

rails g migration CreateTasks name:string content:text

マイグレーションファイル例
class CreateTasks < ActiveRecord::Migration[5.2]
def change
    create_table :tasks do |t|
      t.string :name
      t.text :content
    end
end

taskテーブルにカラムを追加

rails g migration AddStateToTasks state:string
rails g migration add_state_to_tasks state:string

マイグレーションファイル例
class AddStateToTask < ActiveRecord::Migration[5.2]
  def change
    #[形式]add_column(テーブル名,カラム名,データ型)
    add_column :tasks, :state, :string
  end
end

add_columnには次のオプションを指定することができる。
・ null:true … NOT NULL制約を削除
・ null:true … NOT NULL制約を追加
・ limit: size … ファイルのサイズに対する制限を設定
・default:[val]… [val]に設定した値をレコード作成じのカラムのデフォルト値とする

taskテーブルに追加したカラム名を変更

rails g migration RenameFromStateToTasks
rails g migration rename_state_to_tasks

マイグレーションファイル例
class RenameFromStateToTasks < ActiveRecord::Migration[5.2]
  def change
    #[形式]rename_column(テーブル名,変更前のカラム名,変更後のカラム名)
    rename_column :tasks,:state,:status
  end
end

taskテーブルに追加したカラムのデータ型を変更

rails g migration ChangeStatusOfTasks
rails g migration change_datatype_status_of_tasks

マイグレーションファイル例
class ChangeDatatypeStatusOfTasks < ActiveRecord::Migration[5.2]
  def up
    #[形式]change_column(テーブル名,カラム名,データタイプ,オプション)
    change_column :tasks, :status, :integer

    # オプション
    # limit - カラム長の最大数
    # change_column :tasks, :status, :integer, limit: 120

    # default - カラムのデフォルト値を設定。NULLにしたい場合は、nilを指定
    # change_column :tasks, :status, :integer, default: "タイトルがありません"

    # null - null制約を設定。false -> null制約がON。true -> null制約がOFF
    # change_column :tasks, :status, :integer, null: true
  end
end

taskテーブルに追加したカラムにインデックスやユニーク制約の追加

rails g migration AddIndexStatusToTasks
rails g migration add_index_Status_to_tasks

マイグレーションファイル例
class AddIndexStatusToTasks < ActiveRecord::Migration[5.2]
  def change
    add_index  :tasks, :status
   #add_index  :tasks, :status ,unique: true ユニーク制約も付加可能
  end
end

taskテーブルに追加したカラムにNULL制約の追加

rails g migration ChangeNotNullToTasks
rails g migration change_notnull_to_tasks

マイグレーションファイル例
class ChangeNotNullToTasks < ActiveRecord::Migration[5.2]
  def change
    #tasksテーブルのstatusにNOT NULL制約を追加
    change_column_null :tasks, :status, false
  end
end

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

DockerでRails・PostgreSQL構築

概要

  • Dockerを使用したRailsの開発環境を構築
  • DBはPostgreSQLを使用
  • ローカル開発環境で構築(PostgreSQLのユーザー名、パスワードは簡易なものを使用)

>>MySQLはこちら

前提

  • Mac、Windows10のローカル開発環境で動作
  • 「Docker for Windows」か「Docker for Mac」がインストール済みであること

ディレクトリ構造

├─src
│  ├─Gemfile
│  └─Gemfile.lock
├─Dockerfile
└─docker-compose.yml

準備するファイル

ディレクトリ構造にある4つのファイルの内容は下記になります。

Dockerfile

Dockerfile
FROM ruby:2.6.3
ENV LANG C.UTF-8

RUN apt-get update -qq && \
    apt-get install -y build-essential \
            libpq-dev \
            nodejs

RUN mkdir /app
RUN mkdir /app/src

ENV APP_ROOT /app/src
WORKDIR $APP_ROOT

ADD ./src/Gemfile $APP_ROOT/Gemfile
ADD ./src/Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install


ADD . $APP_ROOT

docker-compose.yml(※WindowsとMacで記述を分ける箇所有)

docker-compose.yml
version: '3'
services:
  postgres:
    image: postgres
    ports:
      - "3306:3306"
    volumes:
      - ./tmp/db:/var/lib/postgresql/data #MacOSの場合
      - app_postgre:/var/lib/postgresql/data #Windowsの場合
    environment:
      POSTGRES_USER: 'admin'
      POSTGRES_PASSWORD: 'admin-pass'
    restart: always
  app:
    build: .
    image: rails
    container_name: 'app'
    command: bundle exec rails s -p 80 -b '0.0.0.0'
    ports:
      - "80:80"
    environment:
      VIRTUAL_PORT: 80
    volumes:
      - ./src:/app/src
    depends_on:
      - postgres
    restart: always

#下記はWindowsの場合追加
volumes:
  app_postgre:
    external: true

※PostgreSQLはWindowsではマウントできないようなので、参照先を外部に指定します。

Gemfile

src/Gemfile
source 'https://rubygems.org'
ruby '2.6.3'
gem 'rails', '5.2.3'

Gemfile.lock

ファイルの中身は空でOK

src/Gemfile.lock

手順

ボリューム作成(※Windowsの場合のみのコマンド)

$ docker volume create --name app_postgre

※PostgreSQLはWindowsではマウントできないようなので、参照先を外部にするためのコマンドです。

スケルトンアプリ作成

$ docker-compose run app rails new . --force --database=postgresql --skip-bundle

database.ymlを修正

src/config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: admin
  password: admin-pass
  host: postgres

イメージ再ビルド

※処理完了までちょっと時間がかかることがあります。。。

$ docker-compose build

コンテナ立ち上げ

$ docker-compose up -d

PostgreSQLにRailsのDBを作成

$ docker-compose run app rails db:create

これで、http://localhostにブラウザでアクセスすると、Railsのインストール完了画面がでてきました。

トップページ作成

とりあえずトップページを表示させたい場合は下記コマンドを実行

$ docker-compose run app rails g controller Home top

「get 'home/top'」を「root to: 'home#top'」を変更

config/routes.rb
# ・・・

root to: 'home#top'

# ・・・

インストール後の作業

私の記事ですが、下記の手順もまとめましたので、参考にしていただければ幸いです。

【Rails】「config」の使い方 - Qiita
サイトから送信するSMTPサーバー情報や、ソーシャルログインなどで使用するAPIキーをGitにアップロードする際に、外部に情報が流出させないようにする設定です。

【Rails】Bootstrap4・jQueryを適用
フロントでBootstrap4とjQueryを適用して構築したい時は便利

Font Awesome適用
アイコンを気軽に使用したい時はこちらが便利。メールアドレスの登録・アンケート回答すると、無料でたくさんのアイコンが使用できます。

【devise】メール認証のサインアップ・イン・アウト機能
メール認証で、サイトに新規会員登録機能や、ログイン・ログアウト機能を搭載する設定です。

【Rails】【Devise】twitter・Facebookログイン実装
上記のメール認証に加えてソーシャルログイン機能を実装する手順です。

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

【49日目】Rails学習日誌 コメントの保存、表示、パーシャル作成、バリデーション、エラーの表示

68 コメント保存処理の実装 続き

コメントのコントローラーの編集 

binding.pryでコメント入力時のパラメーターを確認
Rails flogによってパラメータが下記のように見やすく表示される

  Parameters: 

{

                  "utf8" => "✓",

    "authenticity_token" => "(略)",

               "comment" => {

        "board_id" => "5",

            "name" => "例",

         "comment" => "コメント"

    },

                "commit" => "送信"

}

ストロングパラメータの設定

保存できるパラメータはboard_id, name, commentのみにする

comments_controll.rb
private

def comment_params
  params.require(:comment).permit(:board_id, :name, :comment)
end

createアクションの作成

ストロングパラメータによって弾かれるかどうかをif文で分岐
分岐した結果、成功をエラーをflashメッセージを用いてリダイレクト先に表示

comments_controll.rb
def create
  # コメントオブジェクトを作成
  # フォームに入力されたパラメータで初期化
  comment = Comment.new(comment_params) 
  # コメントの保存。保存前にバリデーションチェック
  # 保存の可否によってif文で分岐
  if comment.save 
    flash[:notice] = 'コメントを投稿しました' #flashでリダイレクト先に一時的にメッセージを渡す
    redirect_to comment.board 
  else
  # 以下はRails5.1以降専用
  # コメントが保存できなかった場合入力したコメントオブジェクトとエラーメッセージのリストを返す。
  # コメントオブジェクトの中身をリダイレクト先のフォームに戻したり、エラー表示は今後作る
    flash[:comment] = comment 
    flash[:error_messages] = comment.errors.full_messages
    redirect_back fallback_location: comment.board # redirect_backで一つ前の画面に戻るようリダイレクト
  end
end

69 コメントの表示

showの問題を修正

修正前のshowアクション

boards_controller.rb
def show
  @comment = @board.comments.new
end

この状態では、新しく作成されたが保存していない空のコメントが含まれてしまう。
=>常にからのコメントがコメント欄に表示されてしまうので修正する。

boards_controller.rb
def show
  @comment = Comment.new(board_id: @board.id)
end

コメントの初期化の際にboard_idを用いることで、該当のboardのコメントで保存されたコメントのみを表示するようにした。

viewの作成

show.html.erb
<div class="p-comment__list">
  <div class="p-comment_listTitle">コメント</div>
  <%= render @board.comments %> 
# 特定のモデル(comment)のオブジェクトのリスト(comments)をrenderの引数に渡した場合、自動的にモデル名(comment)のviewが使用される。
# オブジェクトの数だけ繰り返しviewがレンダリングされる。
# 今回のrenderの対象は「comments」なので、/app/views/comments/_comments.html.erbがオブジェクトの数レンダリングされる
</div>

## コメント表示用のパーシャルの作成
/app/views/commentsを作成し、_comment.html.erbを作成。

_comment.html.erb
<div class="p-comment__item">
  <p><%= simple_format(comment.comment) %></p> # simple_formatヘルパーで改行を<br>タグに変換してくれる
  <div class="p-comment__bottomLine">
    <span><%= comment.name %></span>
    <span><%= comment.created_at.to_s(:datetime_jp) %></span> #以前作った日本語時刻表示
  </div>
</div>

リファクタリング

commentを投稿するためのviewはcommentsディレクトリの下にある。
しかし、_comment_formはboardsディレクトリの下にある。
=>役割的にcommentsディレクトリ配下にあるべきなので修正する。

_form.html.erb としてcomments配下にコピーした上で、shownのパーシャルの呼び出し方を変えれば完了。

show.html.erb
<%= render partial: 'comments/form', locals: { comment: @comment } %>

70 コメントモデルへのバリデーション追加

モデルにバリデーションを作成

comment.rb
validates :name, presence: true, length: { maximum: 10 }
validates :comment, presence: true, length: { maximum: 1000 }

## エラーメッセージを作成
comments_controller.rbのcreateアクションのelse以下がバリデーションエラーの表示になる
以前作成した掲示板のcreate時のエラーメッセージを流用する

app/views/sharedディレクトリを作成
_error_messages.html.erbを作成

<% if flash[:error_messages] %>
  <div class="alert alert-danger">
    <ul>
      <% flash[:error_messages].each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

各viewのエラー呼び出しを編集
まずはboardsから
こちらではすでに作成している_form.html.erb内にエラー表示の部分があるため、sharedを呼び出すように書き換える。

_form.html.erb
<%= render 'shared/error_messages' %>

commentsにおいても_form.html.erbの先頭にsharedを呼び出すように上記と同じ記載をする。

エラーの日本語化

config/locales/ja.yml を編集

ja.yml
ja:
  activerecord:
    attributes:
      board:
        name: 名前
        title: タイトル
        body: 本文
      comment:
        name: 名前
        comment: コメント
   views:
     pagination:
       first: '最初'
       last: '最後'
       previous: '前'
       next: '次'
       truncate: '...'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む