Open38

Railsキャッチアップ

miniseraminisera

立ち上げる

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]
miniseraminisera
miniseraminisera

クラスとモジュールには

  • クラスはインスタンスを作成できるが、モジュールはできない。
  • モジュールを他のモジュールやクラスにインクルードすることはできるが,クラスをインクルードすることはできない。
    という違いがありますが、それ以外のほとんどの機能は Module から継承されています。Module のメソッドのうち

Module#module_function
Module#extend_object
Module#append_features
Module#prepend_features
Module#refine
は Class では未定義にされています。

https://docs.ruby-lang.org/ja/3.0/class/Class.html

miniseraminisera
miniseraminisera

インスタンス変数は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"
miniseraminisera

インスタンスの場合は初期化時にセットしておけばよいか

    def initialize
        @hoge = "hoge"
    end
miniseraminisera

Railsでは、アプリケーションのクラスやモジュールはどこでも利用できるようになっているので、上のようにrequireを書く必要はありませんし、app/ディレクトリの下で何かを読み込むためにrequireを書いてはいけません。この機能は「自動読み込み(autoloading: オートロード)」と呼ばれています。詳しくはガイドの『Railsの自動読み込みと再読み込み』を参照してください。
https://railsguides.jp/getting_started.html#自動読み込み

miniseraminisera

requireを書く必要があるのは、以下の2つの場合だけです。

lib/ディレクトリの下にあるファイルを読み込む場合
Gemfileでrequire: falseが指定されているgem依存を読み込む場合

miniseraminisera
miniseraminisera

DSLの定義が曖昧だったのでインプット
https://gihyo.jp/admin/feature/01/dsl/0001

  • 言語としての役割が一般的なプログラミング言語と比べて限定的で、その代わりに特定の問題領域に精通した操作を容易に可能
  • 特定のタスクを解決するために使用する際には、一般的なプログラミング言語と比べて直感的で理解しやすい表現を提供
miniseraminisera

DSLは2種類

  1. 内部DSL(内部的DSL、または組み込みDSL): これは一般的なプログラミング言語の中で設計され、その言語の構文を使用します。例えば、Ruby言語によるRuby on Railsのルーティング記述やRSpecのテスト記述などがこれに該当します。
  2. 外部DSL(外部的DSL、またはスタンドアロンDSL): これは特定のプログラミング言語から独立した新しい言語で、専用のパーサを使って解析されます。例えば、SQL(データベース操作)、CSS(ウェブページのスタイリング)、HTML(ウェブページのマークアップ)などがこれに該当します。

Ruby on Railsのルーティング定義やRSpecのテストケース定義は、DSLの一例です。これらは通常のRubyのシンタックスを使用しつつも、より直感的でドメイン特定的な記述を可能にしています。

miniseraminisera

RackはRailsのようなRuby製のwebフレームワークとアプリケーションサーバーの両方が話せる共通言語のようなものだと考えてください。両者が共通言語を理解できるので、RailsはUnicornと話せますし、UnicornはRailsと話せます。しかも、RailsもUnicornも相手のことを知っておく必要は全くありません。
https://qiita.com/jnchito/items/3884f9a2ccc057f8f3a3#rackとは

雰囲気は分かった

miniseraminisera

Railsはpumaなどのwebサーバーの種別を意識せずに会話ができる。

miniseraminisera

PumaはRailsにおけるアプリケーションサーバー。
ローカルで開発しているとwebサーバーとして振る舞っているように見える。
本番環境ではNginxやApacheと併用される。

  1. HTTPリクエストをWebサーバーであるNginxやApacheで受けとる
  2. NginxはそのリクエストをPumaにSocket通信で渡す
  3. PumaはそのリクエストをRack::Serverに渡す
  4. 最終的にRailsのRouterに渡される
    https://qiita.com/yusuke2310/items/1695cd702cdf25d34fbc
miniseraminisera

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が長いスロークライアントには都合がよい

https://nekorails.hatenablog.com/entry/2018/10/12/101011#スレッドベース

miniseraminisera

プロセスとスレッドの基礎が無いのでインプット

プロセス
独立の仮想メモリ空間を保有している処理の単位。実行中のプログラム。
プロセス間では基本的にメモリは共有されない。
1つ以上のスレッドから構成される。

スレッド
1つのプロセスに割り当てられた仮想メモリ内で動作する処理の単位。
割り当てられた仮想プロセッサ(CPUコア)を占有する。
スレッド間ではメモリが共有される。
スレッド間では同じデータに簡単にアクセスできる。

https://qiita.com/yokoto/items/3b4bcc489d93bc57baf3
https://zenn.dev/antez/books/568dd4d86562a1/viewer/531a45

miniseraminisera

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
miniseraminisera

どうやらminitestよりもrspecが主流らしいのでそちらでテストを書く。

https://zenn.dev/igaiga/books/rails-practice-note/viewer/rails_rspec_workshop

miniseraminisera

準備

Gemfile
group :development, :test do
  ...
  gem "rspec-rails"
  gem "factory_bot_rails"
  ...
end
bundle install
rails generate rspec:install
miniseraminisera

メソッド事に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