NestJSに慣れた仙人がRuby(Rails)をキャッチアップする様
bundle exec(依存関係のインストール)される時の流れ
- bundleコマンドのtips: 通常はrailsを打つ場合はbundle execを利用しなければならないが、direnvでgemの実行ファイルのPATHを指定することでbundle execを利用せずともシェルがコマンドを見つけることができる。
- ex.
bundle exec rails server
→rails server
だけで済むので楽 -
bundle exec
はRubyのコマンドを実行する際に、そのコマンドがgemfile.lockで指定されたバージョンンのgemを使用することを保証するためのコマンド。
- ex.
-
bundle config
でgemをbundle install
でインストールする時の設定を色々いじれる。- 設定スコープ(--local、--global、--system)によって設定が保存される場所が異なる。
- --localは現在のプロジェクトのディレクトリ(具体的には.bundle/config)、--globalはユーザーディレクトリ(具体的には~/.bundle/config)、--systemはシステム全体(具体的には/usr/local/bundle/configや/opt/rubies/ruby-x.y.z/lib/ruby/gems/x.y.z/configなど)
- ex. https://zenn.dev/orange634nty/articles/d3dd8ec1df63ec (--with-cflags=-Wno-error=implicit-function-declarationで、ビルド時のc99エラーを無視する、など)
- 慣れ親しんだnode.jsとrubyの依存管理の違いは、node.jsはnpm(or yarn)単体でパッケージのダウンロード・依存関係管理ができるのに対して、rubyはパッケージのinstall自体はgem, 依存管理はbundleと分かれている。
@@
でクラス変数(そのクラス内やサブクラス内をスコープとして使える変数)を利用できる。ただスコープが広く意図せずして参照・変更されるリスクがあり、インスタンス変数(@hoge
)を利用するのが良さげ。
class MyClass
@@class_variable = "Hello, world!"
def self.print_class_variable
puts @@class_variable
end
def print_class_variable
puts @@class_variable
end
end
obj = MyClass.new
MyClass.print_class_variable # => "Hello, world!"
obj.print_class_variable # => "Hello, world!"
- クラス内で
class << self
(クラスメソッド)を記述できる- 利用例:
- ファクトリーパターン
- シングルトンパターン
- ↑はイマイチピンと来ていない。クラスメソッドを使わなくてもいい気がするが。
-
new.Animal("Dog")
はnew("Dog")
と省略できる。 - クラスメソッド内で呼び出せるのはクラスメソッドだけ。インスタンスメソッドは直接呼び出せない
- クラス内でそのクラス自体をインスタンス化するという挙動ができる。インスタンス化すればインスタンスメソッドは呼び出せる。
ファクトリーパターンのコード:
class Animal
attr_accessor :name
def initialize(name)
@name = name
end
class << self
def create_dog
new("Dog") # Animalクラスの新しいインスタンスを生成
end
def create_cat
new("Cat") # Animalクラスの新しいインスタンスを生成
end
end
end
dog = Animal.create_dog
cat = Animal.create_cat
puts dog.name # => "Dog"
puts cat.name # => "Cat"
JSでは!0はtrueだったが、Rubyではfalse。これはnil以外はtrueという性質があるため。truthy, falthyがわかりやすくてgood
モジュールについて
モジュールはクラスをまたがって共通化したい処理に使う。多重継承を避けるため。クラスにモジュールをincludeすることをmix-in
という。
無関係なクラスで継承を使うのはだめ。なぜかというと、継承はis-aの関係が成り立たないのであれば使わないのが吉であるため。「has-a」(「〜が〜を持っている」という)関係を示したい場合はコンポジション(クラスが他のクラスのインスタンスを属性として持つ)を使い、複数のクラス間で特定の振る舞いを共有したい場合はモジュールを使う方がメンテしやすくなる。
モジュール内でinclude先のメソッドを定義することもできる。これは「メソッドを実行する瞬間にそのメソッドが呼び出せればいい」という思想がRubyにはあるため。
module Taggable
def price_tag:
"#{price}円"
end
class Product
include Taggable
def price
1000
end
end
product = Product.new
product.price_tag # => 1000円
例外処理のJS / Ruby比較
JS
例外処理: try-carch-finnaly
例外発生: throw
const hoge = () => {
try {
fuga()
} catch (e) {
throw new Error()
} finally {
console.log("process is done")
}
}
Ruby
例外処理: begin-rescue-ensure
例外生成: raise
def hoge
begin
fuga()
rescue => e
raise StandardError.new
ensure
puts "process is done"
end
end
Rubyでオブジェクト指向プログラミング
ダックタイピング
def quack(duck)
duck.quack
end
class Duck
def quack
puts 'Quack!'
end
end
class Dog
def quack
puts 'Woof!'
end
end
quack(Duck.new) # Prints: 'Quack!'
quack(Dog.new) # Prints: 'Woof!'
quack
関数は、渡された引数のオブジェクトがquack
メソッドを持っている前提で定義されている。このように振る舞いに着目したプログラミング方法をダックタイピングと言う。渡された引数は、Duckクラスでなくとも良い。Dogクラスのインスタンスでも、それがquack
メソッドを持っていれば問題ない。
ダックタイピングを使ってDIP
DIP = SOLIDのD。 依存関係逆転の原則(Dependency Inversion Principle)。
概要:
- 高レベルのモジュールは低レベルのモジュールに依存すべきではない。両方とも抽象に依存すべきである。
- 抽象は詳細に依存すべきではない。詳細が抽象に依存すべきである。
class XMLParser
def parse
puts 'Parsing XML...'
# XML parsing logic here
end
end
class JSONParser
def parse
puts 'Parsing JSON...'
# JSON parsing logic here
end
end
Document
クラスを作成。このクラスは具体的なパーサーに依存せず、どんな種類のパーサーでも受け入れることができる。
class Document
def initialize(parser)
@parser = parser
end
def parse
@parser.parse
end
end
このようにすると、Document
クラスは具体的なパーサークラス(XMLParser
やJSONParser
)に依存せず、代わりにparse
メソッドを持つ任意のオブジェクト(これが抽象に該当)に依存する。
使用例:
xml_parser = XMLParser.new
document1 = Document.new(xml_parser)
document1.parse
# Output: Parsing XML...
json_parser = JSONParser.new
document2 = Document.new(json_parser)
document2.parse
# Output: Parsing JSON...
このように、Document
クラスは具体的なクラスに依存せず、代わりに抽象(ここではparse
メソッドを持つオブジェクト)に依存する。これにより、新たな種類のパーサーを追加するときもDocument
クラスは変更する必要がなく、コードの柔軟性と再利用性が高まる。
モジュールを使ってインターフェースを模倣する
→これとかが参考になる。
モジュールを使ってインターフェースを模倣することができる。"契約"の形でメソッドを定義(契約を守らないクラスがあった場合エラーを出す)し、そのモジュールを具体的なクラスでincludeしメソッドを再定義する。
ダックタイピングに加えて、インターフェースを模倣しDIPを実現してみる。
まず、インターフェースを表すモジュールを定義する:
module Parser
def parse
raise NotImplementedError, 'You must implement the parse method'
end
end
次に、このParser
モジュールを各具体的なクラスでincludeする:
class XMLParser
include Parser
def parse
puts 'Parsing XML...'
# XML parsing logic here
end
end
class JSONParser
include Parser
def parse
puts 'Parsing JSON...'
# JSON parsing logic here
end
end
このようにすることで、各具体的なパーサークラスはParser
モジュールの契約(parse
メソッドを実装する)を満たすことが期待される。
最後に、依存関係を持つクラス(ここではDocument
クラス)を作成する。このクラスは具体的なパーサークラスに依存せず、Parser
モジュールの契約を満たす任意のオブジェクトに依存する:
class Document
def initialize(parser)
unless parser.is_a?(Parser)
raise ArgumentError, 'Invalid parser'
end
@parser = parser
end
def parse
@parser.parse
end
end
ここでは、初期化時にパーサーオブジェクトがParser
モジュールの契約を満たしていることをチェックする。これにより、Document
クラスのparse
メソッドは、パーサーオブジェクトがParser
モジュールのparse
メソッドを実装していることを確信できる。
個人的感想→ビルドする前にコンパイラに怒られるって言うのがインターフェースのメリットだと思う。模倣したインターフェースでは、仮に変なメソッドを定義していたとしても、実際にデプロイするまで気づかないというデメリットがある。不具合としてのエラーではなく仕様としてのエラーを出すというのも冗長な気がする・・・。
ブロックは、さまざまなデータを持つ引数として用いられることがある。定義する関数は&blockで引数を受け取る
スーパークラスで、下位クラスのクラスメソッドを呼び出すと、スーパークラスに書かれたコードが実行されたあとクラスメソッドが実行される。これが結構わかりにくい上に、実務のコードで結構見かけたりする。。。
下記ChatGPT先生に生成してもらった例
====================================================
モジュールを使ってメソッドを共有し、別のクラスでそのメソッドを使用する例を示します。具体的には、クラスが持つ特定の属性の値をキャッシュし、それを取得するクラスメソッドを定義します。
まず、以下のようなモジュールを定義します。
module Cachable
extend ActiveSupport::Concern
class_methods do
def cache_attribute(attribute)
@cached_attribute = attribute
end
def get_cached
@cached_attribute
end
end
end
ここで、cache_attribute
はクラス変数@cached_attribute
に特定の属性を設定するクラスメソッドで、get_cached
はその設定した属性を取得するメソッドです。
次に、このCachable
モジュールをインクルードするクラスを定義します。
class MyClass
include Cachable
cache_attribute :some_attribute
end
ここで、MyClass
クラスがロードされると同時に、cache_attribute :some_attribute
が実行され、@cached_attribute
に:some_attribute
が設定されます。
そして、以下のようにget_cached
メソッドを使用することで、設定した属性を取得することができます。
puts MyClass.get_cached # => :some_attribute
こうした方法で、モジュールを使用してクラスに振る舞いを追加し、その振る舞いをそのクラスの一部として利用することができます。