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モデルが作成できているのかわからず試行錯誤したのですが、
どうやらその時に作成されたマイグレーションファイルを削除してしまったようです。
解決方法
この記事を参考に以下の処理をしました。
状況
削除してしまったマイグレーションファイルの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へのデプロイまでをひと通り完了することができました。
次に何をするか考えようと思います。
参考リンク
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」エラー
RailsのCSRF対策によって発生しているエラーですが、動き自体は正常なものなので、
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アプリケーションで、タスクのドラッグ&ドロップで並び順を自由に替えられるようにしよう!ということで
こちらの記事を参考に実装を進めていたんですが、手順を完了してもドラッグ&ドロップが動かない……
application.jsファイルへの記述が足らなかったことが原因でした。
今回はこのapplication.jsファイルの役割とjQueryを動かすのに必要な記述をまとめていきます。
前提
そもそもこのapplication.js、ひいてはassetsディレクトリって一体何なの?ということなんですが、
アセットパイプライン(Asset Pipeline)というRailsの機能の一部分であります。
アセットパイプラインについてはRailsチュートリアル第4版の5.2.1、 Rails初学者がつまずきやすい「アセットパイプライン」が分かりやすいです。
要するに
①CSS、JavaScript、画像など、HTMLを装飾する要素を専用のディレクトリにまとめることで開発をしやすくし
②実際にアプリケーションが動作するときにはそれらのファイルがアセットパイプラインの機能によって効率的にまとまり、ファイルサイズを最小化してくれるのでサイトの読み込み時間も短くなる
という便利な仕組みです
application.js って何?
アセットパイプラインではCSS、JavaScript、画像などの「アセット」はapp/assetsディレクトリ配下にあるそれぞれのフォルダに置きます。
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
RailsでJavaScriptを使えるようにする
//= 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以降は以下の記事にあるような処理が必要です!
Deviseインストール直後のテストで失敗
自作のアプリにログイン機能を実装しようということで Devise を試してみました。
こちらの記事を参考にさせていただいたんですが、ひと通り設定が終わって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との結びつけ)
アップデート
上記の機能を備えたアプリケーションの完成後にアップデートという形で追加したい。
チェックを入れて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>
こんな感じにきちんと表示されました。
引き続きレイアウトを綺麗にしていきます。とりあえずデザインの知識が皆無なので、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に指定したビューテンプレートが描写されたら成功 |