普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました
読めるようにするまで
以前書いた記事で読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com
読んだ箇所
instance_values
を今日は読んでみようと思います
どんな使い方だっけ?
読んでみる前にまずは使い方を調べてみます
Rails Guidesの日本語ドキュメントを見てみると
instance_valuesメソッドはハッシュを返します。インスタンス変数名から"@"を除いたものがハッシュのキーに、インスタンス変数の値がハッシュの値にマップされます。キーは文字列です。
class C def initialize(x, y) @x, @y = x, y end end C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
インスタンス変数を簡単に取り出して使うメソッドです
ソースコードを読んでみる
1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def instance_values
を探してみます
2. 該当箇所が1個あったので、それをみてみます
1. activesupport > lib > active_support > core_ext > object > instance_values.rb
# frozen_string_literal: true class Object # Returns a hash with string keys that maps instance variable names without "@" to their # corresponding values. # # class C # def initialize(x, y) # @x, @y = x, y # end # end # # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} def instance_values Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] end
まず、 Hash[]
を調べてみます
Hashクラスのクラスメソッドは、新しいハッシュ(Hashクラスのインスタンス)を返します。の中に[キー1, 値1, キー2, 値2, ...]のようにオブジェクトを並べると、それが新しいハッシュのキーと値になります。
[]内のオブジェクトの数が奇数のときは、例外ArgumentErrorが発生します。
movie = Hash[:title, "Alien", :director, "Ridley Scott", :year, 1979] puts movie[:title] puts movie[:year]Alien
1979
ハッシュクラスのインスタンスを生成するクラスメソッドになります
次に、どんな値を元にハッシュ生成しているのか見るため instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }
を順を追って見てみたいと思います
まず、instance_variables.map {}
です
instance_variables
を調べてみると
instance_variablesメソッドは、レシーバのオブジェクトが持っているインスタンス変数の名前を配列に入れて返します。
Ruby 1.9 Ruby 1.8では配列中の変数名は文字列ですが、Ruby 1.9ではシンボルになります。
class Book def initialize(title, price) @title = title; @price = price end end book = Book.new("Programming Ruby", 2000) p book.instance_variables["@title", "@price"] (Ruby 1.8の場合)
[:@title, :@price] (Ruby 1.9の場合)
インスタンス変数の名前を配列で取得しています。そしてその配列に対して map
を実行しています
mapは
mapメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。collectメソッドの別名です。
numbers = ["68", "65", "6C", "6C", "6F"] p numbers.map {|item| item.to_i(16) }[104, 101, 108, 108, 111]
ブロックの戻り値を集めて配列にするメソッドです
次にmapに渡しているブロック部分 { |name| [name[1..-1], instance_variable_get(name)] }
を見てみます
name[1..-1]
はインスタンス変数名から部分文字列を取得している処理です
レシーバがStringクラスだった場合は、次のとおりです
文字列の中から部分文字列を取り出すメソッドです。s[2]、s[3,5]、s[2..7]、s[/[0-9]/] のように、いろいろな形で利用できます。配列要素の取り出しのように記述しますが、実際にはメソッド呼び出しです。[]の中はメソッドの引数です。
省略
引数に範囲を指定すると、その範囲に対応する部分文字列を返します。範囲外の位置を指定すると、nilが返ります。
s = "hello, world" puts s[7..10] # 7文字目から10文字目まで puts s[7...10] # 7文字目から10文字目まで、10文字目は含まないworl
wor
開始位置と終了位置がマイナスの場合は、文字列の末尾から数えます(-1が末尾から1番目、-2が末尾から2番目、...)。
s = "hello, world" puts s[-5..-1] # 末尾から5文字目..末尾から1文字目までworld
name[1..-1]
は2文字目から末尾1文字目までを取得しています
たとえば、 "@title"
であれば title
が取得できます
レシーバがシンボルだった場合にも同様です
rangeで指定したインデックスの範囲に含まれる部分文字列を返します。
(self.to_s[range] と同じです。)
[PARAM] range:
取得したい文字列の範囲を示す Range オブジェクトを指定します。
:foo[0..1] # => "fo"[SEE_ALSO] String#[], String#slice
次に、instance_variable_get(name)
を見てみます
instance_variable_getメソッドは、レシーバが持っているインスタンス変数の値を返します。引数nameにはインスタンス変数の名前を:@titleや"@title"のようにシンボルか文字列で渡します。
定義されていない変数名を渡すとnilが返ります。:titleのようにインスタンス変数と見なされない名前を渡すと例外NameErrorが発生します。
class Book def initialize(title) @title = title end end book = Book.new("Programming Ruby") p book.instance_variable_get(:@title) p book.instance_variable_get(:@price)"Programming Ruby"
nil
インスタンス変数名を引数に渡すとその値が取れるという動きです
ブロックの中で行っている処理は、 配列の先頭にインスタンス変数名、次にインスタンス変数の値をセットしている処理です
たとえば、 { |name| [name[1..-1], instance_variable_get(name)] }
の name
が"@title"
の場合であれば、
["title", "@titleに入ってた値"]
ということになります
ここまでをまとめて考えてみると、Hash[]
の引数に入る値は、
[ ["インスタンス変数名1", "インスタンス変数の値1"], ["インスタンス変数名2", "インスタンス変数の値2"], ["インスタンス変数名3", "インスタンス変数の値3"] ]
になります
Hash[]
を調べてみると
新しいハッシュを生成します。 引数は必ず偶数個指定しなければなりません。奇数番目がキー、偶数番目が値になります。
このメソッドでは生成するハッシュにデフォルト値を指定することはできません。 Hash.newを使うか、Hash#default=で後から指定してください。
[PARAM] key_and_value:
生成するハッシュのキーと値の組です。必ず偶数個(0を含む)指定しなければいけません。
[EXCEPTION] ArgumentError:
奇数個の引数を与えたときに発生します。
以下は配列からハッシュを生成する方法の例です。
省略
(2) キーと値のペアの配列からハッシュへ
alist = [[1,"a"], [2,"b"], [3,["c"]]] p Hash[*alist.flatten(1)] # => {1=>"a", 2=>"b", 3=>["c"]}
さきほどの配列をHash[]
すると
{ "インスタンス変数名1" => "インスタンス変数の値1", "インスタンス変数名2" => "インスタンス変数の値2", "インスタンス変数名3" => "インスタンス変数の値3" }
となります
読んでみて
インスタンス変数も利用がしやすいようにこんなメソッドもあるんだというのが学びでした