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
この節はやっていません.