【Rails5】Herokuへのデプロイ
基本的に【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】を参考にさせてもらった. でもやっぱりね, エラーは出るもんですよ.
赤文字で出たエラーがこれ.
Precompiling assets failed.
だからこればっかり検索してたんだけど, あまりいい情報が出てこなかった. それもそのはず. もっと手前で問題が発生していた.
Precompiling assets
がfail
したのは, 単なる結果だったというわけ. もっとログを読めばよかったね. ログ全体を見て気になった点は以下の文言.
Warning: Multiple default buildpacks reported the ability to handle this app. The first buildpack in the list below will be used.
Warning: the running version of Bundler (1.15.2) is older than the version that created the lockfile (1.16.4).
Uglifier::Error: Unexpected token name «of», expected punc «;». To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).
結論から言うと, 気にすべきなのは最後のエラーだけ. このエラーに関しては対処法が見つかった. config/environments/production.rb
を以下のように編集.
# config/environments/production.rb - config.assets.js_compressor = :uglifier + config.assets.js_compressor = Uglifier.new(harmony: true)
これでなんとかデプロイできたああ.
【Rails5】Autoprefixer doesn’t support Node v4.8.2. というエラーへの対処法
Rails5での開発中に
Autoprefixer doesn’t support Node v4.8.2.
というエラーに遭遇しました. (少なくとも私の環境では解決した)対処法をまとめました.
環境
対処法
検索してみると, Stack Overflowで質問されているのが見つかりました.
この解答の通り, mini_racer
というGemを追加したら直りました.
結局どんなエラーだったの?
経験が浅いなりに調べてみました. 結局どうしてmini_racer
を導入するとエラーが解消されたかははっきりわかっていません. しかし原因を調べていく上で自分にとって新しい知識が得られたのでまとめておきます. エラーが解消された理由を推測することはできるかもしれません.
Vender Prefixes とは
CSSにはVender Prefixesというものがありまして, これについては以下のブログにとてもわかりやすく書かれていました.
まだベンダープレフィックスで消耗してるの? – ysklog
少し引用しておきましょう.
「ベンダープレフィックス」とは、主に、CSS3で採用予定(草案段階)の機能を各ブラウザが先行的に実装し、その先行実装した機能を有効化させるために必要な各ブラウザ毎の「-moz-」や「-webkit-」という接頭辞のことをいいます。
つまり, 機能によってはブラウザごとに書かなくてはならない面倒なもの, ということですね.
Autoprefixerとは
面倒なVender Prefixesを自動でつけてくれるのが, Autoprefixer
というGemです. エラー文にも登場していますね.
ExecJSとは
ExecJSというGemにより, JavaScriptで書かれたコードをRubyから実行することができるようになります. ExecJSがサポートしているruntime
として, 以下などが挙げられています.
- therubyracer
- Node.js
- mini_racer
mini_racerとは
これはGitHubのReadmeを読んでもらうのが良いかと. GitHub - discourse/mini_racer: Minimal embedded v8
こちらも少し抜粋しておきましょう.
MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and Ruby.
It was created as an alternative to the excellent therubyracer. Unlike therubyracer, mini_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exhaustive testing simpler.
JavaScriptエンジンのV8とRubyの橋渡しをするものだそうですね. ExecJSはmini_racer
のようなものを使って, RubyからJavaScriptを実行するということなのでしょう.
エラーが解消された理由
すでに述べましたが, 正直よくわかっていません.
mini_racer
を導入することで, ExecJSはNode.js
ではなくmini_racer
を使うようになった, ということでしょうか.
【Rails5】`find`と`find_by`の違い
単一のレコードを取得する2つのメソッドの違いをまとめました. これについての記事はいくらでもあるけどね.
違い
find
id
で検索を行う.- レコードが見つからない場合はエラー.
User.find(1000) ActiveRecord::RecordNotFound (Couldn't find User with 'id'=10)
nil
を渡した場合も.
User.find(nil) ActiveRecord::RecordNotFound (Couldn't find User without an ID)
find_by
id
以外でも検索可能.
User.find_by(email: 'hello@world.com')
- レコードが見つからない場合は
nil
を返す.
User.find_by(id: 1000) #=> nil
nil
を渡した場合はIS NULL
で検索.
User.find_by(id: nil) User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
使い分け
404エラーを表示させたい場合はfind
例えばユーザーの詳細ページ(プロフィールページ)を考えてみましょう. コントローラでは以下のようにインスタンス変数を定義するでしょう.
def show @user = User.find(params[:id]) end
もしexample.com/users/1000
のように, 存在しないユーザーを表示させようとすると, 404エラーページを表示してくれます.
ここでfind_by
を使ってしまうと, @user = nil
となり, view側でエラーが発生してしまいますね.
nil
を期待する場合はfind_by
エラーを発生させたくない, そしてレコードがなければnil
を期待するという場合はfind_by
を使います. 例えばRails Tutorialで定義されているcurrent_user
ヘルパーメソッドの定義が以下です(ただし8章時点でのもの).
def current_user @current_user ||= User.find_by(session[:user_id]) end
ここでfind
を使うと, エラーが生じてしまい大変ですね.
id
以外で検索したいが, エラーを発生させたい場合
この場合はfind_by!
が用意されています. この記事を投稿して2日後に, ちょうどこのメソッドを使う機会があったので追記します.
例としてPost
という架空のモデルを考えます. Post
モデルはdraft
というboolean
型のattributeを持っているとします.
# schema.rb の省略版 create_table "post" do |t| t.string "title" t.text "body" t.boolean "draft" end
draft: true
によって下書きとしてPost
を保存できます. 他人は下書きにアクセスできないようにしたいとします. こんなときは次のように実装すればよさそうです.
class PostsController < ApplicationController def show @post = Post.find_by!(id: params[:id], draft: false) end end
下書き保存されたPost
にアクセスすると, まるでレコードが存在しないかのように振舞ってくれますね.
【Rails】データベース上のデータをシンボルで取得
前置き
私はRails Tutorialを1周しただけの初心者ですから, いろいろツッコミどころがあるかもしれません. ご容赦くださいませ.
やりたいこと
たとえばボクサーのデータベースシステムを作るとします(ボクシング全然知りませんが...). データベースには名前(name
)と階級(weight
)を保存するものとします. テーブル例は以下の通りです.
id | name | weight |
---|---|---|
1 | Muhammad Ali | heavy |
2 | Shinsuke Yamanaka | bantam |
この場合, 取得される階級は文字列型ですね.
(irb) boxer = Boxer.first (irb) boxer.weight #=> 'heavy'
しかしアプリケーション内において, 階級はシンボルで扱いたいとしましょう. つまり以下のような動作を実現させたいわけです.
(irb) boxer.weight #=> :heavy
実装
答えはわりと簡単です. アクセサメソッドを上書きすればいいのです.
class Boxer < ApplicationRecord def weight read_attribute(:weight).to_sym end end
実はモデルにはread_attribute
というメソッドが用意されています. これをつかってattribute
を取得し, シンボルに変換しています.
最後に
そもそも階級と自然数を対応付けて, データベースには自然数を保存すればいいのかもしれませんね. 独自にモジュールやクラスを定義すれば, 上でやりたいことは実現できます.
さらにボクシングの階級は全順序集合ですから, 自然数との対応付けがより意味をなします. つまり階級順に取得したいときは, 自然数の自然な順序を利用すればいいということです.
なんだかまとまらない記事でお粗末様でございました.
has_manyで関連づけられたモデルも一括保存する方法
Nested Attributes
によって, has_many
で関連づけられたモデルのデータも丸ごと保存する方法を紹介します.
モデル例
本記事では例として以下のようなモデルを扱うことにします.
class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end
実現したいこと
やりたいことは, User
といくつかのPost
をフォームから同時に登録することです. もう少しだけ正確に言うと, User
の登録時に, いくつかのPost
もまとめて登録できるようにします. つまりメインのデータはUser
ということです.
では実装すべきコードを順に見ていきましょう.
モデルの実装コード
以下のようにUser
モデルに1行追加します.
class User < ApplicationRecord has_many :posts accepts_nested_attributes_for :posts end
これで, Post
も一緒に保存するための受け入れ体制ができました.
またPost
モデルからは, (もし書いていたら)以下のバリデーション を取り除きます.
class Post < ApplicationRecord belongs_to :user # validates :user_id, presence: true end
当然湧いてくるであろう疑問
- なぜこのバリデーションを除く必要があるのか
- バリデーションはどのように定義すればいいのか
については最後に説明しますのでご心配なく.
コントローラの実装コード
次はコントローラです.
class UsersController < ApplicationController def new @uesr = User.new 2.times { @user.posts.build } end def create @user = User.new(user_params) if @user.save flash[:success] = 'User created!' redirect_to root_url else render 'new' end end private def user_params params.require(:user).permit(:name, :age, posts_attributes: [:title, :body]) end end
細かいところは仕様によって変わるでしょうが, 最低限上記のような実装は必要になると思います. 詳しく見ていきましょう.
new
アクション
new
アクションでのポイントは以下のコードですね.
2.times { @user.posts.build }
単にUser.new
で終わらせるのではなく, 空のPost
オブジェクトもbuild
しておく必要があります. 今回は例としてPost
を2つ作成しましたが, ここは仕様に左右されるところでしょう.
Strong Parameters
Post
モデルも保存するわけですから, Strong Parameters
においてこれを許可しておかなくてはなりません. 書き方は以下の通りです.
posts_attributes: [:title, :body]
attributes
は複数形です. また今回はUser has many Posts
という関係ですから, posts
も複数形となります.
create
アクション
ここまでできていれば, Post
オブジェクト込みのUser
オブジェクトを作成することは簡単です.
User.new(user_params)
User
オブジェクト単体で作成するときと同じですね.
コントローラはどんな形でパラメータを受け取るのか
モデルとコントローラが実装できれば, integration test
を書くことができます. そのためには, コントローラがどんな形でパラメータを受け取るのかを知る必要がありますね. その答えは以下の通りです.
params: { user: { name: 'Ruby on Rails', age: 14, posts_attributes: [ { title: 'hey', body: 'Lorem ipsum' }, { title: 'hi', body: 'Thank you!'} ] } }
ビューの実装コード
フォームのコード例です.
<%= form_for @user do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :age %> <%= f.number_field :age %> <%= f.fields_for :posts do |post_fields| %> <%= post_fields.label :title %> <%= post_fields.text_field :title %> <%= post_fields.label :body %> <%= post_fields.text_field :body %> <% end %> <%= f.submit "Submit" %> <% end %>
簡単ですね! コントローラで2.times
としているので, 自動的にPost
用のフォームが2つ作られます.
Post
モデルのバリデーション
はじめにPost
モデルから以下のバリデーションを取り除きました.
validates :user_id, presence: true
これについて深掘りしていきます.
なぜこのバリデーションを取り除く必要があるのか
accepts_nested_attributes_for
により子モデルであるPost
もまとめて保存できるようにした場合, 以下の流れで処理が進みます.
User
のバリデーションPost
のバリデーションUser
の保存処理Post
の保存処理
これは以下の記事を参考にしました. Rails5でnested attributesに詰まった話 - ウェブエンジニア珍道中
この記事にもあるように上のバリデーションを適用してしまうと,
Post
のバリデーションの段階で, 未だ作られていないUser
オブジェクトのid
を要求してしまうため, 必ずバリデーション失敗する.
ということが起きてしまうわけです.
どのようにバリデーションを定義すべきか
失敗してしまうからといって, user_id
の存在性を諦める必要はありません. ちゃんと別の方法が用意されています. 各モデルを以下のように編集しましょう.
class User < ApplicationRecord has_many :posts, inverse_of :user accepts_nested_attributes_for :posts end class Post < ApplicationRecord belongs_to :user, inverse_of :posts validates_presence_of :user end
これで一括保存とバリデーションを両立することができます.
Railsチュートリアル 第14章
14.1 Relationshipモデル
14.1.1
マイグレーションした際にschema.rb
に反映されませんでした. しかしバッファにはなんのエラーも出ませんでした. ちょっと調べてみましょう.
以下のコマンドでデータベースに接続できます.
$ rails db
私のようにDockerで環境構築している場合は次のようになるでしょう. ちなみに私はMySQLをRailsとは別のコンテナで起動させています.
$ docker-compose exec web rails db
するとパスワードが求められるので, database.yml
で指定したパスワードを入力します. すると以下のようなプロンプトが表示されます.
MySQL [myapp_development]>
myapp_development
はデータベース名ですね. マイグレーションしたテーブルが存在するかを確認してみます.
MySQL [myapp_development]> show tables; +-----------------------------+ | Tables_in_myapp_development | +-----------------------------+ | ar_internal_metadata | | microposts | | relationships | | schema_migrations | | users | +-----------------------------+ 5 rows in set (0.01 sec)
しかしschema.rb
に反映されていないのはおかしいので, マイグレーションをやりなおしましょう.
MySQL [myapp_development]> exit Bye $ docker-compose exec web rails db:rollback $ docker-compose exec web rails db:migrate
すると以下のようなエラーが出ました.
Mysql2::Error: Table 'relationships' already exists
そもそも最初のマイグレーションがうまくいってないから, ロールバックによってテーブルが削除されなかったようですね. では手動でテーブルを削除しましょう.
$ docker-compose exec web rails db MySQL [myapp_development]> drop table relationships; Query OK, 0 rows affected (0.02 sec) MySQL [myapp_development]> exit Bye
ここでもう一度マイグレーションをしたところ, ちゃんとschema.rb
に反映されました.
演習1
答えが問題文中に書かれていますね. 図14.7を見ると以下の配列が得られることがわかります.
[2, 7, 10, 8]
演習2
id
が2であるユーザーはid
が1のユーザーしかフォローしていません. したがってこのユーザーだけからなる配列が得られます.
したがってuser.followind.map(&:id)
は[1]
に等しいですね.
14.1.2
演習1, 2
省略.
14.1.3
演習1
たしかにバリデーションを解除してもテストは成功しました.
14.1.4
演習1, 2
以下の通りです.
>> first = User.first >> second = User.second >> first.following?(second) SELECT 1 AS one FROM `users` INNER JOIN `relationships` ON `users`.`id` = `relationships`.`followed_id` WHERE `relationships`.`follower_id` = 1 AND `users`.`id` = 2 LIMIT 1 => false >> first.follow(second) INSERT INTO `relationships` (`follower_id`, `followed_id`, `created_at`, `updated_at`) VALUES (1, 2, '2018-07-05 15:19:20', '2018-07-05 15:19:20') >> first.following?(second) => true >> first.unfollow(second) DELETE FROM `relationships` WHERE `relationships`.`id` = 1 >> first.following?(second) => false
14.1.5
演習1
>> user = User.first >> second = User.second >> last = User.last >> second.follow(user) >> last.follow(user) >> user.followers.map(&:id) SELECT `users`.* FROM `users` INNER JOIN `relationships` ON `users`.`id` = `relationships`.`follower_id` WHERE `relationships`.`followed_id` = 1 => [2, 100]
演習2, 3
前者はデータベースの中で数えていますが, 後者はRubyの配列として数えています. したがって前者のほうがパフォーマンスが高いですね.
user.followers.count SELECT COUNT(*) FROM `users` INNER JOIN `relationships` ON `users`.`id` = `relationships`.`follower_id` WHERE `relationships`.`followed_id` = 1 => 2 >> user.followers.to_a.count => 2
14.2 [follow]のWebインターフェース
14.2.1
演習1, 2
>> user = User.first >> user.following.count => 49 >> user.followers.count => 38
14.2.2
演習1, 2
省略.
演習3
プロフィールテストに書きました.
# test/integration/users_profile_test.rb test "stats" do log_in_as(@user) get root_path assert_select '#following', text: "#{@user.following.count}" assert_select '#followers', text: "#{@user.followers.count}" get user_path(@user) assert_select '#following', text: "#{@user.following.count}" assert_select '#followers', text: "#{@user.followers.count}" end
以下のように数字のままだとダメでした.
assert_select '#following', text: @user.following.count
14.2.3
演習1, 2
省略.
14.2.4
演習1
省略.
演習2
Follow, Unfollowどちらを押しても, users/show
が描画されています.
Rendered users/show.html.erb within layouts/application
14.2.5
演習1
Safariの開発メニューからJavascriptを無効にした場合もちゃんと動作しました.
演習2
省略.
14.2.6
演習1, 2
省略.
14.3 ステータスフィード
14.3.1
演習1
新しい順に取得するので, 以下のようになるでしょう.
user.feed.map(&:id) => [10, 9, 7, 5, 4, 2, 1]
14.3.2
演習1
feed
メソッドを以下のように変更すればいいですね.
def feed Micropost.where("user_id IN (?)", following_ids) end
この場合はmichael
自身の投稿を確認するテストで失敗します.
演習2
これは13章で実装した通りですね.
def feed Micropost.where("user_id = ?", id) end
するとlana
の投稿を確認するテストで失敗します.
演習3
feed
メソッドを以下のように変更すればいいですね.
def feed Micropost.all end
するとarcher
の投稿を確認するテストで失敗します.
14.3.3
演習1
以下の通りです.
test "feed on Home page" do get root_path @user.feed.paginate(page: 1) do |micropost| assert_match CGI.escapeHTML(micropost.content), response.body end end
演習2
ヒントから, 以下のmicropostがまずいんでしょうが, よくわかりませんでした.
tone: content: "I'm sorry. Your words made sense, but your sarcastic tone did not." created_at: <%= 10.minutes.ago %> user: lana
Railsチュートリアル 第13章
13.1 Micropostモデル
13.1.1
演習1
インスタンスを作成しただけなので, マジックカラムの中身はnil
ですね.
>> micropost = Micropost.new(content: "Lorem ipsum", user_id: User.first.id) >> micropost.created_at => nil
演習2
関連付けがされているモデル名がアクセサになっているんですね.
>> micropost.user User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => #<User id: 1, name: "Example User", email: "example@railstutorial.org", (省略)> >> micropost.user.name => "Example User"
演習3
データベースに保存すれば, そこでタイムスタンプが押されます.
>> micropost.save >> micropost.created_at => Wed, 04 Jul 2018 04:11:06 UTC +00:00
そういえばこの場合はリロードしなくていいんですね.
13.1.2
演習1
>> micropost = Micropost.new >> micropost.valid? => false >> micropost.errors.full_messages => ["User must exist", "User can't be blank", "Content can't be blank"]
演習2
>> micropost.content = "a"*141 >> micropost.valid? => false >> micropost.errors.full_messages => ["User must exist", "User can't be blank", "Content is too long (maximum is 140 characters)"]
13.1.3
演習1
マイクロポストが保存されました.
>> user = User.first >> micropost = user.microposts.create(content: "Lorem ipsum") (0.3ms) BEGIN Micropost Create (0.5ms) INSERT INTO `microposts` (`content`, `user_id`, `created_at`, `updated_at`) VALUES ('Lorem ipsum', 1, '2018-07-04 04:44:53', '2018-07-04 04:44:53') (0.8ms) COMMIT => #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2018-07-04 04:44:53", updated_at: "2018-07-04 04:44:53">
演習2
検索する場合はid
を渡さなくてはなりません.
>> user.microposts.find(micropost.id) => #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2018-07-04 04:44:53", updated_at: "2018-07-04 04:44:53"> >> user.microposts.find(micropost) ArgumentError (You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.)
演習3
13.1.2の演習ですでにUser.first
を使ってしまったので, 最後のマイクロポストを取得して比較しました.
>> user == micropost.user => true >> user.microposts.last == micropos => true
13.1.4
演習1, 2
以下のようになりました.
>> Micropost.first.created_at Micropost Load (2.6ms) SELECT `microposts`.* FROM `microposts` ORDER BY `microposts`.`created_at` DESC LIMIT 1 => Wed, 04 Jul 2018 04:44:53 UTC +00:00 >> Micropost.last.created_at Micropost Load (0.8ms) SELECT `microposts`.* FROM `microposts` ORDER BY `microposts`.`created_at` ASC LIMIT 1 => Wed, 04 Jul 2018 04:11:06 UTC +00:00
演習3
ユーザーに紐づけられたマイクロポストの配列を取得する際も, 降順で検索されていることがわかります.
>> user = User.first >> user.microposts Micropost Load (0.5ms) SELECT `microposts`.* FROM `microposts` WHERE `microposts`.`user_id` = 1 ORDER BY `microposts`.`created_at` DESC LIMIT 11
ユーザーを削除すると, マイクロポストも削除されます.
>> user.destroy >> Micropost.find(1)ActiveRecord::RecordNotFound (Couldn't find Micropost with 'id'=1)
13.2 マイクロポストを表示する
13.2.1
1つのマイクロポストを表示するパーシャルには, micropost
というローカル変数が使われています.
# app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> </li>
ではこのローカル変数はどこで定義されているのでしょうか. RailsガイドやAPIを見ると, 以下はすべて等価であることがわかります. ただしインスタンス変数@customer
は, Customer
モデルのインスタンスであることとします.
<%= render @customer %>
<%= render partial: "customer", object: @customer %>
<%= render partial: "customer", locals: { customer: @customer } %>
これらはapp/views/customers/_customer.html.erb
をレンダリングします. またこのパーシャル内ではパーシャル名の先頭からアンダースコアを除いたローカル変数が利用できます. つまりcustomer
というローカル変数が定義されるということです. この場合は
customer = @customer
のような定義がなされます.
Micropostの場合がまさにこの例の通りでしたね. 命名規則によって, シンプルに書くことができるようになっています.
演習1
いろいろ試してみました.
>> helper.time_ago_in_words(3.seconds.ago) => "less than a minute" >> helper.time_ago_in_words(3.minutes.ago) => "3 minutes" >> helper.time_ago_in_words(3.hours.ago) => "about 3 hours" >> helper.time_ago_in_words(3.days.ago) => "3 days" >> helper.time_ago_in_words(3.weeks.ago) => "21 days" >> helper.time_ago_in_words(3.months.ago) => "3 months" >> helper.time_ago_in_words(3.years.ago) => "about 3 years"
演習2
単数が複数かによって多少挙動が異なりますね.
>> helper.time_ago_in_words(1.second.ago) => "less than a minute" >> helper.time_ago_in_words(1.minute.ago) => "1 minute" >> helper.time_ago_in_words(1.hour.ago) => "about 1 hour" >> helper.time_ago_in_words(1.day.ago) => "1 day" >> helper.time_ago_in_words(1.week.ago) => "7 days" >> helper.time_ago_in_words(1.month.ago) => "about 1 month" >> helper.time_ago_in_words(1.year.ago) => "about 1 year"
演習3
以下の通りです.
>> microposts = User.first.microposts.paginate(page: nil) >> microposts.class => Micropost::ActiveRecord_AssociationRelation
直接取得するとこうなります.
>> m = Micropost.all >> m.class => Micropost::ActiveRecord_Relation
13.2.2
演習1, 2
>> (1..10).to_a.take(6) => [1, 2, 3, 4, 5, 6] >> (1..10).take(6) => [1, 2, 3, 4, 5, 6]
演習3
>> Faker::University.name => "The Harris College" >> Faker::University.name => "West Bergstrom Institute" >> Faker::PhoneNumber.phone_number => "1-252-219-7186" >> Faker::PhoneNumber.cell_phone => "1-248-219-0040" >> Faker::Hipster.words(4) => ["DIY", "knausgaard", "kitsch", "pitchfork"] >> Faker::Hipster.sentence => "Poutine butcher salvia green juice vhs biodiesel." >> Faker::ChuckNorris.fact => "The only pattern Chuck Norris knows is God Object." >> Faker::ChuckNorris.fact => "Chuck Norris doesn't need a java compiler, he goes straight to .war"
他にも多種多様なFakerが用意されています. ドキュメントを読むだけでも楽しいですね.
13.2.3
演習1, 2
簡単なので省略します.
13.3 マイクロポストを操作する
13.3.1
演習1
ブログ等を見てみると, 単にDRYに反するから, という解答でいいんですね. メソッドのオーバーライドという仕組みがあるので, コード自体に不備があるというわけではないですね. DRYに反していることが不備なのかもしれませんが...
13.3.2
エラーメッセージパーシャルにオブジェクトを渡すために,
object: f.object
という書き方が見られます. でもおかしく感じる点がありますね. object
キーワードは13.2.1でも登場しましたが, その時とは異なる点があります. それはパーシャル内におけるローカル変数の名前です.
たとえば
render 'micropost', object: @new_micropost
のような書き方をすれば, パーシャル内に定義されるローカル変数はmicropost
です.
しかしエラーメッセージパーシャルの中では, object
という名前の変数を使っていますね.
<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
パーシャル名と同じ名前のローカル変数は定義されていないのでしょうか. では実験してみましょう. print
デバッグのような方法を取りましょう. エラーメッセージパーシャルの中に, 以下の一行を加えます.
<%= p error_messages %>
パーシャル名と同名の変数の中身を見てやろうということです. これで不正な値をフォームから送ろうと思ってページ遷移したところ, エラーが出ました.
undefined local variable or method `error_messages'
やっぱりダメでしたね.
ということはrender
メソッドの挙動をまだよく理解できていないということです. 詳しくはまた今度調べます.
演習1
以下のようにパーシャルを作成しました.
# app/views/static_pages/home.html.erb <% if logged_in? %> <%= render 'home_logged_in' %> <% else %> <%= render 'welcome' %> <% end %>
# app/views/static_pages/_welcome.html.erb <div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %>
# app/views/static_pages/_home_logged_in.html.erb <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> </div>
13.3.3
演習1
以下の通りです.
INSERT INTO `microposts` (`content`, `user_id`, `created_at`, `updated_at`) VALUES ('Good morning.', 1, '2018-07-05 01:07:23', '2018-07-05 01:07:23')
演習2
すべて等しいです.
>> Micropost.where("user_id=?", user.id) == user.microposts => true >> Micropost.where("user_id=?", user.id) == user.feed => true
13.3.4
演習1, 2
省略.
13.3.5
演習1
無効な送信に対しては, 以下をコメントアウトしました.
# app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user default_scope -> { order(created_at: :desc) } validates :user_id, presence: true # validates :content, presence: true, length: { maximum: 140 } end
たしかにテストは失敗します.
有効な送信に対しては, 以下をコメントアウトしました.
# app/controllers/microposts_controller.rb def create @micropost = current_user.microposts.build(micropost_params) # if @micropost.save # flash[:success] = "Micropost created!" # redirect_to root_url # else # @feed_items = [] # render 'static_pages/home' # end end
たしかにテストは失敗します.
投稿の削除に対しては, 以下をコメントアウトしました.
# app/controllers/microposts_controller.rb def destroy #@micropost.destroy flash[:success] = "Micropost deleted" redirect_back(fallback_location: root_url) end
確かにテストは失敗します.
異なるユーザのプロフィールに対しては, コメントアウトというか, 以下のように編集しました.
# app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestanp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if true #current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You Sure?" } %> <% end %> </span> </li>
確かにテストは失敗します.
演習2
省略.
13.4 マイクロポストの画像投稿
13.4.1
Gemfileを編集したら, Dockerイメージに反映させます.
$ docker-compose build
これでイメージが更新され, 同時にGemもインストールされます. あとはいつも通りコンテナを起動すればOKです.
$ docker-compose up -d
演習1
省略.
演習2
テストコードは以下のように編集しました.
# test/integration/microposts_interface_test.rb test "micropost interface" do log_in_as(@user) get root_path assert_select 'div.pagination' assert_select 'input[type=file]' # 変更点 # invalid post assert_no_difference 'Micropost.count' do post microposts_path, params: { micropost: { content: "" } } end assert_select 'div#error_explanation' # valid post content = "This micropost really ties the room together." picture = fixture_file_upload('test/fixtures/rails.png', 'image/png') # 変更点 assert_difference 'Micropost.count', 1 do post microposts_path, params: { micropost: { content: content, picture: picture } } # 変更点 end assert @user.microposts.first.picture? # 変更点 assert_not flash.empty? assert_redirected_to root_url follow_redirect! assert_match content, response.body # 省略 end
13.4.2
チュートリアルの中ではextension_white_list
というメソッドが使われていますが, 自分のファイルを見ると, extension_whitelist
というメソッドでした. アンダースコアの分だけ異なります.
carrierwaveのドキュメントには後者が載っていました. バージョンが上がったことにより名前が変わったんでしょう.
演習1
そもそもそんな大きな画像ねーよと思いましたが, 10mb 画像
と検索したら出てきました.
jQueryによるアラートは出ましたが, 投稿自体はできてしまいました. Railsサーバーを再起動したところ, サーバー側のエラーが表示されるようになりました.
演習2
そもそもブラウザからファイルを指定する場合, 許可された拡張子を持つファイルしか選択できませんね.
13.4.3
Debian環境を使っているので, パッケージのインストールは以下のコマンドで行いました. パッケージ名は全て小文字でOKです.
apt-get install -y imagemagick
Dockerイメージに反映させたい場合は, Railsコンテナ用のDockerfileを編集します.
FROM ruby:2.5.1 RUN apt-get update -qq && apt-get -y install \ vim \ build-essential \ libpq-dev \ nodejs \ mysql-client \ imagemagick # ここを追加! RUN mkdir /myapp COPY Gemfile /myapp COPY Gemfile.lock /myapp WORKDIR /myapp RUN gem install bundler && bundle install ENV COLUMNS=200 LINES=50
DockerfileやGemfileを変更した場合は以下のコマンドを実行します.
$ docker-compose build
あとはいつも通りコンテナを起動すればOKですね.
$ docker-compose up -d
演習1
省略.
演習2
エラー出ない...
rails.png
は小さい画像ですから, 問題なくアップロードされてしまいますよね. だからエラーは出なくて正解な気がしてしまうのですが. とはいえテスト環境ではリサイズが行われないようにしておきます.
13.4.4
この節はやっていません.