2

Ruby(Rails上)のオブジェクトが与えられた場合、次のように、そのオブジェクトのインスタンス変数名とその値を表示するようにメソッドを作成するにはどうすればよいですか?

@x: 1
@y: 2
@link_to_point: #<Point:0x10031b298 @y=20, @x=38>

更新: 大きなオブジェクトを除いて、Railsのように、またはActionViewオブジェクトのinspect場合のように、200行の出力から変数を分解することは困難です)request.inspectself.inspect

<br>また、各インスタンス変数の値の最後まで印刷して、Webページにうまく印刷できるようにしたいと思います。

現在の難しさは、すべてのインスタンス変数にアクセサーがあるわけではないため、obj.send(var_name)で呼び出すことができないことです。

(var_nameでは「@」が削除されているため、「@ x」は「x」になります)

更新:再帰を使用すると、より高度なバージョンを印刷できると思います。

#<Point:0x10031b462>
    @x: 1
    @y: 2
    @link_to_point: #<Point:0x10031b298>
        @x=38
        @y=20
4

4 に答える 4

6

私はおそらく次のように書くでしょう:

class Object
  def all_variables(root=true)
    vars = {}
    self.instance_variables.each do |var|
      ivar = self.instance_variable_get(var)
      vars[var] = [ivar, ivar.all_variables(false)]
    end
    root ? [self, vars] : vars
  end
end

def string_variables(vars, lb="\n", indent="\t", current_indent="")
  out             = "#{vars[0].inspect}#{lb}"
  current_indent += indent
  out            += vars[1].map do |var, ivar|
                      ivstr = string_variables(ivar, lb, indent, current_indent)
                      "#{current_indent}#{var}: #{ivstr}"
                    end.join
  return out
end

def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
  string_variables(obj.all_variables, lb, indent, current_indent)
end

このObject#all_variablesメソッドは、(0) 指定されたオブジェクトと (1) インスタンス変数名を (0) インスタンス変数と (1) ハッシュ マッピングを含む配列にマッピングするハッシュを含む配列を生成します。したがって、それはあなたに素晴らしい再帰構造を与えます。関数はそのstring_variablesハッシュを適切に出力します。inspect_variables単なる便利なラッパーです。したがって、print inspect_variables(foo)改行で区切られたオプションが提供さprint inspect_variables(foo, "<br />\n")れ、HTML 改行付きのバージョンが提供されます。インデントを指定したい場合は、それもできます:print inspect_variables(foo, "\n", "|---")タブベースのインデントの代わりに (役に立たない) 偽ツリー形式を生成します。

コールバックを提供する関数を作成する賢明な方法があるはずeach_variableです (中間ストレージを割り当てる必要はありません)。何か思いついたら、この回答を編集して含めます。 編集1:私は何かを考えました。

別の書き方を次に示します。

class Object
  def each_variable(name=nil, depth=0, parent=nil, &block)
    yield name, self, depth, parent
    self.instance_variables.each do |var|
      self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
    end
  end
end

def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
  out = ''
  obj.each_variable do |name, var, depth, _parent|
    out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
  end
  return out
end

このObject#each_variableメソッドは、ユーザーが指定するようには設計されていない、いくつかのオプションの引数を取ります。代わりに、状態を維持するために再帰によって使用されます。指定されたブロックが渡されます (a) インスタンス変数の名前、またはnil変数が再帰のルートである場合。(b) 変数。(c) 再帰が下降した深さ。(d) 現在の変数の親、またはnilその変数が再帰のルートである場合。再帰は深さ優先です。inspect_variables関数はこれを使用して文字列を作成します。引数はobj反復するオブジェクトです。nl行区切りです。indent各レベルで適用されるインデントです。名前と値をsep区切ります。

編集2:これはあなたの質問への答えに実際には何も追加しませんが、再実装で何も失っていないことを証明するために、 のall_variables観点から を再実装しeach_variablesます。

def all_variables(obj)
  cur_depth = 0
  root      = [obj, {}]

  tree      = root
  parents   = []
  prev      = root

  obj.each_variable do |name, var, depth, _parent|
    next unless name

    case depth <=> cur_depth
      when -1 # We've gone back up
        tree = parents.pop(cur_depth - depth)[0]
      when +1 # We've gone down
        parents << tree
        tree = prev
      else # We're at the same level
        # Do nothing
    end

    cur_depth            = depth
    prev = tree[1][name] = [var, {}]
  end

  return root
end

もっと短くすべきだと思いますが、それは不可能かもしれません。現在は再帰がないため、スタックを明示的に維持する必要があります ( でparents)。しかし、それは可能であるため、このeach_variable方法も同様に機能します (そして、少し優れていると思います)。

于 2010-06-17T00:14:07.887 に答える
5

なるほど…アンタルがここで上級版を出しているに違いない…

短いバージョンはおそらく次のとおりです。

def p_each(obj)
  obj.instance_variables.each do |v|
    puts "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  nil
end

または文字列として返すには:

def sp_each(obj)
  s = ""
  obj.instance_variables.each do |v|
    s += "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  s
end

またはそれより短い:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
于 2010-06-17T01:56:06.940 に答える
1

これは、私が別の質問のために書いた単純な JSON エミッターの簡単な適応です。

class Object
  def inspect!(indent=0)
    return inspect if instance_variables.empty?
    "#<#{self.class}:0x#{object_id.to_s(16)}\n#{'  ' * indent+=1}#{
      instance_variables.map {|var|
        "#{var}: #{instance_variable_get(var).inspect!(indent)}"
      }.join("\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}>"
  end
end

class Array
  def inspect!(indent=0)
    return '[]' if empty?
    "[\n#{'  ' * indent+=1}#{
      map {|el| el.inspect!(indent) }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}]"
  end
end

class Hash
  def inspect!(indent=0)
    return '{}' if empty?
    "{\n#{'  ' * indent+=1}#{
      map {|k, v|
        "#{k.inspect!(indent)} => #{v.inspect!(indent)}"
      }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}}"
  end
end

それがすべての魔法です。これで、完全な検査が実際には意味をなさないいくつかのタイプ ( nilfalsetrue、数値など)に対していくつかの単純なデフォルトのみが必要になります。

module InspectBang
  def inspect!(indent=0)
    inspect
  end
end

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end
于 2010-06-17T11:50:23.017 に答える
0

このような?

# Get the instance variables of an object
d = Date.new
d.instance_variables.each{|i| puts i + "<br />"}

instance_variables に関する Ruby ドキュメント

その概念は一般に「内省」(自分自身を見つめること)と呼ばれています。

于 2010-06-16T23:15:21.647 に答える