RubyとRailsに入門する

これを読んだ。

配列をputsするとそれぞれの要素が改行されて出力される

クラスの属性は@(変数名)
みたいな感じで指定
オブジェクトの属性にはデフォルトでアクセスできない。
attr_accessor
という何かを属性に指定するとアクセスできるようになる。

型を期待してメソッドを書く感じではないらしい。
duck typingと呼ばれている概念らしい。
say_byeメソッドはeachを使いません。その代わり、@namesがjoinメソッドを 処理できるかをチェックしています。もし処理できることがわかれば、それを使います。 そうでなければ、変数の値を文字列として出力します。 このメソッドは実際の変数の型を意識せず、サポートしているメソッドに頼っています。 これは“Duck Typing”という名前で知られている、「もしアヒルのように歩き、 アヒルのように鳴くものは……」というものです。この方法の良いところは、 対応する変数の型に不要な制約を課さずにすむことです。
引数がメソッドをサポートしているかどうかを調べて処理を変えるらしい。
interfaceとかgenericsとか使いたいな~

出力はp
とputs
とprint
があるらしい

配列の各要素に対する操作
a.each {|val| puts val}
pythonでいうenumerateみたいなやつ
a.each_with_index {|val, i| puts "#{val}, #{i}"}

配列の末尾に要素を追加
a.push "Python"
配列の末尾の要素を出す
a.pop
=> "Python"
a.unshift "C++" #配列の先頭に要素を追加
a.shift #配列の先頭の要素を抽出し元配列から削除
=> "C++"
a.delete("Java") #値を指定して削除
a.delete_at(1) #インデックスを指定して削除
a.delete_if{|lang| lang.size >= 5} #条件指定して削除
a.clear #配列の要素をすべて削除
deleteは同じ値があるとすべて削除する。

配列にはsort
メソッドがある
sortのルールを変えるには、sort
メソッドにブロックと呼ばれる関数みたいなやつを引数として渡す?
ブロックは波カッコで囲んで指定、またはdo
とend
で囲んで指定
a = [2, 33, 1, 3, 11, 22]
a.sort{|x,y| x.to_s <=> y.to_s}
次のような書き方も可能
a.sort do |x, y|
x <=> y
end
関数型っぽい

irb(main):001:0> a = ["C++", "Ruby", "Python", "Java"]
=> ["C++", "Ruby", "Python", "Java"]
irb(main):002:0> a.size #配列の要素数
=> 4
irb(main):003:0> a.reverse #配列を逆転
=> ["Java", "Python", "Ruby", "C++"]
irb(main):004:0> a.find{|l| l.length == 4} #最初に条件に合うものを検索
=> "Ruby"
irb(main):005:0> a.find_all{|l| l.length == 4} #条件に合うもの全て検索
=> ["Ruby", "Java"]
irb(main):006:0> a.map{|lang| lang * 2} #配列の各要素の置き換え
=> ["C++C++", "RubyRuby", "PythonPython", "JavaJava"]
irb(main):007:0> b = ["Lisp", "Haskell"]
=> ["Lisp", "Haskell"]
irb(main):008:0> a + b #配列の連結
=> ["C++", "Ruby", "Python", "Java", "Lisp", "Haskell"]
irb(main):009:0> ["Ruby", "Java", "Ruby", "C++"].uniq #重複要素の除去
=> ["Ruby", "Java", "C++"]
irb(main):010:0> [nil, "Java", "Ruby", nil].compact #nil要素の除去
=> ["Java", "Ruby"]

ハッシュ
ハッシュはpythonでいう辞書
key valueのペアは=>
で指定する
h = {"John"=>40, "Paul"=>42}
h = {John: 40, Paul: 42} #シンボルをキーとして生成
h.to_a #キーと値のリスト
- keyに対するvalueがないときはnilを返す
シンボルは:
から始まるデータ型で、
- 不変
- 一意性
- 高速な比較
という特徴があるらしい
ブロック
hashもarrayと同様にブロックを使える
h.each{|k, v| puts "#{k}, #{v}"} #各キーと値の処理
h.each_key{|k| puts k} #ハッシュの各キーに対する処理
h.each_value{|v| puts v} #ハッシュの各値に対する処理
存在チェック
h.has_key? "George" #キーがあるか?
h.has_value? 40 #値があるか?
削除
arrayとだいたい同じ
その他
irb(main):001:0> h = {"John" => 40, "Paul" => 42, "Geroge" => 43}
=> {"John"=>40, "Paul"=>42, "Geroge"=>43}
irb(main):002:0> h.size #ハッシュの要素数
=> 3
irb(main):003:0> h.empty? #ハッシュが空かどうか?
=> false
irb(main):004:0> h.invert #値からキーへのハッシュを返す
=> {40=>"John", 42=>"Paul", 43=>"Geroge"}
irb(main):005:0> h.sort #ハッシュのキーでソートし配列で返す
=> [["Geroge", 43], ["John", 40], ["Paul", 42]]
irb(main):006:0> h2 = {"Ringo" => 40}
=> {"Ringo"=>40}
irb(main):007:0> h.merge h2 #ハッシュの内容をマージする(非破壊的)
=> {"John"=>40, "Paul"=>42, "Geroge"=>43, "Ringo"=>40}
irb(main):008:0> p h
{"John"=>40, "Paul"=>42, "Geroge"=>43}
=> {"John"=>40, "Paul"=>42, "Geroge"=>43}
irb(main):009:0> h.update h2 #ハッシュの内容をマージする(破壊的)
=> {"John"=>40, "Paul"=>42, "Geroge"=>43, "Ringo"=>40}
irb(main):010:0> p h
{"John"=>40, "Paul"=>42, "Geroge"=>43, "Ringo"=>40}
=> {"John"=>40, "Paul"=>42, "Geroge"=>43, "Ringo"=>40}

時刻
Timeクラスを使う
irb(main):001:0> t = Time.now #現在時刻を取得
=> 2012-01-23 01:23:45 +0900
irb(main):002:0> t.strftime("%Y/%m/%d %H:%M:%S") #時刻の書式化
=> "2012/01/23 01:23:45"
irb(main):003:0> t1 = Time.local(2012,1,1,0,0) #地方時で任意時刻を設定
=> 2012-01-01 00:00:00 +0900
irb(main):004:0> t2 = t1 + 3600 #3600秒後をt2に設定
=> 2012-01-01 01:00:00 +0900
irb(main):005:0> puts t2 #t2はt1の1時間後に
2012-01-01 01:00:00 +0900
=> nil
数字を足し算すると秒扱いなのか

正規表現
正規表現オブジェクトは以下で生成
-
/
で囲む -
Regexp
クラスから生成 -
%r|
と|
の間に書く
マッチングするかどうかは=~
演算子で判定する。マッチすると、マッチした文字列の先頭インデックスを返す。
- マッチした文字列は
$&
で取得する - マッチしない場合はnilを返す
最新の結果が予約された変数に入るのか……
文字列メソッドと組み合わせることもできます
irb(main):001:0> s = "apple banana orange"
=> "apple banana orange"
irb(main):002:0> s.scan(/\w+/) #英数字の並びを抽出し配列に
=> ["apple", "banana", "orange"]
irb(main):003:0> s.split(/\s+/) #スペースの並びを区切り文字とし配列に
=> ["apple", "banana", "orange"]
irb(main):004:0> s.gsub(/\s+/, ",") #スペースの並びをカンマに変換
=> "apple,banana,orange"

条件分岐
-
if 条件式 then
とend
で囲む -
else ifは
elsif
-
switch文的なやつはcase
-
三項演算子
score = 80
result = score > 70 ? "Pass" : "Failed" #右辺の評価結果をresultに代入
puts result
-
if文の逆(条件を満たさないときに実行する)
unless
もある- いらんくね…?
-
if修飾子
debug = true
num = 10
puts "num = #{num}" if debug #debugがtrueのときのみ実行
繰り返し
for文はtimesメソッドを使う
数値.times {|変数|
#処理
}
関数型っぽくていいね
その他、upto
メソッド、downto
メソッド、step
メソッドもある
puts "upto:"
#numが3から5に1つずつ増加
3.upto(5) {|num|
puts "num = #{num}"
}
puts "downto:"
#numが8から6に1つずつ減少
8.downto(6) {|num|
puts "num = #{num}"
}
puts "step:"
#numが12.3から14.1を超える前まで0.5ずつ増加
12.3.step(14.1, 0.5) {|num|
puts "num = #{num}"
}
for文もあるがpythonのfor文っぽい感じ(オブジェクトを一個ずつ取り出す)
lang = ["Ruby", "Java", "Python"]
for s in lang do
puts s
end
for i in 1..3 do
puts i
end
いまさらだけど 1..3
はRangeオブジェクトという種類らしい
while文もある
while 条件式 do
#処理
end
-
条件が成立しない間繰り返すuntil文もある(いらんくね)
-
無限ループをするための
loop
文もある(いらんくね) -
continueに相当するnext文
-
同じ繰り返しをするredo文
もある
例外
begin
#例外が発生するかもしれない処理
rescue 例外クラス => 例外オブジェクトを格納する変数 then
#例外が発生したときの処理
else
#例外が発生しなかったときの処理
ensure
#例外の有無に関わらず最後に実行される処理
end
- raise関数で明示的にエラーを発生させることも可能。

入出力
標準入出力は定数に格納されている。
- 標準入力からデータを受け取る
while str = STDIN.gets
break if str.chomp == "exit"
print "input text:", str
end
getsは改行文字を取り除かないので注意
- STDERR.putsメソッドで標準エラー出力できる。
ファイル入出力
openしたら閉じる
io = File.open(ファイル名, モード)
#ファイル処理
io.close
ブロックを渡すと終わったら自動で閉じてくれる
File.open(ファイル名, モード) do |io|
#ファイル処理
end
File.open("data.txt", "r") do |io|
puts "---gets"
p io.gets #1行読み込んで表示
puts "---readline"
p io.readline #1行読み込んで表示
puts "---read"
io.rewind #読み込み位置を先頭に戻す
p io.read(5) #5バイト分読み込んで表示
p io.read #現在位置よりファイル全体を読み込んで表示
puts "---readlines"
io.rewind #読み込み位置を先頭に戻す
p io.readlines #ファイルの各行を要素とする配列を作成して表示
end
File.open("out.txt", "w") do |io|
p io.puts "AAAAA" #文末に改行を入れて書き込む
p io.print "BBBBB" #文末の改行は付けずに書き込む
p io.write "CCCCC" #文末の改行は付けない。書き込んだバイト数を返す
end
コマンドライン引数
ARGV変数に配列として入っている
ファイル操作、ディレクトリ操作
FileクラスとDirクラスに各種メソッドがある
特にほかの言語と変わることはない
joinとかはおもしろいかも?
File.join("C:/ruby", "data.txt") #ファイルパスを連結
=> "C:/ruby/data.txt"
Dir.globメソッド
Dir.glob("C:/ruby/*.txt") #ファイル一覧の取得
=> ["C:/ruby/out.txt", "C:/ruby/test.txt"]

更新サボっていたがRuby on Railsチュートリアル進めている。
8章から気になったことをメモしていく。
cookies と session
cookiesメソッド(9.1)とは対照的に、sessionメソッドで作成された一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了します
使い方(抽象)はともかく、実態(具象)が何なのかよくわからないなあ…
次でsessionを生成できる
session[:user_id] = user.id
これはハッシュのように使えるがハッシュではない。session
インスタンスを生成しているらしい。
要はcookieを生成してuser_idをkey、暗号化(ハッシュ化?)されたuser_idの値をvalueにセットして、HTTPのset-cookieヘッダに載せて返すってこと?
クッキーをdevtoolsで確認したら、_sample_app_session
というNameでValueがよくわからん値のものが保存されていた。
ログインしたときのPOSTリクエストのレスポンスのSet-Cookieヘッダには_sample_app_session=<ランダム?文字列>
というのがついてた。
中身はわからないけどこの中にuser_idが入っていて、railsはそれをデコードできるようになっているのだと思う

or equals
次のコードは@current_user
がnilならば後ろの式を評価して代入するという意味である。
@current_user = @current_user || User.find_by(id: session[:user_id])
ただし普通次のようにかく
@current_user ||= User.find_by(id: session[:user_id])

fixture
test/fixtures
の下にyamlで書く。中にERBを書ける。
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
password
を使いたいが、DBにはそのようなカラムはないため、password属性を追加するとエラーになる。そこで、fixtureでは全員同じパスワードpassword
が使われる。
テスト中では以下のように参照可能。
user = users(:michael)

safe navigation演算子(ぼっち演算子)
obj && obj.method
はobj&.method
と書ける。
- 1回の呼び出しのようで実は2回呼び出してるのがちょっと気持ち悪いかも。まあ実際困る場面は思いつかない。

Cookies
session
メソッドを使うとブラウザを閉じるとセットした情報は消えてしまう。ブラウザにセッション情報を永続化するにはcookies
メソッドを使えば良い
- 実際、ブラウザへのインターフェースとしては何が異なるんだろう?
- HTTPのヘッダが異なる?
- ChatGPT先生によると、
Set-Cookie
ヘッダに有効期限を指定しない場合、有効期限はブラウザが開いている間とブラウザは解釈するらしい。 - Cookieに有効期限を設定するには、
Expires
またはMax-Age
フィールドを使う。
Set-Cookie: myCookie=myValue; Expires=Wed, 21 Oct 2023 07:28:00 GMT;
Set-Cookie: myCookie=myValue; Max-Age=3600;
- ChatGPT先生によると、
- 要は
cookies
メソッドを使うとこれらのフィールドをつけてくれるのだと理解した。
- HTTPのヘッダが異なる?
cookieに認証情報を載っけるので、これが漏洩するとまずい。主な攻撃方法として以下がある:
-
平文でcookieが送られている時に、man-in-the-middleでcookieを取り出す。
- TLSで防御可能。
-
DBへの侵入で、保存されているトークンを窃取する
- ハッシュを保存しておくことで防げる。
-
XSSで窃取する。
- RailsではView templateで入力された内容はエスケープされる
-
パソコンやスマホを物理的に窃取して取得する。
- 根本的対策は難しい。
- 別の端末などでログアウトしたらトークンも必ず更新する、などで二次的被害を最小限にすることは可能。
-
一時セッションはセッションリプレイ攻撃に対しては脆弱。
- Diveseでは対策済みらしい
次の方針で永続的セッションを作成する。
- ランダムな文字列を生成して、記憶トークンとして使う
- 記憶トークンは、ハッシュ化してデータベースに保存
- 記憶トークンをブラウザのcookiesに保存するときは、有効期限を設定する
- 上で説明したように、
Expires
かMax-Age
をつければ良い
- 記憶トークンをブラウザのcookiesに保存するときは、ユーザIDを暗号化する
- どゆこと?ランダムな文字列じゃなかったん?どうやってユーザIDを入れる?
- 以降、もしブラウザからcookiesが送られてきて、暗号化されたユーザIDがあったら、復号したユーザIDでDBを検索して、DB内のハッシュ値と一致するか確認する
-
sessions
の時も思ったけど復号キーはどこで持っておく?

有効期限の設定
cookies[:remember_token] = { value: remember_token,
expires: 20.years.from_now.utc }
有効期限は省略できる。この場合は、ブラウザの動作としてはsessions
メソッドと同じになり、セットしたcookieはブラウザ終了で削除される。
- ブラウザ終了で削除されてよいcookieをどちらで保存するかは、好みの問題らしい。
- セッションクッキーは
sessions
を使い、それ以外の一時クッキーはcookies
でいいらしい
なお、上のコードと以下のコードは同じ動作となる。
cookies.permenent[:remember_token] = remember_token
- permenentにすると期限が20年になる。
cookieの暗号化
ユーザIDを暗号化してクッキーにセットする
cookies.encrypted[:user_id] = user.id
- 暗号化しないと、ユーザ自身がクッキーを書き換えて操作可能となってしまう。
-
例えば、悪意のあるユーザーaliceがサイトにログインした結果、CookieStoreに{“user”:”alice”}という情報が保存されたとします。攻撃者は自分自身のクッキーを{“user”:”bob”}に書き換えるだけで、bobになりすましができてしまいます。このような攻撃を防ぐために、CookieStoreを暗号化(本質的に改ざん防止)するのです。
- 参考: セッションを暗号化するのにはなんの意味がありますか?
-

ユーザにログインを要求する
before_action
で保護したいページに認証を要求する
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
private
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url, status: :see_other
end
end
end
- なぜ
See Other
なのか気になる。401 Unauthorized
では?

sendメソッド
次の関数を〇〇_digestという属性一般で使えるようにしたい
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
railsではメタプログラミングで可能
def authenticated?(attribute, token)
digest = self.send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
sendメソッドは文字列とかシンボルとかをとってその名前に対応する属性にアクセスすると思っておけば良い
こうすると、次のようにかける
user.authenticated?(:remember, remember_token)
所感
気持ち悪い…
というか、バグりそう。そのメソッドがあるかどうか、コーディングの段階で気付きたいのだけどそういう方法はあるのだろうか。

Active Recordにおけるテーブル間の関係
AとBが1対多なら、Aのmodelにはhas_many
をつけてBにはbelongs_to
をつける
class Micropost < ApplicationRecord
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
class User < ApplicationRecord
has_many :microposts
.
.
.
end

user.microposts
にメソッドチェーン繋げる形で
- create
- create! (失敗時に例外を発生)
- build
- find_by(id: )
などを使える
- createはDBに保存するが、buildはしない

あれ、DBに制約つけるならばmigrateしなくていいのかな?

関連データの削除
データ削除の際に関連しているデータを削除したければhas_many
側にdependent: destroy
をつければ良い
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
.
.
.
end
これはON DELETE CASCADE
制約をつけているわけではなく、railsが代わりに削除のSQLを発行してくれるという機能らしい。
ChatGPTさんによると
このオプションはRailsのActiveRecordの機能であり、実際のデータベーステーブルにON DELETE CASCADE制約を直接的に追加するものではありません。代わりに、Railsはdependent: :destroyが指定された関連付けに関連するレコードを削除するためのコードを生成します。この場合、Userが削除されると、関連するMicropostsも一緒にdestroyされますが、それはRailsのActiveRecordのメソッドを使用して個々のレコードを削除することによって行われます。
例えば、PostgreSQLを使っている場合、マイグレーションファイルで外部キー制約を追加する際にon_delete: :cascadeオプションを指定することで、ON DELETE CASCADE制約を設定できます。以下は、マイグレーションファイルでの例です
class AddForeignKeyToMicroposts < ActiveRecord::Migration[6.0]
def change
add_foreign_key :microposts, :users, on_delete: :cascade
end
end

ChatGPTさんによると、modelでhas_many
をつけてもDBに外部キー制約はつかないらしい。
まあそれはそうか。
外部キー制約は参照する側が持つので、belongs_to
側に持たせる。
チュートリアルでは、modelの生成の際に外部キー制約を付与したmigrationを生成していた。

default_scope
default_scope
メソッドを使うと、ORMで取得する順番を指定できる
class Micropost < ApplicationRecord
belongs_to :user
default_scope -> { order(created_at: :desc) }
end
->
はラムダ式で、ブロックを引数に取り、Proc
オブジェクトを返す。Proc
オブジェクトは、call
メソッドが呼ばれたとき、ブロック内の処理を評価する。
この場合呼ばれるSQLはこんな感じになる
irb(main):001> a = Micropost.first
Micropost Load (0.4ms) SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ? [["LIMIT", 1]]
=> nil

countメソッド
user.microposts.count
は
データベース上のマイクロポストを全部読みだしてから結果の配列に対して
length
を呼ぶ、といった無駄な処理はしていないという点です。そんなことをしたら、マイクロポストの数が増加するにつれて効率が低下してしまいます。そうではなく、データベース内で高度に最適化された計算をしてもらった後に、特定のuser_id
に紐付いたマイクロポストの数をデータベースに問い合わせています
高度に最適化された計算…?
irb(main):002> User.count
User Count (0.2ms) SELECT COUNT(*) FROM "users"
=> 100
irb(main):003> User.first.microposts.count
User Load (1.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Micropost Count (0.2ms) SELECT COUNT(*) FROM "microposts" WHERE "microposts"."user_id" = ? [["user_id", 1]]
=> 0
普通にCOUNT(*)
メソッド呼んでいるだけだった。

13章のリファクタリングについて…
テストでログインしているかどうかを使うためのメソッドlogged_in_user
をUserモデルに実装していたが、Micropostクラスから参照できないので、共通の親であるApplicationController
に移すということだが…
class ApplicationController < ActionController::Base
include SessionsHelper
private
# ユーザーのログインを確認する
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url, status: :see_other
end
end
end
なんだろう、これ…
本当に?という気持ちになってしまうけど
うまく言えないけど共通の処理があるから安易に親に実装を書くというのに非常にためらいがある。SessionsHelper
に書くのではダメなんだったけ?(まあ、それでも結局同じことではあるけど)
なんか、親にはインターフェースだけ書いて実装は書くなという刷り込みがかなりあるのですごく嫌な感じする。
じゃあどう分割するのがいいのか、ってのはわからないけど。

Active Recordと関係
フォロワーの関係を表すためにエンティティを作成する
class CreateRelationships < ActiveRecord::Migration[7.0]
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :followed_id
t.timestamps
end
add_index :relationships, :follower_id
add_index :relationships, :followed_id
add_index :relationships, [:follower_id, :followed_id], unique: true
end
end
- なぜ、外部キー制約をつけないのだろう?
シンボルとクラス名が一致しないので、ユーザ側で指定:
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
.
.
.
end
- 外部キー制約つけるのは参照側だからUser側につけるのはなんだかこんがらがるな
- 多分このあとfollowedにもつけるんだろう
生のSQLであれば参照される側のテーブル定義には参照している側の定義は全く関与しないが、ORMを使う以上参照される側に参照する側との関係をちゃんと書かないと、属性として使用できない、というのは理解した。

14.1.2の演習で、NoMethodError
になったけど
先ほどの演習を終えたら、active_relationship.followedの値とactive_relationship.followerの値を確認し、それぞれの値が正しいことを確認してみましょう。
irb(main):006> user.active_relationships
Relationship Load (0.4ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=>
[#<Relationship:0x0000000108bfa280
id: 1,
follower_id: 1,
followed_id: 3,
created_at: Sun, 19 Nov 2023 04:31:18.942491000 UTC +00:00,
updated_at: Sun, 19 Nov 2023 04:31:18.942491000 UTC +00:00>]
irb(main):007> user.active_relationships.follower
/Users/shira/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.0.4.3/lib/active_record/relation/delegation.rb:110:in `method_missing': undefined method `follower' for #<ActiveRecord::Associations::CollectionProxy [#<Relationship id: 1, follower_id: 1, followed_id: 3, created_at: "2023-11-19 04:31:18.942491000 +0000", updated_at: "2023-11-19 04:31:18.942491000 +0000">]> (NoMethodError)
irb(main):008> user.active_relationships.followed
/Users/shira/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.0.4.3/lib/active_record/relation/delegation.rb:110:in `method_missing': undefined method `followed' for #<ActiveRecord::Associations::CollectionProxy [#<Relationship id: 1, follower_id: 1, followed_id: 3, created_at: "2023-11-19 04:31:18.942491000 +0000", updated_at: "2023-11-19 04:31:18.942491000 +0000">]> (NoMethodError)

あー、user.active_relationships
経由でフォロワー、フォロイー取得するのかと思ってたけど違うのか。
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
.
.
.
end
-
has_many through
をつけるとエイリアス?みたいな感じでアクセスできると理解- DBの構造を隠蔽しつつORMを使いやすくする措置だと理解。

すごい。inner joinしてくれている。
irb(main):001> user = User.first
User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=>
#<User:0x000000010870ca20
...
irb(main):002> user.following
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]

そのあともう一回user.following
にアクセスしたけど、SQLが表示されない。
キャッシュしている?

ルーティングとobject schema
memberメソッドは1つのインスタンスに紐づく別のインスタンスへのルーティングを提供する。
Rails.application.routes.draw do
# 略
resources :users do
member do
get :following, :followers
end
end
# 略
end
- 上の例では、
users/1/following
とかusers/1/followers
というパスへのgetクエリに対するルーティングを提供する。
idに紐づけず、全てのリソースへのルーティングはcollectionメソッドを使うらしい
resources :users do
collection do
get :tigers
end
end

アクションはそのまま、following
とfollowers
になる
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers]
.
.
.
def following
@title = "Following"
@user = User.find(params[:id])
@users = @user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
@title = "Followers"
@user = User.find(params[:id])
@users = @user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
.
.
.
end

非同期通信
- Hotwire
- Turbo
- Turbo Streams
何これ〜〜?

とりあえず、以下のコードは、createアクションが起動されたときに、
-
follow_form
というCSS IDを持つパーシャルをusers/unfollow
に置き換える -
followers
というCSS IDを持つパーシャルを@user.followers.count
に置き換える
ということをしているのは理解した。
<%= turbo_stream.update "follow_form" do %>
<%= render partial: "users/unfollow" %>
<% end %>
<%= turbo_stream.update "followers" do %>
<%= @user.followers.count %>
<% end %>
- 対応するアクションは例によってNaming Conventionで指定する。
-
<アクション名>.trubo_stream.erb
という命名にする
-

- [Follow]ボタンまたは[Unfollow]ボタンをクリックすると、Turbo StreamリクエストがRailsサーバーに送信される
- Turbo Streamに応答するコードが存在しない場合は、Turbo Streamリクエストを通常のHTMLリクエストとして扱う
- Turbo Streamに応答するコードが存在する場合は、同じ名前を持つTurboテンプレートを、リクエストに対応するアクション(ここではcreateまたはdestroy)として自動的に評価する
- Turboテンプレートでは、turbo_stream.updateメソッドを用いて特定のHTML要素のコンテンツを、テンプレートに渡されたERBブロックを評価した結果で置き換える

eager loading
Active Recordでは.includes
をつけたら表を結合してくれるらしい。これは便利。
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
.includes(:user, image_attachment: :blob)

Rails tutorial完走した
完走した感想は
- 必要な機能が大体全て最初から使えるようになっていて非常に便利
- Active Record便利
- ただし、Naming conventionが多すぎて、DHHの手のひらの上で踊らされている感覚になる。
- 開発初期はRailsでガッと作って後から別の技術で置き換えることも含めてリファクタリングするのが多いよ、みたいな話にそれなりの実感が伴った。
- テストを簡単に書けるのもよかったが、フロントのテストが統合テストしかないのがつらかった。
- HotwireでSPA作るのはReactとかと比べてどんなメリットがあるのか気になった。

むちゃくちゃ納得した。
何でもかんでもrailsでやるのは悪手というあれね。