Herokuへのデプロイ時に苦労した話

昨日完成したTodoアプリをHerokuにアップしました!

その際2箇所でつまづいたので、記録しておきます。

pg gemがインストールできない

HerokuではSQLiteがサポートされていないのでPostgreSQLを使います。

sqlite3 gemをproduction環境で使わないように、
そしてpg gemをproduction環境で使うようにgemfileを編集し、bundle installしたところ

Building native extensions.  This could take a while...
ERROR:  Error installing pg:
    ERROR: Failed to build gem native extension.
(略)
Can't find the 'libpq-fe.h header

libpq-fe.h が見つからない、というエラーが出ました。

このエラーの処理は

$ sudo yum install postgresql-devel

で解決。
(yumというのを使ってPostgreSQLをインストールするということらしいです)


(本筋と関係なし)

無事にpg gemもインストールできたのでherokuにpushしました。
デプロイしたページにアクセスすると……




We're sorry, but something went wrong

ああ……

色々調べた結果、

「production環境でrails_12factorというgemを有効にする必要がある」

という情報にたどり着き、gemfileに追記したのですが
調べ直したところこの情報はRailsチュートリアル第4版から、つまりRails5になってからは記載が無くなった情報なので、今はもう必要ないのかもしれません。

本題は次のエラーです。

Userテーブル作成時のマイグレーションファイルを削除してしまった

heroku logsで確認してみると、rootページへのGETリクエストは成功しているけどその後移動するログインページで500エラーが出ていました。

データベースの問題じゃないかと思ってdb:migrateを実行してみると……

$ heroku run rake db:migrate
Running rake db:migrate on ⬢ rocky-beyond-45744... up, run.9472 (Free)
   (0.8ms)  SELECT pg_try_advisory_lock(2653661978130648765)
   (1.3ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Migrating to AddUserRefToTasks (20180827040204)
   (0.8ms)  BEGIN
== 20180827040204 AddUserRefToTasks: migrating ================================
-- add_reference(:tasks, :user, {:foreign_key=>true})

  (略)

   (1.4ms)  ROLLBACK
   (5.0ms)  SELECT pg_advisory_unlock(2653661978130648765)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  relation "users" does not exist

(略)

Caused by:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  relation "users" does not exist

usersテーブルが存在しない、というエラーが出ました。

ん?と思ってdb/migrateを確認すると、Userモデルを作成したときのマイグレーションファイルが無い。

Devise導入の際にrails g devise UserでUserモデルが作成できているのかわからず試行錯誤したのですが、
どうやらその時に作成されたマイグレーションファイルを削除してしまったようです。

解決方法

joppot.info

この記事を参考に以下の処理をしました。

状況

削除してしまったマイグレーションファイルのIDを調べます

$ bundle exec rake db:migrate:status
database: 省略/db/development.sqlite3

 Status   Migration ID    Migration Name
--------------------------------------------------
   up      20180823030402  Create tasks
   up     ★20180826083613  ********** NO FILE **********
   up      20180827040204  Add user ref to tasks
   up      20180829041636  Add row order to tasks

NO FILE になっているIDが今回間違えて削除してしまったマイグレーションファイルです。

そして、Statusがupになっているマイグレーションファイルはデータベースに取り入れられているものです。

本番環境でマイグレーションを実行した時に、★20180826083613のファイルが存在しないので冒頭のエラーが出てしまった、という状況ですね。

対処方法

プロジェクトのdb/migrateディレクトリに移動します

$ cd db/migrate

ここで、先程確認した間違えて消してしまったファイルと同じIDのマイグレーションファイルを作成します。

$ touch マイグレーションID_ファイル名.rb

今回の場合だと

$ touch 20180826083613_devise_create_users.rb

になります。

作成したマイグレーションファイルに内容を書き込みます。
私の場合はDevise練習用に作成した別のプロジェクトがあったのでそこから引っ張ってきました。

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
 
(略)

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

これで間違えて削除してしまったマイグレーションファイルが復活した状態になったので

$ git add .
$ git commit
$ git push heroku master

でherokuにプッシュし、再度 本番環境でdb:migrate

$ heroku run rake db:migrate

うまくいきました!
これでアプリケーションの作成からHerokuへのデプロイまでをひと通り完了することができました。
次に何をするか考えようと思います。

参考リンク

Herokuへのデプロイでつまづいたことまとめ

libpq-fe.h が見つけられない。

railsのrakeのmigrationファイルを削除しNO FILEとstatusに出た時の対処 – joppot

jQueryUIのsortableで並び替えが保存されない

Rails5.2.1

初の自作アプリとしてタスク管理アプリケーションを作成すべくこの一週間奮闘してきまして、本日とりあえずの完成に至りました。

Taskの並び替えをドラッグ&ドロップで実現すべくjQueryUIの導入を目指してドン詰まったのが前回の記事でしたが、その後並び替えたTaskの順番が保存されないという問題が新たに浮上しまして、その解決までの道のりを記しておきます。

エラー内容

Started PUT "/users/1/tasks/51/sort" for 157.107.100.245 at 2018-08-30 10:33:07 +0000
Cannot render console from 157.107.100.245! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 
Processing by TasksController#sort as JSON
  Parameters: {"task"=>{"row_order_position"=>"1"}, "user_id"=>"1", "task_id"=>"51"}
★Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 0ms (ActiveRecord: 0.0ms)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
vendor/bundle/ruby/2.4.0/gems/actionpack-5.2.1/lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'
vendor/bundle/ruby/2.4.0/gems/actionpack-5.2.1/lib/action_controller/metal/request_forgery_protection.rb:243:in `handle_unverified_request'
vendor/bundle/ruby/2.4.0/gems/devise-4.5.0/lib/devise/controllers/helpers.rb:255:in `handle_unverified_request'(こんな感じのがたくさん

エラー部分抜粋

Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 0ms (ActiveRecord: 0.0ms)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

これに関してはこちらの記事がとても参考になりました。

Rails4のCSRF対策で「Can't verify CSRF token authenticity」エラー

RailsCSRF対策によって発生しているエラーですが、動き自体は正常なものなので、

class TasksController < ApplicationController
 protect_from_forgery except: :sort
end

と記述することでsortアクションを例外に指定します。


これで解決したと思いきや、次のエラーが

Started PUT "/users/1/tasks/52/sort" for 157.107.100.245 at 2018-08-30 11:16:00 +0000
略
 [1m[35m (5.4ms)[0m  [1m[36mcommit transaction[0m
  ↳ app/controllers/tasks_controller.rb:60
Completed 500 Internal Server Error in 32ms (ActiveRecord: 7.6ms)
  
ActionView::MissingTemplate (Missing template tasks/sort, application/sort with {:locale=>[:en], :formats=>[:json, :js, :html, :text, :js, :css, :ics, :csv, :vcf, :vtt, :png, :jpeg, :gif, :bmp, :tiff, :svg, :mpeg, :mp3, :ogg, :m4a, :webm, :mp4, :otf, :ttf, :woff, :woff2, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip, :gzip], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :slim, :coffee, :jbuilder]}. Searched in:
  * "/home/ec2-user/environment/TodoApp3/app/views"
  * "/home/ec2-user/environment/TodoApp3/vendor/bundle/ruby/2.4.0/gems/devise-4.5.0/app/views"
):
  
app/controllers/tasks_controller.rb:61:in `sort'

ActionView::MissingTemplate (Missing template tasks/sort, application/sort with
という部分から「tasksコントローラのsortアクションで描写するviewがないよ!」
という意味だと読み取れます。
最後の行にあるapp/controllers/tasks_controller.rb:61:in `sort'に従って61行目を確認すると……

  def sort
    @user = User.find(params[:user_id])    
    task = Task.find(params[:task_id])
    task.update(task_params)
⇛ render nothing: true
  end

ここが良くないようです。

調べてみるとどうやら、

render nothing: true はRails5から使えなくなったので変わりに

render body: nil あるいは head :ok を使う

ということだったようです。

感想

Rails4を想定して書かれた記事を参考にしていたばかりに、思わぬところで時間を取られてしまいました。
やり方を調べればいくらでも記事は出てきますが、情報が古い可能性があるということ、プログラミングで使うものは日々アップデートされるということをしっかり理解していないと今回のような事態になってしまいます。なんでもコピペではダメですね。

application.jsの記述が原因でjQueryUIが動かなかった話

前置き

作成中のTodoアプリケーションで、タスクのドラッグ&ドロップで並び順を自由に替えられるようにしよう!ということで

qiita.com

こちらの記事を参考に実装を進めていたんですが、手順を完了してもドラッグ&ドロップが動かない……
application.jsファイルへの記述が足らなかったことが原因でした。

今回はこのapplication.jsファイルの役割とjQueryを動かすのに必要な記述をまとめていきます。

前提

そもそもこのapplication.js、ひいてはassetsディレクトリって一体何なの?ということなんですが、
アセットパイプライン(Asset Pipeline)というRailsの機能の一部分であります。

アセットパイプラインについてはRailsチュートリアル第4版の5.2.1、 Rails初学者がつまずきやすい「アセットパイプライン」が分かりやすいです。

要するに
CSSJavaScript、画像など、HTMLを装飾する要素を専用のディレクトリにまとめることで開発をしやすくし

②実際にアプリケーションが動作するときにはそれらのファイルがアセットパイプラインの機能によって効率的にまとまり、ファイルサイズを最小化してくれるのでサイトの読み込み時間も短くなる
という便利な仕組みです

application.js って何?

アセットパイプラインではCSSJavaScript、画像などの「アセット」はapp/assetsディレクトリ配下にあるそれぞれのフォルダに置きます。
f:id:naito-coding0322:20180830165306p:plain

JavaScriptsとstylesheetsフォルダにはそれぞれapplication.js / application.scssというファイルがあります。

このファイルの役割は
このRailsアプリケーションでは、このJavaScript/CSSを使うよ~
という宣言をすることです。

この宣言(ディレクティブ)によってアセットパイプラインのSprocketsという機能がファイルを読み込んで効率的にまとめてくれます。

application.jsの中身

今回はこのapplication.jsファイルの記述を間違えていたのでjQueryUIを利用した並び替えがうまく動きませんでした。
かなり長い時間を取られてしまったので、このファイルの中身についてまとめておきます。 (説明を簡略化するため、jQueryUIの動作に関するもののみ記述しています)

//
//= require rails-ujs
//= require jquery
//= require turbolinks
//= require jquery-ui/widgets/sortable
//= require_tree .

最終的にこんな感じできちんと動作しました。一つ一つの記述を解説していきます。

(//= はSprocketsに対してどのJavaScriptを使うかという宣言のやり方です )


//= require rails-ujs

RailsJavaScriptを使えるようにする
//= require jquery_ujsという記述はrails5.1から不要になったようです


//= require jquery

jQueryを読み込む


//= require turbolinks

ページ遷移をAjaxに置き換え、高速化する。Rails4から標準装備になった。


//= require jquery-ui/widgets/sortable

jQueryUIのsortable機能を使えるようにする


//= require_tree .

app/assets/javascripts以下の全てのjsファイルを読み込む
(これによってtable_sort.js.coffeeが読み込まれる)

注意点

実装したjQueryの機能がページ遷移後に動かなくなる場合、Rails5以降は以下の記事にあるような処理が必要です!

qiita.com

Deviseインストール直後のテストで失敗

自作のアプリにログイン機能を実装しようということで Devise を試してみました。

qiita.com

こちらの記事を参考にさせていただいたんですが、ひと通り設定が終わってrailsサーバー再起動のあとログイン画面を確認できたので、自動で作成されたテストを試しに実行してみたら以下のエラーが

Error:
HomeControllerTest#test_should_get_index:
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: column email is not unique: INSERT INTO "users" ("created_at", "updated_at", "id") VALUES ('2018-08-26 07:01:20.987535', '2018-08-26 07:01:20.987535', 298486374)


いろいろ検索してスタックオーバーフローを見た結果、自動生成されたfixtureのymlファイルに問題がありました。

one: {}
# column: value
#
two: {}
# column: value

自動生成されるテストユーザーが2人いるんですが、それが同じ内容なのでemailが被ってしまっていたんですね。

one:
  email: user@example.com
  encrypted_password: password1

two:
  email: user2@example.com
  encrypted_password: password2

上記のように書き換えて解決。
テストも成功しました。

<参考にしたページ>

ruby on rails - SQLite3::ConstraintException: column email is not unique - Stack Overflow

Todoアプリケーション作成に向けて

アプリケーションの目的

普段プログラミングを勉強しているときに、
「このサイトを参考にしながらこういう知識を学んで……」
「そのために前提としてまずこっちを学んで……」
とやっているうちに頭の中でごちゃごちゃになってしまう。
今回はそういった問題を解消できるようなタスク管理アプリを制作する。

必要な機能

  • タスク一覧ページ
    • チェックボックスで完了にしたら消去できるように
    • クリックして詳細表示
      • 長いテキストを入力して表示
      • 参考にするページをリンクで追加する機能
  • ソート機能
  • 自由に並び替える機能

  • ログイン機能(Userとの結びつけ)

    • Railsチュートリアルに習ってメールアドレスでの認証まで実装したい
    • あるいは少しレベルを下げてIDとパスワードでログイン可能にする
    • ログイン時のトップページはタスク一覧

アップデート

上記の機能を備えたアプリケーションの完成後にアップデートという形で追加したい。

  • チェックを入れてdestroyしたtaskを「完了したタスク」という形で利用できるようにする

  • todo.lyのようにtodoAの下にtodoBをドラッグ&ドロップで配置、という操作を可能にしたい

    • おそらくモデル側をいじるのではなく、単純に画面上でアニメーション的な形で実装することになる(調べる)
  • タスクにカテゴリ追加
    • ネストした?ようなモデルの構成が必要になるので高度に思えるが、「機能変更/修正をしやすいコード」の必要性を学ぶ足がかりにしたい

期間

今日から1週間(9/1まで)で完成を目指したい。その後アップデート内容の実装に取り掛かる。
ログイン機能の実装をひとまずおいておき、レイアウト含めた基本的な機能を実装。そのあとRailsチュートリアルを参考にログイン機能とUserとTaskの結びつけ、という流れでやる予定。

Bootstrap4の導入方法

ドットインストールのBootstrap 4入門を見ながらTodoアプリにナビゲーションバーを追加してみたけど……あれ、レイアウトがおかしいぞ……

あ!Bootstrap4追加してないじゃん!

ということで、Rails(5.2.1)でBootstrap4を導入してみました
(執筆時点でのbootstrap最新はbootstrao 4.1.3)

導入方法

Gemfile

gem 'bootstrap', '~> 4.1.3'
gem 'jquery-rails'

BootstrapはjQueryに依存するため、Rails5.1以上ではjquery-rails追記が必要

また、sprockets-railsがv2.3.2以上である必要がある

application.scss

サイトのレイアウトファイルであるapplication.cssなどを.scssにリネームし、
application.scssでbootstrapをimportさせる

@import "bootstrap";

application.js

Bootstrapと依存関係であるということをapplicationl.js追記する

//= require jquery3
//= require popper
//= require bootstrap-sprockets

こんな感じに

これで、追加したヘッダーが

    <header> 
      <div class="container">
       <nav class="navbar navbar-expand navbar-light">
         <a href="" class="navbar-brand">TodoApp</a>
         <ul class="navbar-nav">
           <li class="nav-item"><a href="" class="nav-link">link</a></li>
           <li class="nav-item"><a href="" class="nav-link">link</a></li>
           <li class="nav-item"><a href="" class="nav-link">link</a></li>
         </ul>
       </nav> 
      </div>
    </header>

f:id:naito-coding0322:20180825130441p:plain

こんな感じにきちんと表示されました。
引き続きレイアウトを綺麗にしていきます。とりあえずデザインの知識が皆無なので、Railsチュートリアルで作ったサンプルアプリを真似ようかな。

その他

インストールしたgemの一覧を表示

gem list

不要なgemをアンインストールしたい場合

gem uninstall (gem名)

bundle update で特定のgemだけをupdateしたい場合

bundle update (gem名)

この方法だと今回設定したような依存関係にあるgemも同時に更新されてしまうので、単体で更新したい場合は

bundle update --source (gem名)

テストメソッドまとめ

まずは基本から

assert(boolean, message = "テキスト" )
  • boolean(式) がtrueであればテスト成功
  • boolean(式) がfalseまたはnilのときはテスト失敗
  • テストが失敗したときにmessageに入力したテキストが表示される(省略可)
  • 1つのテスト内にassert~メソッドが複数ある場合、上から実行していって最初に失敗した時点でテストは中止される
メソッド 説明
assert_equal(変数1, 変数2, message ) 変数1と変数2が等しければ成功
assert_not_equal(変数1, 変数2, message ) 変数1と変数2が等しくなければ成功
assert_nil(変数, message ) 変数がnilならば成功
assert_not_nil(変数, message ) 変数がnilじゃなければ成功
assert_match(正規表現, 文字列, message ) 正規表現に文字列がマッチすれば成功
assert_no_match(正規表現, 文字列, message ) 正規表現に文字列がマッチしなければ成功
assert_difference( expressions, difference = 1, message) { ブロック } ブロックの実行前と後でexpressionsの式の結果にdifferenceと同じ数の差異が発生すれば成功
assert_no_difference( expressions, difference = 1, message) { ブロック } ブロックの実行前と後でexpressionsの式の結果にdifferenceと同じ数の差異が発生しなければ成功 | assert_template( expected, message ) | expectedに指定したビューテンプレートが描写されたら成功

参考になる記事

assertの基本から - ザリガニが見ていた...。

テストで使うメソッド - challenge Ruby on Rails

テスト(test) - - Railsドキュメント