Ruby on Rails Guide をやる
動機
WebをそこそこやってるのにRailsを触ったことないのはなんだかなーって思ってた
フルスタックに書くときはTypeScriptしか使ったことがない
新規でRailsをやる仕事があるのかは知らないけど、動いているRailsのアプリケーションはたくさんある
そういうやつにも参加してみたいしな〜って感じ
あと、Hotwireってやつを触ってみたい
2024-10-17 13:48
code:environment
$ ruby --version && sqlite3 --version && gem --version && rails --version
3.45.3 2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355 (64-bit)
3.4.19
Rails 7.2.1
rails new して rails server した
自分の知ってる "Yay! You're on Rails!" ってページじゃないlemonadern.icon
Rails7からシンプルなロゴに変わったらしい
config.ru
Rack configuration for Rack-based servers used to start the application.
Rakefile
npm scripts 的なやつ?
でも、タスクを追加するときはここに書くんじゃなくてlib/rasks に.rakeなファイルを作れって書いてある
storage
Active Storage files for Disk Service.
vendor
自分が作ったわけじゃない静的ファイルとかを置いとく場所らしい
css フレームワークとかロゴとか?lemonadern.icon
.roborcop.yml
robocopってテストのやつだっけ?lemonadern.icon
Lint っぽい
4.2 Say "Hello", Rails
ControllerとViewの説明をされる
routeがactionとrequestをマッピングするよ
Controllerはクラスで、actionはPublic Methodで表現されているよ
routesはDSLで書くよとあるが、どのへんがDSLなんだろう
get "/articles", to: "articles#index"
action への紐付けは文字列で指定してるだけで、フレームワーク側がそれを解釈するからDSLって言ってることかlemonadern.icon
route を手書きして Controllerを生成する
rails generate controller Articles index --skip-routes
routeごと生成もできるみたい
Controllerで明示的にViewを指定していなくても、名前にマッチするViewを勝手に表示するらしい
Convention Over Configuration! Views are located in the app/views directory. So the index action will render app/views/articles/index.html.erb by default.
出た、Convention Over Configuration
ガッツリ開発するときも明示的に書かないのかな
まあViewに埋め込みたい値があるときにどうするのかまだ知らないのでわからない
あと、今どきテンプレートエンジンとか使わないんだろうな
Viewを書き換える
erbのシンタックスハイライトが変なので拡張機能を入れる
とりあえずこれを入れた
書き換えてリロードして変更を確認
4.3 Setting the Application Home Page
root の Route を Articles#index に紐付ける
ていうか route.rb は config/ にあるんだ
5 Autoloading
Application Classes は勝手に読み込まれてるから require するなとのこと
このしくみをAutoloadingと言う
TypeScriptとRustで生きてるので explicit import したくない?と思うけど Rails Way に従うlemonadern.icon
これ静的解析辛くないのかな?lemonadern.icon
RubyMineの静的解析がすごいみたいな話は聞いたことある
今のところテンプレートエンジン用の拡張機能しか入れてない
require がいるケースは lib 配下のファイルを読み込むときか、gemの設定がrequire: false のときだけ
ていうかRubyの継承ってclass ArticlesController < ApplicationControllerって書くんだ
確かに見たことあるような気もする
Rubyを知らなすぎる
6 MVC and You
ここから分量が多そうなので章ごとじゃなくて節ごとに区切りますlemonadern.icon
ここからMVCの説明
なんで and YOU?
Rails follows this design pattern by convention.
また出た、Convention
6.1 Generating a Model
Modelの説明
A model is a Ruby class that is used to represent data. Additionally, models can interact with the application's database through a feature of Rails called Active Record.
こういう説明すんのか〜、参考になるlemonadern.icon
あくまでデータを表してるものって説明してからデータベースも触りますって言うんだ
Modelをつくろう
bin/rails generate model Article title:string body:text
やっぱ全部にScaffoldあるの強いな〜lemonadern.icon
TSだとこういうの全部面倒見るフレームワーク作ろうっていう流れは薄いよね
開発者の責任で部品選定と組み立て頑張ってねって雰囲気だもんな
Modelの命名は単数形(singlar)
Modelのインスタンスは1レコードに対応しているから
Article.new()って書きたいから
2024-10-17 14:56
6.2 Database Migrations
マイグレーションはRubyで書くよ、だからデータベース非依存だよ
id(PK, autoincrement)は自動で作られるよ
t.timestampsを書くとcreated_at, updated_at が追加定義されるよ
idはいいとして、created_atとupdated_atはRails出身の人がデータベース設計するときに手癖で書いてるのをよく見るから馴染み深いlemonadern.icon
rails db:migrate で migrate する
schema.rbが書き換わってる
6.3 Using a Model to Interact with the Database
Modelをいじくるために console っている機能を使ってみるよ
console は irb みたいな対話環境で、勝手にRailsとアプリケーションのコードを読み込んでるよ
rails consoleで起動するよ
rails console
なんか補完効くし色つくしすごいlemonadern.icon
insert
article.save()の返り値はtrue/falseなんだlemonadern.icon
code:insert
blog(dev)> article = Article.new(title: "Hello, Rails", body: "I am on Rails!")
blog(dev)> article.save()
TRANSACTION (0.1ms) begin transaction
Article Create (0.5ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) RETURNING "id" "title", "Hello, Rails"], "body", "I am on Rails!", "created_at", "2024-10-17 06:14:53.035450", ["updated_at", "2024-10-17 06:14:53.035450" TRANSACTION (5.6ms) commit transaction
=> true
blog(dev)> article
=> #<Article:0x00007a40d4270440 id: 1, title: "Hello, Rails", body: "I am on Rails!", created_at: "2024-10-17 06:14:53.035450000 +0000", updated_at: "2024-10-17 06:14:53.035450000 +0000"> query
Article.find(id)とかArticle.allを使うよ
発行されるSQLを見せてくれるのいいねlemonadern.icon
Article.all
This method returns an ActiveRecord::Relation object, which you can think of as a super-powered array.
らしい
code:query
blog(dev)> Article.find()
(blog):5:in `<main>': Couldn't find Article without an ID (ActiveRecord::RecordNotFound)
blog(dev)> Article.find(2)
Article Load (0.1ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ? "id", 2], ["LIMIT", 1
(blog):6:in `<main>': Couldn't find Article with 'id'=2 (ActiveRecord::RecordNotFound)
blog(dev)> Article.find(1)
Article Load (0.2ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ? "id", 1], ["LIMIT", 1
=> #<Article:0x00007a40cffac820 id: 1, title: "Hello, Rails", body: "I am on Rails!", created_at: "2024-10-17 06:14:53.035450000 +0000", updated_at: "2024-10-17 06:14:53.035450000 +0000"> blog(dev)> Article.all
Article Load (0.3ms) SELECT "articles".* FROM "articles" /* loading for pp */ LIMIT ? "LIMIT", 11
これ今は勝手にsqliteのデータベースを触ってるってことなのか?lemonadern.icon
特に設定してないけど、デフォルトがそうなってるってことなのかな
sqliteインストールする前に試してみたらよかったな
6.4 Showing a List of Articles
Controllerのインスタンス変数はViewからアクセスできるよ
Rubyのインスタンス変数は@fooってやつ
クラス変数とかなんかいっぱいあるなーlemonadern.icon
Viewはこんな感じで書きますよ
code:views/articles/index.html.erb
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= article.title %>
</li>
<% end %>
</ul>
Django やってた頃の記憶がよみがえるな〜lemonadern.icon
懐かしい
あのテンプレートエンジンは何なんだっけ
Django Template Language (DTL) っていうDjango独自のテンプレートらしい
ERBはEmbedded Rubyの略らしい
<% %>:コード評価
<%= %>:評価と出力
7 CRUDit Where CRUDit Is Due
「Credit Where Credit Is Due」という英語の慣用句をもじったものらしい
「正当に評価されるべきものに対して評価を与えるべき」
Let's give credit where redit is due
due: 値する (verb)
WebアプリケーションがやることはCRUDがほとんどだから、CRUDが簡単になる機能を用意しているよ
じゃあタイトルのもじりはあんま上手くなくないか?むやみにCRUDするなよ、だったらわかるけどlemonadern.icon
言いたかっただけ?
7.1 Showing a Single Article
Route parameterは:idみたいに書くよ
get "/articles/:id", to: "articles#show"
Controllerからはparams[:id]でアクセスできるよ
import 無しで全部使えるの気持ちわりーlemonadern.icon
View: views/articles/show.html.erbを作るよ
Linkもつけておく
<a href="/articles/<%= article.id %>" > <%= article.title %> </a>
7.2 Resourceful Routing
CRUDをするためのEntity?をresourceと呼ぶ
routes.rbで resources :<resource_name>と書くと勝手にデフォルトの名前パターンでルーティングを生成してくれる
routeのリストは rails routesで確認できる
resourcesはURL以外にも、resourceのヘルパー関数を作ってくれる
article_path, article_url
これを使うと特定のRoutes名に依存しなくなるのでうれしい
link_to
<%= link_to article.title, article %>
第一引数がテキスト
第二引数のオブジェクトから勝手にヘルパーを呼んで、遷移先のパスにしてくれる
すげーlemonadern.icon
2024-10-17 16:13 一旦ここまで
2024-10-17 21:13 再開
7.3 Creating a New Article
関係ないが、拡張機能の話
Marketplaceで検索してみたらShopify製の拡張機能が出てきたので入れた
Shopifyには全幅の信頼を置いているlemonadern.icon
code:new_and_create.rb
def new
@article = Article.new
end
def create
@article = Article.new(title: "...", body: "...")
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
newはインスタンスを作って返しているだけだよ、saveはしていないよ
これはformページ用
formから改めてcreateを呼ぶよ
save method の返り値が true だったのはこう使うからなのかlemonadern.icon
Ruby だと、ifにtrue/falseじゃない値を渡したときの挙動はどうなるんだろう
saveが失敗したときにfalseが帰ってくるかはまだ知らない #wakaran render :new, status: :unprocessable_entity のとこは魔法すぎてマジで何やってるかわからないlemonadern.icon
422 unprocessable entity を返すんだって
あとでまた帰ってきたときに見る
あと、メソッドが括弧無しで呼べるの気持ち悪いlemonadern.icon
ミューテーションしたあとは redirect_to を使ってね
7.3.1 Using a Form Builder
form_withっていうやつのブロックの中ではいろいろなform系のhelperを呼べるよ
7.3.2 Using Strong Parameters
Submitted form data is put into the params Hash, alongside captured route parameters.
そういえばRubyは辞書的なデータ型をHashって呼ぶんだっけ、MapじゃなくてHashのほうを取るの不思議な命名に感じるlemonadern.icon
params[:article][:title]とかで送信されてきたパラメータにアクセスできるが、パラメータの数だけ受け取る処理を書くのは面倒なのでまとめてHashごと受け取ることができるよ
でもそれだと変なパラメータが追加されてきたときに気づけないのでそれをフィルタリングするための Strong Parameters という機能があるよ
これはControllerのPrivate Methodとして書けるよ
Strong Parametersのほうでフォームから受け取るパラメータを指定していく感じになるんだなlemonadern.icon
code:string_parameters.rb
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
7.3.3 Validations and Displaying Error Messages
バリデーションの機能があるよ
タイミングとしては、モデルのインスタンスを作ってからsaveするまでの間にバリデーションするよ
code:models/article.rb
class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
ここでの presence は存在する、の意
もっと厳ついバリデーション書きたくなったらどうすればいいんだろう #wakaran これはずっとだけど、括弧とかを書いたり書かなかったり波括弧だったりして、どこまでが式で何がブロックなのかぜんぜんわかんねえ、木が全然想像できない... これは慣れの問題なんだろうなlemonadern.icon
You may be wondering where the title and body attributes are defined. Active Record automatically defines model attributes for every table column, so you don't have to declare those attributes in your model file.
便利なのか恐ろしいのかわかんねえなlemonadern.icon
Viewでは、@article.errors.full_messages_for(:title)でエラーメッセージの配列が取れるよ
こういう感じで出てくる
https://gyazo.com/c6f78230ce35ea7f53988479c32b8a6d
へ〜、よくできてるlemonadern.icon
7.3.4 Finishing Up
index にnewへのリンクを追加
ここでも link_to "New Article", new_article_path と書けるよ
2024-10-17 22:12 一回中断
2024-10-21 09:12 再開
7.4 Updating an Article
だいたいcreateと同じだよ
フォームを書き換えてみるよ
code:_form.html.erb
<%= from_with model: article do |form| %>
<div>
<%= form.label :title %>
<br>
<%= form.text_field :title%>
<% article.errors.full_messages_for(:title).each do |message|%>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :body %>
<br>
<%= form.text_field :body%>
<br>
<% article.errors.full_messages_for(:body).each do |message|%>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
code:new.html.erb
<h1>New Article</h1>
<%= render "form", article: @article %>
テンプレートエンジンを共通化しているよ
form_with model: article do ... のところでテンプレートエンジンに対する引数みたいなものを定義しているよ
このテンプレートはアンダーバー始まりになっているよ
new のほうは@articleを引数に渡してformを呼び出しているよ
7.5 Deleting an Article
destory, redirect_to するよ
引数なしのメソッド呼び出しで括弧を書かないの気持ち悪いなって思ってたけど、別に書いても怒られないのかlemonadern.icon
code:show.html.erb
<li><%= link_to "Destroy", article_path(@article), data:{
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
key: :valueはハッシュを作るみたいなこと?
render :edit, status: :unprocessable_entity の第2引数はそういうことか
turboってなんだろう、俺の知ってるturbo (turbopack)ではなさそうlemonadern.icon
Rails でJavaScriptを書かずにSPA的なことしたいよ〜って感じ?
Hotwireとはどう違う?
Hotwireのサブドメインにはなってるんだ?
destoryボタンを押したら window.confirm のやつが出てきた
2024-10-21 09:41
8 Adding a Second Model
モデルを扱う練習としてコメントを追加してみるよ
8.1 Generating a Model
おなじみジェネレータ
bin/rails generate model Comment commenter:string body:text article:references
article:referencesすごいな
articleって名前のテーブルを探しに行ってIDを勝手に持つようにするってことなんだろうな
実際にはどういうフィールド名で持ってるんだろう
It creates a new column on your database table with the provided model name appended with an _id that can hold integer values.
article_id となってるらしい
migrateしたあとの schema.rbで追加された部分
勝手にインデックスもはられている
add_foreign_key で外部キー制約を宣言している?
code:;schema.rb
create_table "comments", force: :cascade do |t|
t.string "commenter"
t.text "body"
t.integer "article_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "comments", "articles"
8.2 Associating Models
RailsのModel上での関係は書いてあげる必要がある
Commentでは belong_to が生成されている
Articleの方に数量関係を明示してあげる必要がある
has_many :comments
ここはCommentsなんだ
参照してるのはテーブルだからなのか、複数返ってくるからなのかどっちだろう #wakaran has_one の場合は単数形で書くっぽいので複数返ってくるからっぽい
でもこれ変数名決めてるだけか?間違えても特に怒られるとかはなさそう
8.3 Adding a Route for Comments
code:before.rb
resources :articles
code:after.rb
resources :articles do
resources :comments
end
おもしろいlemonadern.icon
8.4 Generating a Controller
関係ないけど、find_by_sqlってメソッドがあった
code:rails_console
blog(dev)> Comment.find_by_sql("select id, commenter, body, article_id, created_at from comments order by created_at desc limit 5")
Comment Load (0.1ms) select id, commenter, body, article_id, created_at from comments order by created_at desc limit 5
=>
[#<Comment:0x00007371453ea3d0
id: 2,
commenter: "Daniel Harrow",
body: "I am a physiologist",
article_id: 3,
created_at: "2024-10-21 01:21:59.886215000 +0000">,
id: 1,
commenter: "john doe",
body: "hi,",
article_id: 3,
created_at: "2024-10-21 01:20:23.236850000 +0000">]
9 Refactoring
9.1
comments/_comment.html.erb にコメントのテンプレートを書く
articles/show.html.erb で <%= render @article.comments %> とすると勝手にループして_commentの内容を呼び出すらしい、すごlemonadern.icon
9.2
フォームをテンプレートに抜き出す
こっちはパスと変数を指定する
<%= render "comments/form", article: @article %>
9.3 Using Concerns
concernというのはmixin的なやつだよ
ここでは、concernを使ってモデルにstatusを追加してみるらしい
Article, Comment に status:string を追加
まずはベタ書き
アプリケーション側でステータスを定義する
Article, Comment に同じものを書く
inclusion: { in: VALID_STATUSES }
archived?
? がつくメソッドおもしろい
Rust脳なので変に感じるlemonadern.icon
Viewではunless、method? で表示非表示を制御する
Concern
code:models/concerns/visible.rb
module Visible
extend ActiveSupport::Concern
included do
validates :status, inclusion: { in: VALID_STATUSES }
end
def archived?
status == "archived"
end
end
モデルの定義でinclude Visibleして使う
こんなん使うんかな
class_methods するとクラスのメソッドみたいに使えるらしい
10 Deleting Comments
commentのdestoryは、Articleを撮ってきてから@article.comments.findしてdestory
Articleを消したときにコメントごと消えるようにするには、Articleにdependant: :destoryを定義する
11 Security
11.1 Basic Authentication
basic auth を簡単にかけられる
Railsの認証gem
11.2 Security Consideration
2024-10-21 10:44 一旦おわり