初めてRubyのgemを読んでみる

# はじめに
難易度が比較的低く、初めてにおすすめのsettingslogicを読んでみることにしました。

# settingslogicとは
簡単にいうと異なる値を環境ごとに持たせることができるようになるgemです。
https://github.com/settingslogic/settingslogic

# 動きの確認

まずは、applicationに以下のクラスを定義します。
Settinglogicを継承したSettingsクラスです。
ここでは、sourceとnamespaceの定義をしています。

```ruby
class Settings < Settingslogic
  source "#{Rails.root}/config/application.yml"
  namespace Rails.env
end
```



Settingsクラスでsourceを指定していることにより、以下のファイルから値を読み込んでくれます。

```ruby
# config/application.yml
defaults: &defaults
  cool:
    saweet: nested settings
  neat_setting: 24
  awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>

development:
  <<: *defaults
  neat_setting: 800

test:
  <<: *defaults

production:
  <<: *defaults
```

```ruby
[1] pry(main)> Settings.cool
=> {"saweet"=>"nested settings"}
[2] pry(main)> Settings.awesome_setting
=> "Did you know 5 + 5 = 10?"
```

ではいったいなぜこのようなことができるのでしょうか？

# 内部で何が起きているのか

```ruby
Settings.cool
```

上記ではSettingクラスのcookメソッドを呼び出しています。
Settingsクラス、継承元のSettingslogicクラスには`cool`メソッド は定義されていないので、
missing_methodが呼び出されます。
渡された引数が`instance.send`に渡されています。
(send:　nameをargsを引数にとり呼び出す = instance.cool(args))

```ruby
def instance
      return @instance if @instance
      @instance = new
      create_accessors!
      @instance
    end

    def method_missing(name, *args, &block)
      instance.send(name, *args, &block)
    end

    # It would be great to DRY this up somehow, someday, but it's difficult because
    # of the singleton pattern.  Basically this proxies Setting.foo to Setting.instance.foo
    def create_accessors!
      instance.each do |key, val|
        create_accessor_for(key)
      end
    end
```

@instanceは最初の段階ではnilのため、
@instance = newでinitializeが呼び出されます。

以下では、Setting.rbに定義したsourceから、中身の情報を取り出しYAMLをハッシュ化しています。

```ruby
def initialize(hash_or_file = self.class.source, section = nil)
    #puts "new! #{hash_or_file}"
    case hash_or_file
    when nil
      raise Errno::ENOENT, "No file specified as Settingslogic source"
    when Hash
      self.replace hash_or_file
    else
      file_contents = open(hash_or_file).read
      hash = file_contents.empty? ? {} : YAML.load(ERB.new(file_contents).result).to_hash
      if self.class.namespace
        hash = hash[self.class.namespace] or return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}")
      end
      self.replace hash
    end
    @section = section || self.class.source  # so end of error says "in application.yml"
    create_accessors!
  end
```

キーが存在すれば、`fetch(key)`で値を取り出し、`create_accessor_for`メソッド に渡しています。

```ruby
def create_accessor_for(key, val = nil)
    return unless key.to_s =~ /^\w+$/  # could have "some-setting:" which blows up eval
    instance_variable_set("@#{key}", val)
    self.class.class_eval <<-EndEval
      def #{key}
        return @#{key} if @#{key}
        return missing_key("Missing setting '#{key}' in #{@section}") unless has_key? '#{key}'
        value = fetch('#{key}')
        @#{key} = if value.is_a?(Hash)
          self.class.new(value, "'#{key}' section in #{@section}")
        elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash}
          value.map{|v| self.class.new(v)}
        else
          value
        end
      end
    EndEval
  end
```

これがぱっと見意味わかりませんでした。。。
まず、`instance_variable_set`メソッドで`@key`(今回は@cool)に値をセットします。
その後は、キーがメソッド名になったメソッドを定義しています。


# 大まかな流れ
- `method_missing`からinitialize`メソッド呼ばれる
- @instanceがYAMLファイルをハッシュ化した物になる
- `create_accessors!`呼ばれ、keyが`create_accessor_for`に渡される
- `create_accessor_for`で動的メソッドを作成する
- 動的メソッドは、ハッシュから値を取り出し、インスタンス変数に渡す
- `create_accessors`呼ばれる
- `send`で動的にメソッドが定義されて呼び出される
- 値が返る




# 知らなかった知識
- self.class.sourceの形がわからなかった
    - console上で実行してもエラーとなるため、何が実行されているかわからなかった。`self.class`でクラスメソッドを呼び出しているので、今回の場合は実行されているのは、`Settings.source`だった。
- evalとは
    - メタプログラミングの一種で文字列をrubyのコードとして扱うことができるようになる。
- is_a(Hash)
    - レシーバが引数のクラス、もしくはそのサブクラスから作成されたオブジェクトであればtrue
- fetch
    - 引数にハッシュのキーを指定することにより、そのキーとセットになっているバリューを取り出す。
    存在しない場合は例外を発生させる。
- replace
    - レシーバの値を引数の値に置き換えるメソッド
- instance_variable_set
    - `p obj.instance_variable_set("@foo", 1)  #=> 1`

# よく出てきた英単語
- accessors
    - アクセサとは、オブジェクト指向プログラミングで、オブジェクト内部のメンバ変数（属性、プロパティ）に外部からアクセスするために用意されたメソッド。 メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。
    - create_accessorsはアクセサを作るメソッドということですね

# まとめ
メタプロ難しすぎたんで、勉強しなおしてもう一回出直してこようと思います。
    

# 参考
https://blog.freedom-man.com/try-rubygem-codereading