🎉

個人的Rubyおよび、Railsの良いところ悪いところまとめ

2025/03/13に公開

個人的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