【Rails5】Herokuへのデプロイ

基本的に【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】を参考にさせてもらった. でもやっぱりね, エラーは出るもんですよ.

赤文字で出たエラーがこれ.

Precompiling assets failed.

だからこればっかり検索してたんだけど, あまりいい情報が出てこなかった. それもそのはず. もっと手前で問題が発生していた.

Precompiling assetsfailしたのは, 単なる結果だったというわけ. もっとログを読めばよかったね. ログ全体を見て気になった点は以下の文言.

  • 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で質問されているのが見つかりました.

node.js - Ruby on Rails - Autoprefixer doesn’t support Node v4.9.1. Update it. How to fix? - 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で環境構築している場合は次のようになるでしょう. ちなみに私はMySQLRailsとは別のコンテナで起動させています.

$ 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

この節はやっていません.