第3章
provideとyield
home.html.erbとかで
<% provide(:title, "Help") %>
とやって、yieldで表示するのは
<%= yield(:title) %> | Ruby on Rails Tutorial Sample App
以下と同じなんじゃないかと思うがprovide, yieldを使う利点はなんだろうか。
<% @title="Help" %>
とやって、
<%= @title %> | Ruby on Rails Tutorial Sample App
layoutファイル(appication.html.erb)を使う時にファイル間でprovideじゃないと参照できないのかなと思ったけど一緒だった。一応メモして残しておこう。
minitest reportersとguard
高度なテストのセットアップを行っておく。テスト結果が見やすくなるのと、ファイル監視して自動でテストを回してくれるらしい。
Gemfile
に以下設定済みなので使用できる準備は整っている
group :test do
・・・・・
gem 'minitest-reporters', '1.3.8'
gem 'guard', '2.16.2'
gem 'guard-minitest', '2.4.6'
end
minitest reporters
minitest-reportersは test_helper.rb
に以下を追加
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
+require "minitest/reporters"
+Minitest::Reporters.use!
これだけで使える。テスト結果が見やすくなる。
- before
$ rails t
Running via Spring preloader in process 2626
Run options: --seed 11806
# Running:
.....
Finished in 2.249599s, 2.2226 runs/s, 4.0007 assertions/s.
5 runs, 9 assertions, 0 failures, 0 errors, 0 skips
- after
[email protected]:/src/sample_app# rails t
Running via Spring preloader in process 2750
Started with run options --seed 54151
5/5: [=======================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.61358s
5 tests, 9 assertions, 0 failures, 0 errors, 0 skips
guard
- 初期化
$ bundle exec guard init
08:56:00 - INFO - Writing new Guardfile to /src/sample_app/Guardfile
08:56:00 - INFO - minitest guard added to Guardfile, feel free to edit it
Guardfileをチュートリアル用に最適化 (チュートリアルのままコピペ)
# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
watch(%r{^test/(.*)/?(.*)_test\.rb})
watch('test/test_helper.rb') { 'test' }
watch('config/routes.rb') { interface_tests }
watch(%r{app/views/layouts/*}) { interface_tests }
watch(%r{^app/models/(.*?)\.rb}) do |matches|
"test/models/#{matches[1]}_test.rb"
end
watch(%r{^app/controllers/(.*?)_controller\.rb}) do |matches|
resource_tests(matches[1])
end
watch(%r{^app/views/([^/]*?)/.*\.html\.erb}) do |matches|
["test/controllers/#{matches[1]}_controller_test.rb"] +
integration_tests(matches[1])
end
watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
integration_tests(matches[1])
end
watch('app/views/layouts/application.html.erb') do
'test/integration/site_layout_test.rb'
end
watch('app/helpers/sessions_helper.rb') do
integration_tests << 'test/helpers/sessions_helper_test.rb'
end
watch('app/controllers/sessions_controller.rb') do
['test/controllers/sessions_controller_test.rb',
'test/integration/users_login_test.rb']
end
watch('app/controllers/account_activations_controller.rb') do
'test/integration/users_signup_test.rb'
end
watch(%r{app/views/users/*}) do
resource_tests('users') +
['test/integration/microposts_interface_test.rb']
end
end
# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
if resource == :all
Dir["test/integration/*"]
else
Dir["test/integration/#{resource}_*.rb"]
end
end
# インターフェースが該当するすべてのテストを返す
def interface_tests
integration_tests << "test/controllers/"
end
# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
"test/controllers/#{resource}_controller_test.rb"
end
# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
integration_tests(resource) << controller_test(resource)
end
以下で実行できるが、
$ bundle exec guard
エラー出る。。
/usr/local/bundle/gems/guard-2.16.2/lib/guard/guardfile/evaluator.rb:93:in `evaluate': invalid byte sequence in US-ASCII (ArgumentError)
どうもdocker環境だとエラーになるっぽい。
参考) https://teratail.com/questions/171538
環境変数を設定するといけるらしいのでそのようにする。Dockerfileにも書いておいたほうがいいのか。
export LANG=C.UTF-8
なぞだが、安心してつかっていいみたいな。。
参考) https://qiita.com/methane/items/dac75ef5019b311a0f10
とりあえずこれでguardも実行できた。
第4章
シングルクォートとダブルクォートは式展開(#{var})ができるかどうかが違う
>> city="岡山市"
>> puts city
岡山市
=> nil
>> pref = "岡山県"
>> pref
=> "岡山県"
>> puts "#{city} #{pref}"
岡山市 岡山県
=> nil
>> puts "#{city}\t#{pref}"
岡山市 岡山県
=> nil
>> puts '#{city}\t#{pref}'
#{city}\t#{pref}
=> nil
後続if やunlessもrubyに特徴的
>> x = "exs"
>> puts "x is not empty" if !x.empty?
x is not empty
>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
メソッドの返り値はreturnが無くても最後の式が返される。
kotlinもreturnが省略できたような
でも書いた方がいいよね。
moduleはincludeでclassでそのメソッドを使えるようになるが(mixin), railsのhelperクラスはincludeしなくても
どこからでも使えるようになるとのこと。おそらくそのcontrollerの名前に合ったhelperクラスである必要がありそう。
第5章 Fill in the layout
メモしていたけど、Wordpressのセッション切れていて消えたorz
書き始める前にちゃんとログインしよう。。
とりあえず、この章は
- asset pipeline (jsやcss, 画像などのリソースをいい感じで管理してくれる)
- _header.html.irbのようなパーシャルを使ってrenderメソッドで部分的にhtmlを読み込もう
- Bootstrapでいい感じにデザイン、cssの上位互換scssで効率的にスタイリング
- 名前付きルート
- リンクをシミュレートする統合テスト
第6章 Modeling Users
validation
class User < ApplicationRecord
validates :name, presence: true
end
引数の最後がハッシュなら{}は省略できる。
そのため以下と一緒
class User < ApplicationRecord
validates :name, {presence: true}
end
reload
保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みする。
逆に保存後の値を確かめたい時はsave後に実行すると良さそう。
# saveせずにreload
>> user.email = "[email protected]"
>> user.reload.email
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 2], ["LIMIT", 1]]
=> "[email protected]"
# save後にreload
>> user.email = "[email protected]"
>> user.save
(0.7ms) SAVEPOINT active_record_1
User Exists? (1.1ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT 3 [["email", "[email protected]"], ["id", 2], ["LIMIT", 1]]
User Update (1.1ms) UPDATE "users" SET "email" =1, "updated_at" = 2 WHERE "users"."id" =3 [["email", "[email protected]"], ["updated_at", "2020-06-29 02:19:25.302483"], ["id", 2]]
(0.6ms) RELEASE SAVEPOINT active_record_1
=> true
>> user.reload.email
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 2], ["LIMIT", 1]]
=> "[email protected]"
secure password
Userモデルに has_secure_password
メソッドを追加する。
- usersテーブルに
password_digest
カラムが存在することが条件 bcrypt
gemが必要- 仮想的な
password
属性とpassword_confirmation
属性が追加される。- この2つの属性が合致するかのvalidationは自分で組む必要があり?
password_confirmation
は nil だと userはvalidになるけど、なにか値が入っていないと、password
と合っていないとinvalidになるっぽい
>> user.password = "aaaaaa"
>> user.password_confirmation
>> user.valid?
User Exists? (1.0ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT 3 [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> true
>> user.password_confirmation = "bbbbbb"
>> user.valid?
User Exists? (1.5ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" != 2 LIMIT3 [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> false
>> user.errors.messages
=> {:password_confirmation=>["doesn't match Password"]}
>> user.password_confirmation = "aaaaaa"
>> user.valid?
User Exists? (1.6ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT $3 [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> true
>> user.errors.messages
=> {}
update_attribute は 認証無しで更新できる?
has_secure_passwordを使うようになったので、save時にパスワードによる認証が必要になったっぽい。が update_attributeを使うとパスワード無しで更新できるっぽい
>> user.update_attribute(:name, "higehige")
(0.6ms) BEGIN
User Update (1.3ms) UPDATE "users" SET "name" = 1, "updated_at" =2 WHERE "users"."id" = 3 [["name", "higehige"], ["updated_at", "2020-06-29 03:01:03.002078"], ["id", 5]]
(2.2ms) COMMIT
=> true
>> user.name
=> "higehige"
# rails console
>> user = User.find_by(email: "[email protected]")
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."email" =1 LIMIT $2 [["email", "[email protected]"], ["LIMIT", 1]]
>> user.name
=> "higehige"
第7章 Sign up
debugger
debugger
というワードを埋め込むとそこで実行をストップしてデバッグできるらしい。
byebug gemの機能。
def show
@user = User.find(params[:id])
debugger
end
DBリセット
途中でテスト的に登録したユーザーを全部消すため、
$ rails db:migrate:reset
を行ったが、以下のようなエラーになったよ。
PG::ObjectInUse: ERROR: database "sample_app_development" is being accessed by other users
DETAIL: There are 2 other sessions using the database.
セッションが2つまだ残っているということ。
[email protected]:/src/sample_app# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 05:25 pts/0 00:00:00 irb
root 6 0 0 05:25 pts/1 00:00:00 /bin/bash
root 11 0 0 05:26 pts/2 00:00:00 /bin/bash
root 85 1 0 05:49 pts/1 00:00:00 spring server | sample_app | started 46 mins ago
root 87 85 0 05:49 ? 00:00:03 spring app | sample_app | started 46 mins ago
root 155 85 0 06:12 ? 00:00:03 spring app | sample_app | started 23 mins ago
root 253 6 0 06:26 pts/1 00:00:00 /usr/local/bin/ruby bin/rails c
root 255 87 0 06:26 ? 00:00:00 rails_console
root 411 6 0 06:34 pts/1 00:00:00 /usr/local/bin/ruby bin/rails c
root 415 87 0 06:34 ? 00:00:00 rails_console
root 518 11 0 06:36 pts/2 00:00:00 ps -ef
# 確かに rails consoleのセッションが残っていたっぽい。PIDの255と415を以下のようにして消すとresetが成功したよ
[email protected]:/src/sample_app# kill -9 255
content_tag
content_tagを使うと ごちゃごちゃしたerbがすっきりする
- before
<% flash.each do |message_type, message| %>
"><%= message %>
<% end %>
- after
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
SSL設定
本番環境でSSLを有効にするにはproduction.rbの以下をtrueにするだけでよいらしい
ただし、Herokuのサブドメイン化の場合のみ
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
自分のドメインでHeroku上で有効にするには以下を参照とのこと
https://devcenter.heroku.com/articles/ssl
本番環境のDB設定
database.ymlに記載した情報はHerokuでいつ設定したのかなと気になってしまった。
SAMPLE_APP_DATABASE_PASSWORD
環境変数にPostgresのパスワードを設定しているっぽいが、Herokuでどうやって設定しているのか。
↓で見てみたが設定されていないっぽい。空でログインできる?
$ heroku run printenv
どうやら Herokuで自動的にDBの接続情報が設定され、
環境変数 DATABASE_URL
の設定の方が 優先されるっぽい。
Railsガイドの以下あたりに記載ありました。
確かに以下のような感じでHerokuに設定されていました。(それぞれプレースホルダー当ててます)
$ heroku run printenv DATABASE_URL
postgres://{username}:{password}@{appurl}:5432/{database}
database.ymlに設定した以下はいったい。。
production:
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://railsguides.jp/configuring.html#データベース接続をプールする
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
database: sample_app_production
username: sample_app
password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>
第8章 Basic Login
セッションをRESTfulなリソースとしてモデリング
今まで作ってきたUsersリソースと同じようにモデリングするが、保存先がRDBではなくCookiesになる。
rails routes
全ルーティングを確認できる。grepでパイプすると便利そう
$ rails routes | grep user
signup GET /signup(.:format) users#new
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
$ rails routes | grep session
sessions_new GET /sessions/new(.:format) sessions#new
login GET /login(.:format) sessions#new
POST /login(.:format) sessions#create
logout DELETE /logout(.:format) sessions#destroy
こうやって見ていると、sessionsの1番目と2番目が同じメソッドを指していることに気づいた。
rails g controller Sessions new で自動的にできたroutingを消していなかった。
routes.rb
Rails.application.routes.draw do
get 'sessions/new' # これいらない
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
end
sessions/newを消すと、想定通りになった。session#createやsessions#destroyはまだSessionsControllerにメソッドを書いていないが、出力されていることに注意しておく。
# rails routes | grep session
login GET /login(.:format) sessions#new
POST /login(.:format) sessions#create
logout DELETE /logout(.:format) sessions#destroy
Login form
セッションはActive Recordオブジェクトではないので、以前のようにActive Recordがよしなにエラーメッセージを表示してくれるということは期待できません。
なるほど、SignupではUsersのActive Recordがエラーの場合、users.errors.messagesにエラーをセットしてくれていた。そして以下のようにフォームにdivタグまでつけてくれていた。
第7章 7.3.3のあたり
Railsは、無効な内容の送信によって元のページに戻されると、CSSクラスfield_with_errorsを持ったdivタグでエラー箇所を自動的に囲んでくれます。
Active Recordの恩恵があったわけだと思うが、それがない場合どうすればいいのか確認したい。
レイアウトを変更する
ドロップダウンメニュー作成のためjqueryとbootstrapをjs側で使えるようにする
$ yarn add [email protected] [email protected]
こんな感じでpackage.jsonに追加される
$ git diff
diff --git a/package.json b/package.json
index 85e5f83..2e8305d 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,8 @@
+ "bootstrap": "3.4.1",
+ "jquery": "3.4.1",
"turbolinks": "^5.2.0"
diff --git a/yarn.lock b/yarn.lock
index ce9a0b7..58346db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1457,6 +1457,11 @@ [email protected]^1.0.0, [email protected]~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
[email protected]:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72"
+ integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==
+
[email protected]^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -3915,6 +3920,11 @@ [email protected]^25.4.0:
merge-stream "^2.0.0"
supports-color "^7.0.0"
[email protected]:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
+ integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
+
[email protected]^2.1.8:
version "2.6.1"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.1.tgz#c328374225d2e65569791ded73c258e2c59334c7"
第9章 Advanced Login
パスワードとトークンとの一般的な違いは、パスワードはユーザーが作成・管理する情報であるのに対し、トークンはコンピューターが作成・管理する情報である点です。
なるほど
remenber_digest
記憶トークンに使う文字列は SecureRandom.urlsafe_base64
メソッドを使う。
ランダムな22文字の文字列を生成してくれる。
# rails c
Running via Spring preloader in process 229
Loading development environment (Rails 6.0.3)
>>
>> SecureRandom.urlsafe_base64
=> "fCjzgRW45nbwbdHPorun5Q"
>> SecureRandom.urlsafe_base64
=> "CGufSw6xxEHd-JSNPvk4ew"
>> SecureRandom.urlsafe_base64
=> "KZN9loW9D8Gbma8ZUoVp7A"
>> SecureRandom.urlsafe_base64
=> "2cZefkB_VwX6rdQr2kSEpg"
>> SecureRandom.urlsafe_base64
=> "kUfkkTZUcYSSlabcaG95dQ"
>> SecureRandom.urlsafe_base64.length
=> 22
>> SecureRandom.urlsafe_base64.length
=> 22
>> SecureRandom.urlsafe_base64.length
=> 22
ソルト
同じパスワードでもソルトが違えば、ハッシュ化した後の値は変わる。
railsのbcryptのハッシュ化はソルト化されている。
ソルトは
念には念を入れて「塩ひとつまみ」を加えるというイメージであり、英語の「take it with a grain of salt (半分疑ってかかる)」という言い回しが語源
らしい。おもしろい。
クラスメソッドの定義方法
今のところ3つ出てきた。
1. User.method_name (クラス名をつける)
def User.digest(string)
・・
・・
end
2. selfをつける(インスタンスのselfとは違うのでややこしい)
def self.digest(string)
・・
・・
end
3. class
class << self
def digest(string)
・・
end
def new_token
・・
end
end
クラス名.メソッド名の1がわかりやすい気がするが、出てきたら混乱しないようにしたい。
セッションとcookies
ユーザーIDをcookiesに保存する。
cookies[:user_id] = user.id
このようにすると生のユーザーIDが保存されてしまうので署名付きcookiesを使って暗号化する。
cookies.signed[:user_id] = user.id
permanentメソッドでcookiesが20年間保存されるらしい。
cookies.permanent.signed[:user_id] = user.id
cookiesからuserを取り出すには
User.find_by(id: cookies.signed[:user_id])
cookies.signedで取り出される際に復号化されるらしい。
assignsメソッド
テストでControllerで定義したインスタンス変数の仮想の属性にアクセスするためのメソッド
Controller#createで以下のように@user変数を定義していると
@user = User.find_by(email: params[:session][:email].downcase)
テストでは
assigns(:user).remember_token
のようにシンボルを使ってアクセスできる。cookiesに保存したセッショントークンが正しいかのテストは以下
assert_equal cookies[:remember_token], assigns(:user).remember_token
第10章 Updating, showing, and deleting users
POSTとPATCHの区別
form_withのコードはusers/new.html.erbとusers/edit.html.erbで両方全く同じだが、railsはPOSTリクエストとPATCHリクエストをどうやって区別しているのかが説明してある。
Active Recordの new_record?
メソッドを使って新規レコードか既存レコードか区別しているらしい。
$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
target="_blank"のセキュリティ脆弱性
リンクへの遷移にこのまま使うと
リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう
という脆弱性が含まれているらしい。特に自分のサイトじゃないサイトへ誘導する際は気をつけておく。フィッシングの詐欺に合う場合があるらしい。
対策は以下行うだけなので簡単
aタグのrel(relationship)属性に、"noopener"と設定するだけ
参考:
https://edge.sincar.jp/web/target_blank-security/
認可(authorization)
ウェブアプリケーションの文脈では、認証(authentication)はサイトのユーザーを識別することであり、認可(authorization)はそのユーザーが実行可能な操作を管理することです。
railsでは以下のように before_action で 実現される。
beforeフィルターを使って、認可(アクセス制御)を実現した
この章のまとめより
パーシャルのリファクタリング
railsの類推力というか、魔法っぽいところでこれは知らないと絶対使えないと思ったのでメモしておく。
ユーザー一覧の表示部分のリファクタ前
<% @users.each do |user| %>
-
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% end %>
リファクタ1
<% @users.each do |user| %>
<%= render user %>
<% end %>
render user
とやることで、以下のように切り出した_user.html.erb
のパーシャルファイルを読み込んでくれるようになる。
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
ここまでは、おーすごいと便利に感じる。
リファクタ2
<%= render @users %>
ユーザー一覧の@users変数をまるごとrenderに渡すだけで同じようにrenderingしてくれる。
もうここまでくると魔法みたいでよくわからん。知ってないと使えない。
render @usersを実行すると、自動的に_user.html.erbパーシャルを参照し、各ユーザーをコレクションとして表示する
この章のまとめより
第11章 Account activation
activation_token, activation_digest
before_create :create_activation_digest
private
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
before_create コールバックでUserインスタンスが作られるとこれら2つの属性が作成されるが、
rails consoleからみる感じではUser.newの段階で作成されていない。
>> user = User.new
>> user.activation_token
=> nil
activation_digestは保存されているみたいだった。
>> user1 = User.first
User Load (1.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 [["LIMIT", 1]]
>> user1.activation_token
=> nil
>> user1.activation_digest
=> "2a10tf69UDk4bdZnv.rTTaE/SOsRZjJFnZVjUUsqhWK5Vw186J73a.UdS"
ということはインスタンスが作成された時ではなく、DBに保存する時にbefore_createはコールバックされるっぽい。ではbefore_saveとの違いは?
>> user = User.new(name: "hige", email:"[email protected]", password: "higehige", password_confirmation: "higehige")
>> user.valid?
User Exists? (0.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) LIMIT2 [["email", "[email protected]"], ["LIMIT", 1]]
=> true
>> user.activation_token
=> nil
>> user.activation_digest
=> nil
>> user.save
(0.7ms) BEGIN
User Exists? (1.9ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) LIMIT2 [["email", "[email protected]"], ["LIMIT", 1]]
User Create (1.0ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (1,2, 3,4, 5,6) RETURNING "id" [["name", "hige"], ["email", "[email protected]"], ["created_at", "2020-07-15 15:07:49.661033"], ["updated_at", "2020-07-15 15:07:49.661033"], ["password_digest", "2a10XqT7ckKa5eP6YQ1n0AkZ5uG1i.b68sr9tR5L4bxp9vRoob3rW6C4u"], ["activation_digest", "2a10lomFN7UICj4/xaD6PDveHewamTg3R1hGmWTYnL6cn6GbqRrN90LSy"]]
(6.0ms) COMMIT
=> true
>> user.activation_token
=> "rUZjwL_6ADiO5rqMNgtRxg"
>> user.activation_digest
=> "2a10$lomFN7UICj4/xaD6PDveHewamTg3R1hGmWTYnL6cn6GbqRrN90LSy"
こちらによると、before_createはbefore_saveの後に、新規データの場合に実行されるらしい。
https://www.techscore.com/blog/2012/12/25/rails%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%BE%E3%81%A8%E3%82%81/
オブジェクトが登録されるとき (new_record? が true のとき) は before_create が実行されます。
オブジェクトが更新されるとき (new_record? が false のとき) は before_update が実行されます。
登録と更新のどちらの場合にも同じ処理を行うのであれば、before_save を使用すると便利です。
本文にも
before_createコールバックの方はユーザーが作成される前に呼び出されることです
と書いてあったが、User.newでインスタンスを作成後にコールバックされるように捉えてしまったので混乱した。
assignメソッドの罠
進めていると途中から以下エラーでテストが通らなくなった。
ERROR["test_login_with_remembering", #, 2.0458919250013423]
test_login_with_remembering#UsersLoginTest (2.05s)
NoMethodError: NoMethodError: undefined method `remember_token' for nil:NilClass
test/integration/users_login_test.rb:58:in `block in '
45/45: [=============================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
9章で作成したテストのusers_login_test.rbの以下 assigns(:user)部分でエラー
test "login with remembering" do
log_in_as(@user, remember_me: '1')
# assert_not_empty cookies[:remember_token]
assert_equal cookies[:remember_token], assigns(:user).remember_token
end
assigns(:user) が nil になっているのが原因
そもそもassignsって何か?
たまたま本章の users_signup_test.rbで出てきていたのでわかった。
このassignsメソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになります。
sessions_controller.rb#createメソッドにactivateの判定を加えた時にテキストの内容をコピペして上書きしてしまったのが原因だったっぽい。
第9章の演習でremember_tokenをテストするコードに改良した。
その時にローカル変数userをインスタンス変数@userにしてまさに、テストからassign(:user)でコントローラーの@userにアクセスできるようにした。
しかし、11章でテキストのコードをコピペしてしまうと、第9章で行った改良が元に戻ってしまう。
そのためテストからassign(:user)でコントローラーのインスタンス変数@userにアクセスしようとしてもnullになってしまっていた。
このassignメソッドはRails 5以降で非推奨になっているっぽい。
rails-controller-testing というgemを使うと現在でも利用できるらしい。実際にrails tutorialでもgemに追加されていた。
group :test do
・・・
gem 'rails-controller-testing', '1.0.4'
・・・
以下のようにコメントを書いておけば思い出せたかもしれない。どこの章、節で編集したかわかるように番号も記載しておくことにする。
test "login with remembering" do
log_in_as(@user, remember_me: '1')
# 9.28 remember_token属性を検証するためにSessionController#createで定義した@userにアクセスするためassignsメソッドを使う
# assert_not_empty cookies[:remember_token]
assert_equal cookies[:remember_token], assigns(:user).remember_token
end
ここでlog_in_asの引数の@userは以下のようにテストのsetupで代入したもので、また別のものなので注意。これも混乱の原因の一つ。
def setup
@user = users(:michael)
end
さらに忘れないように SessionsController#createにもコメントしておく。そもそもここを見てメソッド内でしか使われていなさそうだし、ローカル変数でいいよねって納得してしまったのでテキストのコピペを疑わず行ってしまったのも罠の一つだったので。。
def create
# user_login_testにてassignでアクセスできるようにインスタンス変数で定義する
@user = User.find_by(email: params[:session][:email].downcase)
・・・
error in sending mail on production
2020-07-16T02:08:22.386245+00:00 app[web.1]: I, [2020-07-16T02:08:22.386172 #17] INFO -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Completed 500 Internal Server Error in 304ms (ActiveRecord: 19.6ms | Allocations: 6734)
2020-07-16T02:08:22.387578+00:00 app[web.1]: F, [2020-07-16T02:08:22.387503 #17] FATAL -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f]
2020-07-16T02:08:22.387579+00:00 app[web.1]: [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Net::SMTPAuthenticationError (535 Authentication failed: account disabled
DBにはユーザー登録されていたので、もう一回検証するために、DBをリセットする。
# DBリセットして
heroku pg:reset DATABASE
# テーブル作り直し heroku run rails db:migrate
# データ入れる
heroku run rails db:seed
エラー内容でググると以下の方の記事が見つかった。ありがとうございます。
https://qiita.com/yuppymam/items/50f893df99cfb1683234
しかし、アプリを作り直しても変わらない。。
Herokuにチケットを上げてサポートを求める。。
Dear support,
I am not sure the reason my sendgrid account is flozen but
when I send mail via my app, the log below is output.
Could you activate my sendgrid account ?
the log below was output.
2020-07-16T02:08:22.386245+00:00 app[web.1]: I, [2020-07-16T02:08:22.386172 #17] INFO -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Completed 500 Internal Server Error in 304ms (ActiveRecord: 19.6ms | Allocations: 6734)
2020-07-16T02:08:22.387578+00:00 app[web.1]: F, [2020-07-16T02:08:22.387503 #17] FATAL -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f]
2020-07-16T02:08:22.387579+00:00 app[web.1]: [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Net::SMTPAuthenticationError (535 Authentication failed: account disabled
My sendgrid usename is below
$ heroku config:get SENDGRID_USERNAME
› Warning: heroku update available from 7.42.0 to 7.42.1.
******@heroku.com
Thank you in advance.
以下Sendgridにログインしようとしても、
https://app.sendgrid.com/login
以下表示される。ログイン/パスワードが違うと別のメッセージなので、また違うエラーなのかな。
You are not authorized to access SendGrid, please contact Support.
以下SendGridにも問い合わせてみる
My sendgrid account is below.
******@heroku.com
when I login, the messeage below shown.
You are not authorized to access SendGrid, please contact Support.
Probably my account is flozen. Could you activate it ?
When I send emai via herokuapp, the log below shown.
Net::SMTPAuthenticationError (535 Authentication failed: account disabled
Thank you in advance.
第12章 Password reset
pathとurl
前章までにもたびたび出てきたが、名前付きルートのpathとurlはどう違うのか、だいたいpathを使って、リダイレクトの時にはurlを使うことが決まりになったいることぐらいしか理解していないかった。
第5章の 5.3.2 RailsのルートURL にちゃんと書いてあった。こういうのを理解していないと。
例えばルートURLを定義すると、root_pathやroot_urlといったメソッドを通してURLを参照することができます。ちなみに前者はルートURL以下の文字列を、後者は完全なURLの文字列を返します。
基本的には_path書式を使い、リダイレクトの場合のみ_url書式を使うようにします。これはHTTPの標準としては、リダイレクトのときに完全なURLが要求されるためです。
なるほど、アカウント有効化のURLをメールのリンクにする時は完全なURLが良いのでurlをつかっていたのか。
form_withのscopeとは
<%= form_with(url: password_resets_path, scope: :password_reset, local: true) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
これもここで初めて出てきたものではなくおさらい。
12.1.2の演習にもこのscopeの値について問題が出ている。
form_withメソッドで、@password_resetではなく:password_resetを使っている理由を考えてみましょう。
scopeはなんだっけ?という話と、"@password_resetではなく:password_reset" を使っている理由というのは別の話で、一緒に考えるものではないかもしれない。
まずscopeは何かという話だけども、理解したのはフォーム送信先のactionでparamとして受け取ることのできるハッシュの名前になるということ。
scope: :password_reset と書くと、
フォーム送信先のアクションである PasswordResetsController#create で入力値を以下のように取得できるっぽい。
params[:password_reset][:email]
生成されたhtmlを見ても、name="password_reset[email]" になっている。
scopeを pswd_rst に変えた時、
なるほど、ではscopeを記載しなかった場合、
なるほど、scopeはparamとして受け取ることができるパラメータの箱の名前みたいなものと理解した。
なのでおそらく、scopeを記載しなかったら取得する時も、以下でいけるのだろう。
この辺はテストで確かめることにする。
params[:email]
ここからは
"@password_resetではなく:password_reset" を使っている理由 について
混乱する一因として、
サインアップやログインの時は@userを使っていたが、そもそもそれはscopeではなくてmodelに使っていたからだろう。
/signupのusers#newと/users/1/editのusers#edit ではformを共有してパーシャル(_form.html.erb)に切り出していたが、ここでは以下のようform_withのmodelに@userを使っていた。
<%= form_with(model: @user, local: true) do |f| %>
・・・
<% end %>
このmodel: @userのようにせず、今回はシンボルを使ってscopeに設定しているので何を比べているのかわかりにくい。model: @password_resetでなく、url, scopeのセットを使っているのはなぜか と聞いてくれるとわかりやすかったのではないかと思った。
<%= form_with(url: password_resets_path, scope: :password_reset, local: true) do |f| %>
・・・
<% end %>
modelに@userを指定すると以下のようにrailsがよしなにルーティングしてくれる。
- @userがまだ存在しないときにpostメソッド
- @userが存在している場合にpatchメソッド(実際にはpostでhiddenフィールドでpatchを指定して擬似的に作成)
今回はpassword_resetというモデルは用意していなく、urlを指定してその名前付きルートに対応するControlloerのアクション、今回では以下を使いたいためというのが演習の答えになるだろうか?
password_resets_path => PasswordResetsController#create
第13章 User microposts
micropostsのfixturesのテストデータを消しとかないとテスト通らない
まずこの章ではrailsコマンドでMicropostモデルを作成するが、
$ rails generate model Micropost content:text user:references
その時作られる /test/fixtures/microposts.yml の中身を消しておかないと、最初の方のテストに通らない。
もし、このテストデータを使うのであれば、中身は以下のような感じなので、user: oneやtwoを作成しておかないといけない。
one:
content: MyText
user: one
two:
content: MyText
user: two
Userモデルを作成した時は fixturesのymlファイルの中身を消しておくこと、とちゃんと書いてあった気がするけど、今回は自分で気づけよ ということかもしれない。
user:references
MicropostにはUserモデルへの関連(belongs_to)が自動的に付加されるが、
Userには自動的にはつかない。
has_many :microposts
を自分でつけなければならない。
InsufficientPrivilege Error
テストで以下のようなエラーでるようになった。
This is most likely caused due to missing permissions.
Rails needs superuser privileges to disable referential integrity.
cause: PG::InsufficientPrivilege: ERROR: permission denied: "RI_ConstraintTrigger_a_51807" is a system trigger
DRb::DRbRemoteError: PG::ForeignKeyViolation: ERROR: insert or update on table "microposts" violates foreign key constraint "fk_rails_558c81314b"
DETAIL: Key (user_id)=(762146111) is not present in table "users".
WARNING: Rails was not able to disable referential integrity.
This is most likely caused due to missing permissions.
Rails needs superuser privileges to disable referential integrity.
Rails needs superuser privileges to disable referential integrity.
という文からテストデータを作るために参照整合性を無効にする権限が必要らしい
とりあえずDBのユーザーがsuper userじゃないとだめみたいな。
調べます。
[email protected]:/# psql -U appuser -d sample_app_development
psql (12.3 (Debian 12.3-1.pgdg100+1))
Type "help" for help.
sample_app_development=> SHOW is_superuser;
is_superuser
--------------
off
(1 row)
やっぱり superuserじゃない。
railsで使うユーザーはsuperuserじゃないとだめなのか。superuser権限をつけることにする
superuserでログインし直して ALTER USER する
[email protected]:/# psql -U postgres
psql (12.3 (Debian 12.3-1.pgdg100+1))
Type "help" for help.
postgres=# \du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
appuser | Create DB | {}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
postgres=# ALTER USER appuser WITH SUPERUSER;
ALTER ROLE
postgres=# \du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
appuser | Superuser, Create DB | {}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
これでappuserはSuperuserになったので、テストも成功するはず。
実際に成功!
RailsでPostgresをデータベースの場合、テストのために参照整合性をOFFにする必要があり、superuser権限じゃないとダメっぽい。
time_ago_in_words(micropost.created_at)
よくニュース記事とかの一覧でも見る、何時間前とか何日前とかを表示してくれるメソッド。すごすぎる。覚えておいてつかわなければ。
Active Job
Railsの非同期処理を行う仕組みらしい。紹介だけで終わっているが、一応覚えておくためにメモ
https://railsguides.jp/active_job_basics.html
Active Storage (ストレージサービス設定)
Amazon S3でのやり方を書いてあったが、そちらはやったことあったので、こちらを見てGCPのCloud Storageで行ってみた。
https://hixonrails.com/ruby-on-rails-tutorials/ruby-on-rails-active-storage-aws-s3-google-cloud-platform-microsoft-azure/
Amazon S3に比べると設定が必要な項目が多いが、普通に設定していると本番でもdockerでの開発環境でもエラーで画像が登録できなかった。どうもPrivate Keyの設定でダブルクォーテーションで囲まないといけないらしい。
本番環境(Herokuでconfig:setで環境変数設定)
まず、エラーは詳細なエラーログがでない500エラーとなる。
$ heroku config:set GCS_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMI......
とコマンドでセットしていたが、Herokuのコンソールから設定できるみたいだった。
そっちの方が早くて見やすい。実際にそちらから確認して、""(ダブルクォーテーション)で囲んだ後、画像の投稿が成功することを確認!
開発環境(docker-composeでenvironementで環境変数設定)
開発環境でも以下エラー出ているみたいだ。秘密鍵と公開鍵がないようなエラーかな
Netither A nor B で AもBも両方ないということだろうか。
Neither PUB key nor PRIV key: nested asn1 error
以下のようにして、docker-compose.yamlのenvironmentに設定しているが他環境変数は
全体を""(ダブルクォート)で囲っているが、GCS_PRIVATE_KEY(秘密鍵)は’’(シングルクォート)で囲っていることに注目。そしてvalueは""(ダブルクォート)で囲っている。
services:
app:
build: app
tty: true
volumes:
- ./app/src:/src
environment:
- "GCS_CLIENT_ID=*********"
- 'GCS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----*******\n-----END PRIVATE KEY-----\n"'
(バックスラッシュ)で”をエスケープとかもいろいろやってみたが、yamlの解析エラーになったりしてうまくいかなかった。シングルクォートで囲むこの方法でうまくいったのでとりあえずOKということにしておく。
第14章 Following users
Relationshipモデル
今回はrelationship.ymlをいったん空にしておくように書いてある。post.ymlの時には書かれていなかったと思うのに。
following, followers
ルーティングに初見のmemberメソッドが出てくる。
URLにIDを使えるようになるらしい。
ルーティングの多様なオプションについては以下参照
https://railsguides.jp/routing.html
Rails.application.routes.draw do
・・
resources :users do
member do
get :following, :followers
end
end
・・
Ajaxで更新処理
form_with の local: true を remote: true に変更するだけで非同期処理で更新処理が実行されるようになるらしい。
controllerのcreateなら以下respond_toメソッドのどちらかの行が実行されるらしい。
つまり remote: trueで非同期処理の場合、format.jsが実行されるが、その実ファイルはアクションに対応する hoge.js.erb (ここではcreate.js.erb) になる。
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format| # 以下のどちらからの行が実行される
format.html { redirect_to @user } # form_withで local:trueならこちら
format.js # remote: trueならこちら
end
end
テストの時も、postやdeleteのリクエストに xhr: true を書くだけで非同期処理になるだと。。
# 普通に
post relationships_path, params: { followed_id: @other.id }
# ajax
post relationships_path, xhr: true, params: { followed_id: @other.id }
unfollow(destroy, delete)でのログ比較
followでなく、なぜかunfollowで確認したが、本質は同じことなので。。
普通の通信ではdestroyのactionのでredirect しているのでshowアクションが実行される。
その結果、ヘッダーやフッターまで再描画されてしまっている。
一方Ajaxの方ではdestroyでdelete後、必要な部分だけ(followボタンの描画、following人数の描画)描画されていることがわかる。
Ajaxではない普通の通信のrendering
Started DELETE "/relationships/110" for 172.23.0.1 at 2020-07-25 07:37:30 +0000
Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by RelationshipsController#destroy as HTML
Parameters: {"authenticity_token"=>"4C5MMWOPZa+ENPiuUzdxmrpt81HruWEFJ3blg+adxt18+a8mmbwAzvU9MkgylO1KnmyadjvdIswqvyT3Yvcj8w==", "commit"=>"Unfollow", "id"=>"110"}
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:19:in `current_user'
Relationship Load (0.4ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."id" = 1 LIMIT2 [["id", 110], ["LIMIT", 1]]
↳ app/controllers/relationships_controller.rb:14:in `destroy'
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 16], ["LIMIT", 1]]
↳ app/controllers/relationships_controller.rb:14:in `destroy'
Relationship Load (0.7ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = 1 AND "relationships"."followed_id" =2 LIMIT 3 [["follower_id", 1], ["followed_id", 16], ["LIMIT", 1]]
↳ app/models/user.rb:103:in `unfollow'
(0.8ms) BEGIN
↳ app/models/user.rb:103:in `unfollow'
Relationship Destroy (0.5ms) DELETE FROM "relationships" WHERE "relationships"."id" =1 [["id", 110]]
↳ app/models/user.rb:103:in `unfollow'
(6.1ms) COMMIT
↳ app/models/user.rb:103:in `unfollow'
Redirected to http://localhost:3000/users/16
Completed 302 Found in 34ms (ActiveRecord: 10.2ms | Allocations: 4954)
Started GET "/users/16" for 172.23.0.1 at 2020-07-25 07:37:30 +0000
Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#show as HTML
Parameters: {"id"=>"16"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 16], ["LIMIT", 1]]
↳ app/controllers/users_controller.rb:7:in `show'
Rendering users/show.html.erb within layouts/application
(1.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = 1 [["follower_id", 16]]
↳ app/views/shared/_stats.html.erb:5
(0.6ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" =1 [["followed_id", 16]]
↳ app/views/shared/_stats.html.erb:11
Rendered shared/_stats.html.erb (Duration: 8.3ms | Allocations: 2078)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:19:in `current_user'
User Exists? (0.9ms) 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 3 [["follower_id", 1], ["id", 16], ["LIMIT", 1]]
↳ app/models/user.rb:108:in `following?'
Rendered users/_follow.html.erb (Duration: 3.0ms | Allocations: 554)
Rendered users/_follow_form.html.erb (Duration: 8.8ms | Allocations: 1806)
Micropost Exists? (0.4ms) SELECT 1 AS one FROM "microposts" WHERE "microposts"."user_id" =1 LIMIT $2 [["user_id", 16], ["LIMIT", 1]]
↳ app/views/users/show.html.erb:16
Rendered users/show.html.erb within layouts/application (Duration: 23.0ms | Allocations: 5415)
Rendered layouts/_rails_default.html.erb (Duration: 42.6ms | Allocations: 17206)
Rendered layouts/_shim.html.erb (Duration: 0.2ms | Allocations: 5)
Rendered layouts/_header.html.erb (Duration: 1.4ms | Allocations: 199)
Rendered layouts/_footer.html.erb (Duration: 0.4ms | Allocations: 49)
Completed 200 OK in 79ms (Views: 68.2ms | ActiveRecord: 4.2ms | Allocations: 24589)
Ajax通信でのrendering
Started DELETE "/relationships/111" for 172.23.0.1 at 2020-07-25 07:39:32 +0000
Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by RelationshipsController#destroy as JS
Parameters: {"authenticity_token"=>"g8D8JoEMZ6E3helxeH2Ea/EbMFUHSBDSPLgvMUyBfLMfFx8xez8CwEaMI5cZ3hi71RpZctcsUxsxce5FyOuZnQ==", "commit"=>"Unfollow", "id"=>"111"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:19:in `current_user'
Relationship Load (0.3ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."id" = 1 LIMIT2 [["id", 111], ["LIMIT", 1]]
↳ app/controllers/relationships_controller.rb:14:in `destroy'
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2 [["id", 16], ["LIMIT", 1]]
↳ app/controllers/relationships_controller.rb:14:in `destroy'
Relationship Load (0.5ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = 1 AND "relationships"."followed_id" =2 LIMIT 3 [["follower_id", 1], ["followed_id", 16], ["LIMIT", 1]]
↳ app/models/user.rb:103:in `unfollow'
(0.2ms) BEGIN
↳ app/models/user.rb:103:in `unfollow'
Relationship Destroy (1.1ms) DELETE FROM "relationships" WHERE "relationships"."id" =1 [["id", 111]]
↳ app/models/user.rb:103:in `unfollow'
(1.1ms) COMMIT
↳ app/models/user.rb:103:in `unfollow'
Rendering relationships/destroy.js.erb
Rendered users/_follow.html.erb (Duration: 1.7ms | Allocations: 467)
(0.7ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = $1 [["followed_id", 16]]
↳ app/views/relationships/destroy.js.erb:2
Rendered relationships/destroy.js.erb (Duration: 8.0ms | Allocations: 1825)
Completed 200 OK in 33ms (Views: 10.4ms | ActiveRecord: 5.4ms | Allocations: 8081)
Comments