LoginSignup
0

More than 1 year has passed since last update.

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

Posted at

はじめに

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

settingslogicとは

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

動きの確認

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

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

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

# 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
[1] pry(main)> Settings.cool
=> {"saweet"=>"nested settings"}
[2] pry(main)> Settings.awesome_setting
=> "Did you know 5 + 5 = 10?"

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

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

Settings.cool

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

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をハッシュ化しています。

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メソッド に渡しています。

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はアクセサを作るメソッドということですね

まとめ

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

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0