method_missing can handle a small set of dynamic method names when normal lookup fails.

Method Missing Intro

method_missing_intro.rb
class Record
  def initialize(data)
    @data = data
  end

  def method_missing(method_name, *args)
    key = method_name.to_s
    return @data[key] if args.empty? && @data.key?(key)

    super
  end

  def respond_to_missing?(method_name, include_private = false)
    @data.key?(method_name.to_s) || super
  end
end

field_name = 
record = Record.new("name" => "Ada", "role" => "mentor", "level" => "beginner")

can_read = record.respond_to?(field_name)
value = can_read ? record.public_send(field_name) : "missing"

puts "field=#{field_name}"
puts "can_read=#{can_read}"
puts "value=#{value}"
class Record
  def initialize(data)
    @data = data
  end

  def method_missing(method_name, *args)
    key = method_name.to_s
    return @data[key] if args.empty? && @data.key?(key)

    super
  end

  def respond_to_missing?(method_name, include_private = false)
    @data.key?(method_name.to_s) || super
  end
end

field_name = 
record = Record.new("name" => "Ada", "role" => "mentor", "level" => "beginner")

can_read = record.respond_to?(field_name)
value = can_read ? record.public_send(field_name) : "missing"

puts "field=#{field_name}"
puts "can_read=#{can_read}"
puts "value=#{value}"
class Record
  def initialize(data)
    @data = data
  end

  def method_missing(method_name, *args)
    key = method_name.to_s
    return @data[key] if args.empty? && @data.key?(key)

    super
  end

  def respond_to_missing?(method_name, include_private = false)
    @data.key?(method_name.to_s) || super
  end
end

field_name = 
record = Record.new("name" => "Ada", "role" => "mentor", "level" => "beginner")

can_read = record.respond_to?(field_name)
value = can_read ? record.public_send(field_name) : "missing"

puts "field=#{field_name}"
puts "can_read=#{can_read}"
puts "value=#{value}"
method missing When `method_missing` is used, `respond_to_missing?` should describe the same dynamic behavior.