BEMAロゴ

エンジニアの
成長を支援する
技術メディア

JavaScriptをずっとやっていたエンジニアがRubyを学んでみた

この記事は「BEMA Lab Advent Calendar 2025Open in new tab」の13日目の記事です。
※本アドベントカレンダーの13日目の投稿となります。

はじめに 

私は趣味も含めてJavaScriptをよく書いていたが、最近、業務でRubyに触れることが増加している。この機会にJavaScriptを書く人間(エンジニア)が、Rubyに初めて触れたときに思ったことやJavaScriptとの違いについて書いて考察する。

if

Rubyのifは最後に評価された式の結果が返ってくる。

x = 42
result = if x % 2 == 0
 'even'
else
 'odd'
end

result # even

if式は、後置で条件を書ける。早期returnなどで見ることがある。

def x(v)
  return v if v == 42
  v * 2
end

x(42) # 42

その他、条件が反転しているunlessがあったり
else ifelsifだったりする。

差分、思ったこと

jsのifと比べて痒い所に手が届く作りだなと思った。
jsで値を返すifを書くには、三項演算子を除けば

const x = 42;
const result = (() => {
  if (x % 2 === 0) {
    return "even";
  } else {
    return "odd";
  }
})();

IIFEOpen in new tabするくらいしか方法がなく
個人的には「このためだけに関数定義走ったりコールスタック積まれるの大掛かりかも」
みたいなランタイム貧乏性な気持ちになるので純粋に羨ましい。
(実行時最適化を信じてIIFEを書いても全然いいと思います)

ブロック

処理の塊(`do ~ end`とかで書く)、メソッドの引数部分でのみ存在できる。

[1,2,3].each do |v|
  p(v)
end

jsでいうと無名関数が使い方的には近いと思う。

[1, 2, 3].each((v) => {
  console.log(v);
});

差分、思ったこと

改めて見ると`each do ~ end`は確かにeachメソッドの引数にブロックを渡す形だなと思った。
jsの無名関数はただの関数なので変数に入るが、ブロックの場合はProcというのを使わないと値にならないらしい。
ただ、Procを使うコードを業務で見たことはあまりないなと思う。

# ng
block = do |v|
  p(v)
end
# ok
block = Proc.new do |v|
  p(v)
end

block.call('hoge')

また、メソッドや関数といったものではないため、ブロック内で`return`と書いたとき、ブロックを呼び出しているメソッドから抜ける。
jsの気持ちで書いていたら見事にハマった。
ブロック内の処理を切り上げるときは`next`を使うと意図した挙動になる。

シンボル

Rubyにはシンボルというデータ型がある。
シンボルはイミュータブルで同じ名前のものは同じ値になる。主にコード中のデータやメソッドの識別子として使われる。

# シンボルリテラル
:name
:name == :name # => true
x = :name
x[0] = 'N' # => エラー、イミュータブル
# ハッシュのキーとしてのシンボル
person = { name: "Alice", age: 25 }
person[:name] # => "Alice"

# メソッド名としてのシンボル
class MyClass
  def my_method
    "Hello"
  end
end

MyClass.instance_methods(false) # => [:my_method]
# シンボルの配列で返ってくる

obj = MyClass.new

obj.send(:my_method) # => "Hello"

差分、思ったこと

Railsだと特にシンボルでも文字列でもうまく動くことが多く、それも相まって`:`が付いているこれは何か特別な意味があるのだろうかと思っていた。
何らかの識別子であるという文脈を型に持たせるのは良い仕組みだと思った。

また、最初の頃は以下のようなハッシュのキーを直感的に文字列型だと思っていたので

person = { name: "Alice", age: 25 }
person['name'] # => nil

と書いて混乱していた。

jsにもシンボル型(Symbol)が存在するが、作られるたびに異なる一意の値を持つので振る舞いや用途はかなり違う。

const sym1 = Symbol('key');
const sym2 = Symbol('key');
console.log(sym1 === sym2); // false

演算子の再定義

Rubyの`+`や`==`のような演算子は演算子左側のインスタンスのメソッドとして定義されている。
以下のような演算の書き方は糖衣構文になっている。

'hoge' + 'fuga'
'hoge'.+('fuga') # 同値

よって、演算子はクラス毎に実装が可能で、一部種類を除いて動作を再定義できる。
例えば、文字列の引き算をjsみたいに定義することもできるし、自分で書いたクラスに対する演算を定義することもできる。

"1" - "2" # error

class String
  def -(other)
   self.to_f - other.to_f
  end
end

"1" - "2" # -1.0

差分、思ったこと

jsには演算子の再定義を行うような機能は存在しないので、どう表現するのだろうと思っていたが
二項演算をメソッド呼び出しの糖衣構文とする捉え方が統一感を出しつつ書き味を落とさない筋の良い仕様だと思った。
自分は演算を自由に定義できることに馴染みがないので

[2, 3, 5, 7] & [1, 1, 2, 3, 5, 8] # [2, 3, 5]

となるのが「話が出来すぎてないか?」ちょっと不気味だった。
また、自由度が高いことの裏返しでライブラリによっては学習コストも高くなるんじゃないかとちょっと不安になった。

メソッドの動的追加

Rubyではdefine_methodやdefine_singleton_methodを使うことでメソッドの定義を動的に増やすことができる。
また、メソッドが見つからないときに呼び出されて、本来はエラーメッセージを出す処理を行うmissing_methodをオーバーライドする方法もある。

class MyClass
  # 動的にアクセサ(reader/writer)を作る
  %i[name email].each do |attr|
    define_method(attr) { instance_variable_get("@#{attr}") }
    define_method("#{attr}=") { |v| instance_variable_set("@#{attr}", v) }
  end
  # 動的に挨拶メソッドを作る
  def self.languages(greetings)
    greetings.each do |lang, greeting|
      define_method("greet_#{lang}") do
        "#{greeting}, #{name}!"
      end
    end
  end

  # 言語を登録(メソッド実行)
  languages :en => 'Hello', :jp => 'こんにちは', :fr => 'Bonjour'

  # クラスメソッドを動的に追加して簡易ファクトリを作る(これはそのままでも書けるが、まあ)
  define_singleton_method(:build) do |attrs = {}|
    obj = new
    attrs.each { |k, v| obj.send("#{k}=", v) if obj.respond_to?("#{k}=") }
    obj
  end
end

user = MyClass.build(name: "Alice", email: "a@example.com")
user.greet_jp # => "こんにちは, Alice!"

差分、思ったこと

jsで同様の処理を実現することは可能であるものの、以下の制約がある。

  • クラスの定義が完成した後でなければ、クラス本体に動的にアクセスできない
  • コンストラクタは複数回呼び出される可能性があるのため、プロパティの初期化処理を1度だけ実行するのに向いていない
  • 静的メソッドをインスタンス化前に追加できない

これらの制約から記述が煩雑なうえ、静的メソッドの追加についてはRubyと同様の動作を再現することができなかった。

Rubyではclass ~ endの間で普通にコードが書け、メソッド定義周りの組み込み機能も揃っているのでかなり気軽にメタプロができるのだなと思った。
Railsの中ではどうやってるんだろうと思っていたことが、思っていたよりもシンプルに実現できそうで驚いた。

まとめ

Rubyは書き味に優れ、少ないコード量で強力なメタプログラミングが可能な言語だと再認識した。
自由度の高い言語であるため、フレームワークにおける学習コストは高くなると推測されるが、使いこなせるようになれば、高い生産性が期待できる。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

岸川 拓磨
岸川 拓磨
2021年メンバーズ入社。フロントエンドをメインにサービスの運用保守に携わっています。最近はバックエンドにも多少触れるようになってきました。
詳しく見る
ページトップへ戻る