Railsキャッチアップ
Rubyバージョン確認
Dockerの設定は下記を参考にする
Railsバージョン確認
立ち上げる
root@6ca4f94a0867:/app# rails -v
Rails 7.0.8
root@6ca4f94a0867:/app# ruby -v
ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [aarch64-linux]
ここ見ながら進めていく
雛形作成は便利だが実務で使うのか🤔
apiモードで遊ぶ
Rubyのシンボルについて学ぶ
- 内部で整数で管理される
- 同じobject_idで管理されるため同値ならば必ず同一
- 書き換え不可
言語仕様は下記から学ぶ
クラスとモジュールの違い
クラスとモジュールには
- クラスはインスタンスを作成できるが、モジュールはできない。
- モジュールを他のモジュールやクラスにインクルードすることはできるが,クラスをインクルードすることはできない。
という違いがありますが、それ以外のほとんどの機能は Module から継承されています。Module のメソッドのうち
Module#module_function
Module#extend_object
Module#append_features
Module#prepend_features
Module#refine
は Class では未定義にされています。
クラス変数とクラスインスタンス変数の違い
インスタンス変数はPHPで言うクラス直下で宣言されたプライベート変数のようなもの。
class Hello
@hoge = "hoge"
def initialize
p @hoge
end
def self.value
@hoge
end
def setFuga
@hoge = "fuga"
end
def say
p @hoge
p "hello world"
end
end
p Hello.value
hello = Hello.new
hello.setFuga
hello.say
#"hoge"
#nil
#"fuga"
#"hello world"
@hoge = "hoge"
だけではnilではダメでセッターで宣言しないといけない?
インスタンスの場合は初期化時にセットしておけばよいか
def initialize
@hoge = "hoge"
end
`@'で始まる変数はインスタンス変数であり、特定のオブジェクトに所属しています。インスタンス変数はそのクラスまたはサブクラスのメソッドから参照できます。初期化されていないインスタンス変数を参照した時の値はnilです。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html
家電を動かすためにスクリプト書いてみる
キーワード引数便利そう
Railsでは、アプリケーションのクラスやモジュールはどこでも利用できるようになっているので、上のようにrequireを書く必要はありませんし、app/ディレクトリの下で何かを読み込むためにrequireを書いてはいけません。この機能は「自動読み込み(autoloading: オートロード)」と呼ばれています。詳しくはガイドの『Railsの自動読み込みと再読み込み』を参照してください。
https://railsguides.jp/getting_started.html#自動読み込み
requireを書く必要があるのは、以下の2つの場合だけです。
lib/ディレクトリの下にあるファイルを読み込む場合
Gemfileでrequire: falseが指定されているgem依存を読み込む場合
メソッドにブロックを渡せる。
渡したブロックはyieldで使用できる。
Rackとは
DSLの定義が曖昧だったのでインプット
- 言語としての役割が一般的なプログラミング言語と比べて限定的で、その代わりに特定の問題領域に精通した操作を容易に可能
- 特定のタスクを解決するために使用する際には、一般的なプログラミング言語と比べて直感的で理解しやすい表現を提供
DSLは2種類
- 内部DSL(内部的DSL、または組み込みDSL): これは一般的なプログラミング言語の中で設計され、その言語の構文を使用します。例えば、Ruby言語によるRuby on Railsのルーティング記述やRSpecのテスト記述などがこれに該当します。
- 外部DSL(外部的DSL、またはスタンドアロンDSL): これは特定のプログラミング言語から独立した新しい言語で、専用のパーサを使って解析されます。例えば、SQL(データベース操作)、CSS(ウェブページのスタイリング)、HTML(ウェブページのマークアップ)などがこれに該当します。
Ruby on Railsのルーティング定義やRSpecのテストケース定義は、DSLの一例です。これらは通常のRubyのシンタックスを使用しつつも、より直感的でドメイン特定的な記述を可能にしています。
RackはRailsのようなRuby製のwebフレームワークとアプリケーションサーバーの両方が話せる共通言語のようなものだと考えてください。両者が共通言語を理解できるので、RailsはUnicornと話せますし、UnicornはRailsと話せます。しかも、RailsもUnicornも相手のことを知っておく必要は全くありません。
https://qiita.com/jnchito/items/3884f9a2ccc057f8f3a3#rackとは
雰囲気は分かった
Railsはpumaなどのwebサーバーの種別を意識せずに会話ができる。
PumaはRailsにおけるアプリケーションサーバー。
ローカルで開発しているとwebサーバーとして振る舞っているように見える。
本番環境ではNginxやApacheと併用される。
- HTTPリクエストをWebサーバーであるNginxやApacheで受けとる
- NginxはそのリクエストをPumaにSocket通信で渡す
- PumaはそのリクエストをRack::Serverに渡す
- 最終的にRailsのRouterに渡される
https://qiita.com/yusuke2310/items/1695cd702cdf25d34fbc
pumaとunicornの違い
unicornはプロセスベース
workerプロセスが2つあったら、2つのリクエストを同時に処理できる
メリット
シンプル(理解しやすい + コードが綺麗になりやすい)
スレッドの知識がなくても安心
スレッドセーフなコードを意識しなくても良い(普通にRailsアプリを書いていれば問題なさそうだけど、スレッドの知識が浅いので不安)
pumaはスレッドベース
スレッドが2つあったら、2つのリクエストを同時に処理できる
実際には本番環境ではマルチプロセス + マルチスレッドで動かす(Clustered mode)。workerプロセスが2つ + スレッドが2つだったら、4つのリクエストを同時に処理できる
ただし実際には、MRIではGILがあるため1プロセスで1スレッドしか実行されない
メリット
(MRIだとしても)IO時に別スレッドに処理させることができる
スロークライアントの影響を受けにくい
メモリ使用量が少ない(参考: http://puma.io/)MRIのスレッド
スレッドは複数持てるが、同時に実行できるスレッドは1つ。GIL(Global Interpreter Lock)のため。
ただし、Bocking IO(ファイルIO、ネットワークIO等)になった際に、別のスレッドに切り替えて処理を進める。MRIでもスレッドベースの恩恵は部分的にある。
IOはだいたい総時間の10~25%程度らしい(参考: https://techracho.bpsinc.jp/hachi8833/2017_11_13/47696)スロークライアント
回線の遅いクライアント(3Gのモバイル端末など)
Pumaはスレッドベースなので、IOの際に(MRIだとしても)別のスレッドに処理をさせることができる。なのでネットワークIOが長いスロークライアントには都合がよい
プロセスとスレッドの基礎が無いのでインプット
プロセス
独立の仮想メモリ空間を保有している処理の単位。実行中のプログラム。
プロセス間では基本的にメモリは共有されない。
1つ以上のスレッドから構成される。
スレッド
1つのプロセスに割り当てられた仮想メモリ内で動作する処理の単位。
割り当てられた仮想プロセッサ(CPUコア)を占有する。
スレッド間ではメモリが共有される。
スレッド間では同じデータに簡単にアクセスできる。
Active Record マイグレーションファイル作成
カラムを追加する場合
# example
# rails g migration {migration_name} {column}*n
rails g migration AddDescriptionToGroups description:string
上記コマンドで生成されるファイル
class AddDescriptionToGroups < ActiveRecord::Migration[7.0]
def change
add_column :groups, :description, :string
end
end
マイグレーションされるまで
$ rails db:migrate:status
database: app_development
Status Migration ID Migration Name
--------------------------------------------------
up 20240115133247 Create groups
down 20240324050919 Add description to groups
$ rails db:migrate
== 20240324050919 AddDescriptionToGroups: migrating ===========================
-- add_column(:groups, :description, :string)
-> 0.0595s
== 20240324050919 AddDescriptionToGroups: migrated (0.0596s) ==================
$ rails db:migrate:status
database: app_development
Status Migration ID Migration Name
--------------------------------------------------
up 20240115133247 Create groups
up 20240324050919 Add description to groups
Active Recordは、このマイグレーションを逆進させる方法も知っています。
https://railsguides.jp/active_record_migrations.html#新しいカラムを追加する
up,downで記載してもよいがActiveRecordがよしなにやってくれるみたい。
テストについて学んでいきたい。
テストデータはymlで管理する。
one:
name: MyString
class GroupsControllerTest < ActionDispatch::IntegrationTest
setup do
@group = groups(:one)
end
test "GET /groupsでグループ一覧を取得できる" do
get groups_url, as: :json
assert_response :success
end
end
どうやらminitestよりもrspecが主流らしいのでそちらでテストを書く。
準備
group :development, :test do
...
gem "rspec-rails"
gem "factory_bot_rails"
...
end
bundle install
rails generate rspec:install
メソッド事にdescribeで区切ると読みやすい
require 'rails_helper'
RSpec.describe Book, type: :model do
describe "Book#title_with_author" do
it "Book#title_with_authorを呼び出したとき、titleとauthorを結んだ文字列が返ること" do
book = Book.new(title: "RubyBook", author: "matz")
expect(book.title_with_author).to eq("RubyBook - matz")
end
end
end
下記に移行
ActiveRecordのcreateとcreate!違い
create!の場合、バリデーションで有効なデータではないと判定された場合、例外を出す