🎉
個人的Rubyおよび、Railsの良いところ悪いところまとめ
個人的Rubyおよび、Railsの良いところ悪いところまとめ
Rubyとは?
- まつもとゆきひろ氏によって開発された、オブジェクト指向スクリプト言語
- シンプルで可読性の高い文法を持ち、多様な用途で使用可能
- 動的型付け言語であり、柔軟なコード記述が可能
Ruby on Railsとは?
- Rubyで記述されたWebアプリケーションフレームワーク
- ActiveRecordなどの強力な機能を提供し、データベース操作を容易にする
- Webアプリケーションに必要なものを大体内包しており、素早く立ち上げが可能
良いところ
ActiveRecord
- データベース操作をRubyのオブジェクトとして扱えるORM(Object-Relational Mapping)
- データベースの種類に依存しないコード記述が可能
# Ruby (ActiveRecord)
# 例:ユーザーの作成、取得、更新
# 新しいユーザーを作成
user = User.create(name: "John Doe", email: "john.doe@example.com")
# IDでユーザーを取得
user = User.find(1)
# ユーザーの属性を更新
user.name = "Jane Doe"
user.save
# 特定の条件でユーザーを検索
users = User.where(name: "John Doe")
// TypeScript (例: TypeORM)
import { getRepository } from "typeorm";
import { User } from "./entity/User";
// 例:ユーザーの作成、取得、更新
async function main() {
// ユーザーリポジトリを取得
const userRepository = getRepository(User);
// 新しいユーザーを作成
const user = userRepository.create({ name: "John Doe", email: "john.doe@example.com" });
await userRepository.save(user);
// IDでユーザーを取得
const foundUser = await userRepository.findOne({ where: { id: 1 } });
if (foundUser) {
// ユーザーの属性を更新
foundUser.name = "Jane Doe";
await userRepository.save(foundUser);
// 特定の条件でユーザーを検索
const users = await userRepository.find({ where: { name: "John Doe" } });
console.log(users);
}
}
main();
タスクランナー(Rake)
- 繰り返し行うタスクを自動化できるツール
- テスト、デプロイ、バッチ処理など、様々なタスクを定義可能
- シェルからの実行も容易
# Rakefile
# 例:Hello Worldを出力するタスク
task :hello do
puts "Hello, world!"
end
# Rakeタスクの実行例
rake hello
クラス拡張
- 既存のクラスにメソッドや属性を追加できる
- コードの再利用性を高め、柔軟な機能追加が可能
- モンキーパッチとも呼ばれる
# Ruby
# 例:Stringクラスに新しいメソッドを追加
class String
def shout
self.upcase + "!"
end
end
# 使用例
"hello".shout # => "HELLO!"
// TypeScript
// 例:Stringプロトタイプに新しいメソッドを追加
declare global {
interface String {
shout(): string;
}
}
String.prototype.shout = function() {
return this.toUpperCase() + "!";
};
// 使用例
"hello".shout(); // => "HELLO!"
DSL作成
- 特定の問題領域に特化した言語(DSL)を作成できる
- コードの可読性、保守性を向上させ、開発効率を高める
- Rubyのメタプログラミング機能によって、DSLの作成が容易
# ActiveRecordでのDSL作成例:状態管理
# app/models/concerns/state_machine.rb
module StateMachine
extend ActiveSupport::Concern
included do
class_attribute :state_transitions, default: {}
enum state: state_transitions.keys if respond_to?(:enum) # Rails 5+
end
class_methods do
def states(*states)
states.each do |state|
state_transitions[state] = {}
end
enum state: state_transitions.keys if respond_to?(:enum) # Rails 5+
end
def transitions(options)
from = options[:from]
to = options[:to]
on = options[:on]
state_transitions[from][on] = to
end
end
def transition(event)
to_state = state_transitions[self.state.to_sym][event.to_sym]
if to_state
self.state = to_state
save
else
raise "Invalid transition from #{self.state} on #{event}"
end
end
end
# app/models/order.rb
class Order < ApplicationRecord
include StateMachine
states :pending, :processing, :shipped, :delivered, :cancelled
transitions from: :pending, on: :process, to: :processing
transitions from: :processing, on: :ship, to: :shipped
transitions from: :shipped, on: :deliver, to: :delivered
transitions from: :pending, on: :cancel, to: :cancelled
transitions from: :processing, on: :cancel, to: :cancelled
end
rails console
- 対話型環境で、Railsアプリケーションの操作やデバッグが可能
- モデルの操作、データベースの確認、コードの実行などをリアルタイムに行える
- 開発効率を大幅に向上させる強力なツール
その他
- 日本語の解説記事が多い
悪いところ
動的なクラス、メソッドの生成
class MyClass
[
"hoge",
"fuga",
].each do |name|
define_method(name+"_test") do |arg|
puts "Method #{name} called with #{arg}"
end
end
end
obj = MyClass.new
obj.hoge_test("taro")
obj.fuga_test("tama")
このコードでは、MyClassの内部でdefine_methodを使って動的にメソッドを生成しています。具体的には、配列["hoge", "fuga"]の各要素に対して、hoge_test、fuga_testというメソッドを生成しています。
問題点
動的に生成されたメソッドは、ソースコード上に明示的には存在しません。そのため、コードを読む人がクラスのインターフェースを把握するのが難しくなります。/hoge_test/ だと、定義元がgrepしても引っかかりません
ダックタイピング
- オブジェクトの型ではなく、メソッドの存在によって動作が決まる仕組み
- 柔軟なコード記述が可能だが、実行時エラーが発生しやすい
- インターフェースと違い、明示性がなく、保守性を低下させる場合がある
# ダックタイピングの例
class Duck
def quack
puts "ガーガー!"
end
end
class Person
def quack
puts "僕はアヒルじゃないよ!"
end
end
def make_it_quack(quacker)
quacker.quack
end
duck = Duck.new
person = Person.new
make_it_quack(duck) # => "ガーガー!"
make_it_quack(person) # => "僕はアヒルじゃないよ!"
事例:メール送信システムの移行
初期段階では、SES(Simple Email Service)を使用してメール送信を行っていたとします。
class SesMailer
def send(to, subject, body)
puts "SESでメールを送信: #{to}, #{subject}"
# SES APIを使ったメール送信処理
end
# ... その他のSES固有のメソッド ...
end
# ... メール送信処理 ...
mailer = SesMailer.new
mailer.send("recipient@example.com", "件名", "本文")
数年後、より高度な機能を持つSendGridへの移行が決まりました。ここで問題となるのは、SendGridMailer
クラスにどのようなメソッドを実装すればよいのか、過去のコードから明確に判断できないことです。
class SendGridMailer
def send(to, subject, body)
puts "SendGridでメールを送信: #{to}, #{subject}"
# SendGrid APIを使ったメール送信処理
end
# 他に何を作成しないといけないんだ...??
end
擬似インターフェースによる対策とその限界
この問題を回避するために、Mailer
という基底クラスを導入し、必要なメソッドを定義する方法があります。
class Mailer
def send(to, subject, body)
raise NotImplementedError
end
end
class SesMailer < Mailer
def send(to, subject, body)
puts "SESでメールを送信: #{to}, #{subject}"
# SES APIを使ったメール送信処理
end
# ...
end
class SendGridMailer < Mailer
def send(to, subject, body)
puts "SendGridでメールを送信: #{to}, #{subject}"
# SendGrid APIを使ったメール送信処理
end
# ...
end
これにより、SendGridMailer
クラスがsend
メソッドを実装することを強制できます。が、実行時エラーのため、動かしてみないとわかりません。
このようにMailer
クラスに定義されたメソッドだけで本当に十分なのか、過去のコードを深く理解しない限り判断できません。
静的型付け言語におけるインターフェース
interface Mailer {
send(to: string, subject: string, body: string): void;
}
class SesMailer implements Mailer {
send(to: string, subject: string, body: string): void {
console.log(`SESでメール送信: ${to}, ${subject}`);
// SES APIを使ったメール送信処理
}
// ... その他のSES固有のメソッド ...
}
class SendGridMailer implements Mailer {
send(to: string, subject: string, body: string): void {
console.log(`SendGridでメール送信: ${to}, ${subject}`);
// SendGrid APIを使ったメール送信処理
}
// ... SendGrid固有のメソッド ...
}
// ... メール送信処理 ...
const mailer: Mailer = new SesMailer();
mailer.send("recipient@example.com", "件名", "本文");
静的型付け言語におけるインターフェースは、このような問題を回避するための有効な手段です。インターフェースを明示的に定義することで、オブジェクトが満たすべき契約を明確にし、コンパイル時に型チェックを行うことができます。これにより、実行時エラーを減らし、コードの信頼性を高めることができます。
まとめ
- Ruby/Railsは記述が容易で、プロトタイピングや小規模プログラム開発に最適。
- 大規模プロジェクトでは、動的な特性やダックタイピングにより、可読性・保守性が低下する可能性あり。
- 大規模Webサービスでの利用には注意が必要。
- 運用次第でGitHubのように大規模サービスでも活用可能。
- 適切な場面で利用することで、開発効率を向上させることができる。
Discussion