You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ruby中捕获类实例赋值给常量时的常量名称?

实现实例绑定常量名称的方案

Great question! This is a common need when building enum-like structures (or any class where instances are assigned to constants) in Ruby, and there are a couple of solid approaches to make your instances aware of the constant name they're bound to.

核心思路

We need a way to capture the name of the constant when an instance is assigned to it, then store that name on the instance itself so we can reference it in error messages or other methods.

方法1:使用TracePoint监听常量赋值(推荐,鲁棒性高)

Ruby's TracePoint API lets us hook into runtime events, including when a constant is set. We can use this to detect when our instance is assigned to a constant, then save that constant's name to the instance.

Here's how to implement this for your Enum class:

class Enum
  attr_reader :name, :options

  def initialize(&block)
    @options = {}
    instance_eval(&block) if block_given?

    # Set up a TracePoint to watch for constant assignments
    trace = TracePoint.new(:cset) do |tp|
      # Check if the value being assigned to the constant is this instance
      if tp.value == self
        @name = tp.name.to_s
        trace.disable # Stop listening once we've found our constant
      end
    end
    trace.enable
  end

  # Helper to define options
  def option(symbol)
    Option.new(self, symbol)
  end

  # Inner class to handle option configuration
  class Option
    def initialize(enum, symbol)
      @enum = enum
      @symbol = symbol
    end

    def set(raw_value)
      @enum.options[@symbol] = raw_value
    end
  end

  # Fetch an option with error handling
  def [](symbol)
    options[symbol] || raise("Error. enum #{name} has no option #{symbol}")
  end
end

方法2:解析调用栈(简单但依赖代码格式)

If you prefer a lighter-weight approach without runtime hooks, you can parse the calling stack to extract the constant name from the assignment line. This works for straightforward assignments like Constant = Enum.new { ... }, but may fail if the assignment is more complex.

class Enum
  attr_reader :name, :options

  def initialize(&block)
    @options = {}
    instance_eval(&block) if block_given?

    # Find the line where the enum is assigned to a constant
    caller_line = caller.find { |line| line.include?('=') }
    if caller_line
      # Extract the constant name using a regex
      match = caller_line.match(/(\w+)\s*=\s*Enum\.new/)
      @name = match[1] if match
    end
  end

  # Rest of the class (option, Option inner class, [] method) is the same as above
end

测试你的Enum实现

Using either method, your Pizza class will work exactly as you expect:

class Pizza
  attr_reader :topping

  Toppings = Enum.new do
    option(:pepperoni).set("Pepperoni")
    option(:sausage).set("Sausage")
    option(:mushrooms).set("Mushrooms")
  end

  def set_topping(symbol)
    @topping = Toppings[symbol]
  end
end

# Normal case works
pizza = Pizza.new
pizza.set_topping(:pepperoni)
puts pizza.topping # Output: "Pepperoni"

# Error case shows the correct constant name
pizza.set_topping(:spinach)
# Raises: RuntimeError (Error. enum Toppings has no option spinach)

补充:降级方案

If the automatic detection fails (e.g., for complex assignments), you can add an explicit parameter to initialize to set the name manually:

def initialize(name: nil, &block)
  @name = name
  # ... rest of initialize ...
end

# Usage:
Toppings = Enum.new(name: "Toppings") do
  # ... options ...
end

内容的提问来源于stack exchange,提问作者Josh Hadik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:24:40