Rubyモジュールの構成

多くのgemが設定に提供しているconfigureメソッドに精通していると思います。 たとえば、搬送波の構成:



CarrierWave.configure do |config| config.storage = :file config.enable_processing = false end
      
      





これをモジュールに実装する方法は?





早くて汚い



落下テストから始めましょう。



 # configure.rb require 'minitest/autorun' class ConfigurationTest < MiniTest::Test def test_configure_block MyModule.configure do |config| config.name = "TestName" config.per_page = 25 end assert_equal "TestName", MyModule.config.name assert_equal 25, MyModule.config.per_page assert_equal "TestName", MyModule.config[:name] assert_equal 25, MyModule.config[:per_page] end end
      
      





 ➜ Projects ruby configure.rb Run options: --seed 25758 # Running: E Finished in 0.001166s, 857.6329 runs/s, 0.0000 assertions/s. 1) Error: ConfigurationTest#test_configure_block: NameError: uninitialized constant ConfigurationTest::MyModule configure.rb:5:in `test_configure_block' 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
      
      





落下テストが完了したので、機能の実装を開始します。 まず、configureメソッドを含むモジュールを宣言します。



 module MyModule def self.configure end end
      
      





設定を保存する場所が必要です。 モジュール変数はこれに適していると思います。



 module MyModule def self.configure @config ||= {} end def self.config @config end end
      
      





ここに問題があります。 構成をハッシュに保存することはできません。 とりあえず、ハッシュをOpenStructに置き換えます。これは、長期的に取得する機能に一致します。 その後、メソッド内でブロックを呼び出して、引数としてストレージを渡すことができます。



 require 'minitest/autorun' require 'ostruct' module MyModule def self.configure @config ||= OpenStruct.new yield(@config) if block_given? @config end def self.config @config || configure end end
      
      





必要な機能の準備ができました。 テストに合格しました。



 ➜ Projects ruby configure.rb Run options: --seed 8967 # Running: . Finished in 0.001607s, 622.2775 runs/s, 2489.1101 assertions/s. 1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
      
      







リファクタリング



このソリューションをリファクタリングする時が来ました。 2つの問題がすぐに明らかになります。





テストを追加して、存在しない構成メソッドを呼び出したときに例外が発生することを確認します。



 def test_set_not_exists_attribute assert_raises NoMethodError do MyModule.configure do |config| config.unknown_attribute = "TestName" end end end def test_get_not_exists_attribute assert_raises NoMethodError do MyModule.config.unknown_attribute end end
      
      





これを修正する方法は2つあります。 1つ目は、使用可能な構成方法のホワイトリストでStructを使用することです。



 module MyModule CONFIG_ATTRIBUTES = %i(name per_page) def self.configure @config ||= Struct.new(*CONFIG_ATTRIBUTES).new yield(@config) if block_given? @config end def self.config @config || configure end end
      
      





すべてが素晴らしく見えます。 テストに合格すると、コードはシンプルで読みやすくなります。 しかし、私は1つの重要な詳細を忘れていました 。 デフォルトの構成値。 彼らのために、別のテストを追加する必要があります。



 def test_default_values MyModule.configure do |config| config.name = "TestName" end assert_equal 10, MyModule.config.per_page end
      
      





異なるテストで構成値が上書きされないようにするには、各テストを開始する前に以前の構成のリセットを追加する必要があります 。 テストニーズにのみ必要であり、パブリックAPIの一部にする必要がないため、テストクラスにリセットメソッドを直接追加します。



 module ::MyModule def self.reset @config = nil end end def setup MyModule.reset end
      
      





デフォルト値を使用して問題の解決に戻りましょう。 最も簡単なソリューションは次のようになります。



 self.config ||= begin config = Struct.new(*CONFIG_ATTRIBUTES).new config.per_page = 10 config end
      
      





うーん、 コードの匂いがし始めます。 デフォルト値はさらに複雑になる可能性があります。 このようなコードは保守が困難です。 私たちはもっと良くできると思います。 Structをclassに置き換えましょう 。 このクラスでは、初期化子で直接デフォルト値を設定できます。 このようなコードは読みやすく、拡張しやすいでしょう。



 module MyModule class Configuration attr_accessor :name, :per_page def initialize @per_page = 10 end def [](value) self.public_send(value) end end def self.configure @config ||= Configuration.new yield(@config) if block_given? @config end def self.config @config || configure end end
      
      





私はこの解決策が好きです。 まだ非常にシンプル読みやすいです。 また、非常に柔軟です。 複雑なデフォルトを設定し、必要に応じて別のメソッドに入れることができます。 また、設定値を取得する方法が2つあります。メソッドを使用する方法と添え字を使用する方法です。



それが今日私が共有したかったすべてです。 ソースはここから入手できます: goo.gl/feCwCC



UPD:



northbearは、クラスの代わりに初期化子でStructを使用する代替案を提案しました。



 module Sample DefaultConfig = Struct.new(:a, :b) do def initialize self.a = 10 self.b = 'test' end end def self.configure @config = DefaultConfig.new yield(@config) if block_given? @config end def self.config @config || configure end end
      
      





この場合、public_sendを使用して添え字を定義する必要はありません。これにより、コードがさらに簡単になります。 ありがとう



All Articles