GitLab ユーティリティ

GitLab では、開発を容易にするためのユーティリティを多数開発しています:

MergeHash

merge_hash.rb をご参照ください:

  • ハッシュの配列をディープマージします:

     Gitlab::Utils::MergeHash.merge(
       [{ hello: ["world"] },
        { hello: "Everyone" },
        { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
         "Goodbye", "Hallo"]
     )
    

    を返します:

     [
       {
         hello:
           [
             "world",
             "Everyone",
             { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
           ]
       },
       "Goodbye"
     ]
    
  • ハッシュからすべてのキーと値を配列に取り出します:

     Gitlab::Utils::MergeHash.crush(
       { hello: "world", this: { crushes: ["an entire", "hash"] } }
     )
    

    を返します:

     [:hello, "world", :this, :crushes, "an entire", "hash"]
    

Override

override.rb をご参照ください:

  • このユーティリティは、あるメソッドが別のメソッドをオーバーライドするかどうかをチェックするのに役立ちます。Java の@Override アノテーションや Scala のoverride キーワードと同じ概念です。ただし、実行時のオーバーヘッドを避けるため、ENV['STATIC_VERIFICATION'] が設定されている場合にのみこのチェックを実行します。これはチェックに便利です:

    • オーバーライド・メソッドにタイプミスがある場合。
    • オーバーライドされたメソッドの名前を変更したために、元のオーバーライド・メソッドが無意味になった場合。

      簡単な例を示します:

       class Base
         def execute
         end
       end
            
       class Derived < Base
         extend ::Gitlab::Utils::Override
            
         override :execute # Override check happens here
         def execute
         end
       end
      

      これはモジュールでも使えます:

       module Extension
         extend ::Gitlab::Utils::Override
            
         override :execute # Modules do not check this immediately
         def execute
         end
       end
            
       class Derived < Base
         prepend Extension # Override check happens here, not in the module
       end
      

      チェックが行われるのは

      • オーバーライド・メソッドがクラスで定義されている場合:
      • オーバーライド・メソッドはモジュールで定義され、クラスまたはモジュールの前に付加されます。

      実際にメソッドをオーバーライドできるのは、クラスまたはプリペンドされたモジュールだけだからです。モジュールを別のモジュールにインクルードしたり、拡張したりしても、オーバーライドすることはできません。

ActiveSupport::Concernprepend 、およびclass_methods

ActiveSupport::Concern クラスのメソッドをインクルードして ActiveSupport::Concern使う場合、通常のRubyモジュールのようには動作ActiveSupport::Concern しないので、期待した結果は得 ActiveSupport::Concernられません。

すでにprepend を有効にするためのActiveSupport::Concern 用のパッチとしてPrependable が用意されているので、overrideclass_methods とどのように相互作用するかが問題になります。回避策として、extend ClassMethodsPrependable モジュールの定義に追加します。

これにより、override を使用して、上記のコンテキストで使用されているclass_methods を検証することができます。この回避策は、検証を実行するときにのみ適用され、アプリケーション自体を実行するときには適用されません。

以下は、この回避策の効果を示すコードブロックの例です:

module Base
  extend ActiveSupport::Concern

  class_methods do
    def f
    end
  end
end

module Derived
  include Base
end

# Without the workaround
Base.f    # => NoMethodError
Derived.f # => nil

# With the workaround
Base.f    # => nil
Derived.f # => nil

StrongMemoize

strong_memoize.rb をご参照ください:

  • nil またはfalse の場合でも、値をメモしてください。

    私たちはよく@value ||= compute とします。しかし、compute が最終的にnil を与える可能性があり、再度計算したくない場合、これはうまく機能しません。代わりに、defined? を使って値が設定されているかどうかをチェックすることができます。このようなパターンを書くのは面倒なので、StrongMemoize

    このようなパターンを書く代わりに

     class Find
       def result
         return @result if defined?(@result)
       
         @result = search
       end
     end
    

    のように書くことができます:

     class Find
       include Gitlab::Utils::StrongMemoize
       
       def result
         search
       end
       strong_memoize_attr :result
       
       def enabled?
         Feature.enabled?(:some_feature)
       end
       strong_memoize_attr :enabled?
     end
    

    パラメータを持つメソッドでのstrong_memoize_attr の使用はサポートされていません。また、override と組み合わせても動作せず、誤った結果を残す可能性があります。

    代わりにstrong_memoize_with を使用してください。

     # bad
     def expensive_method(arg)
       # ...
     end
     strong_memoize_attr :expensive_method
       
     # good
     def expensive_method(arg)
       strong_memoize_with(:expensive_method, arg)
         # ...
       end
     end
    

    引数を取るメソッドをメモするためのstrong_memoize_with もあります。これは、引数として取りうる値の数が少ないメソッドや、ループ内で一貫して引数を繰り返すメソッドに使用します。

     class Find
       include Gitlab::Utils::StrongMemoize
       
       def result(basic: true)
         strong_memoize_with(:result, basic) do
           search(basic)
         end
       end
     end
    
  • 明確なメモ化

     class Find
       include Gitlab::Utils::StrongMemoize
     end
       
     Find.new.clear_memoization(:result)
    

RequestCache

request_cache.rb をご参照ください。

このモジュールは、RequestStore に値をキャッシュする簡単な方法を提供します。キャッシュキーは、クラス名、メソッド名、オプションでカスタマイズされたインスタンスレベルの値、オプションでカスタマイズされたメソッドレベルの値、およびオプションのメソッド引数に基づいています。

インスタンスレベルでカスタマイズされた値のみを使用する簡単な例を示します:

class UserAccess
  extend Gitlab::Cache::RequestCache

  request_cache_key do
    [user&.id, project&.id]
  end

  request_cache def can_push_to_branch?(ref)
    # ...
  end
end

このようにすると、can_push_to_branch? の結果はキャッシュキーに基づいてRequestStore.store にキャッシュされます。RequestStore が現在アクティビティでない場合は、ハッシュに保存され、インスタンス変数に保存されるので、キャッシュロジックは同じになります。

メソッドごとに異なる戦略を設定することもできます:

class Commit
  extend Gitlab::Cache::RequestCache

  def author
    User.find_by_any_email(author_email)
  end
  request_cache(:author) { author_email }
end

ReactiveCaching

ReactiveCaching のドキュメントをお読みください。