Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rails 5、Module#prepend、および`Alias_method_chain`の終わり

Rails 4.2の発表には、次のRails5に関する興味深いニュースがいくつかありました。おそらくRuby2.2が必要になるでしょう。 これにより、Ruby2のすべての優れた機能を利用する最初のRailsバージョンになります。

投稿では、ガベージコレクションされたシンボルとキーワード引数について言及しました。しかし、私にとって、最も興味深いRuby 2の機能の1つは、Module#prependです。

alias_method_chainの良い点(および悪い点)

Railsを最初に学んだとき、alias_method_chain 完全に私の心を吹き飛ばしました。それは、Rubyがいかに柔軟であるかを実際に示しました。

1行のコードで、メソッドの動作方法を完全に変更できます。 必要なコードを追加するためにライブラリをハックする必要はもうありません。その場で追加するだけです。 alias_method_chain 宝石への最初のパッチにつながり、それが私の最初のプルリクエストにつながり、それが私の最初のオープンソースの貢献につながりました。

ただし、モンキーパッチのように、alias_method_chain 使いすぎて、その問題が明らかになり始めました:

  • 生成されるメソッド名はわかりにくいため、エラーを見つけてデバッグするのが難しくなります。 例:
class Person
  def greeting
    "Hello"
  end
end

module GreetingWithExcitement
  def self.included(base)
    base.class_eval do
      alias_method_chain :greeting, :excitement
    end
  end

  def greeting_with_excitement
    "#{greeting_without_excitement}!!!!!"
  end
end

Person.send(:include, GreetingWithExcitement)

Person#greetingでエラーが発生した場合 、バックトレースは、エラーが実際にPerson#greeting_without_excitementで発生したことを示します。 。しかし、そのメソッドはどこで定義されていますか?どこにも見えません。 どのgreetingをどのように知っていますか メソッドはバグのあるメソッドですか? また、メソッド名は、連鎖するほどさらに混乱します。

  • alias_method_chainを呼び出す場合 同じクラスで同じパラメータを2回使用すると、スタックオーバーフローが発生する可能性があります。 (理由がわかりますか?)requireである限り、これは通常は発生しません。 ステートメントは、使用するパスについて一貫しています。ただし、Railsコンソールにコードを頻繁に貼り付けると非常に煩わしくなります。

  • そして、イェフダカッツのブログ投稿で説明されている残りのこと。 この投稿により、多くのRails開発者がalias_method_chainを放棄し始めるようになりました。 モジュールの継承を支持します。

それで、なぜそれがまだ使用されているのですか?

ほとんどのalias_method_chainを置き換えることができます ■モジュール内のこれらのメソッドをオーバーライドし、それらのモジュールを子クラスに含めることによって。ただし、これは、クラス自体ではなく、スーパークラスをオーバーライドする場合にのみ機能します。つまり:

class ParentClass
  def log
    puts "In parent"
  end
end

class ChildClass < ParentClass
  def log
    puts "In child"
    super
  end

  def log_with_extra_message
    puts "In child, with extra message"
    log_without_extra_message
  end

  alias_method_chain :log, :extra_message
end

ChildClass.new.logを実行した場合 、次のように表示されます:

In child, with extra message
In child
In parent

alias_method_chainの代わりにモジュールを使用しようとした場合 、次のように出力を取得できます:

In child
In child, with extra message
In parent

しかし、できません logを変更せずに元の出力と一致させます ChildClassのメソッド 。 Rubyの継承はそのようには機能しません。そうではありませんでした。

Ruby 2.0で何が変更されましたか?

Ruby 2.0までは、以下にコードを追加する方法はありませんでした。 その上だけのクラス。 ただし、prepend 、モジュールのメソッドを使用してクラスのメソッドをオーバーライドし、superを使用してクラスの実装にアクセスできます。 。 したがって、最後の例を使用すると、次のようにして元の出力を取得できます。

class ParentClass
  def log
    puts "In parent"
  end
end

module ExtraMessageLogging
  def log
    puts "In child, with extra message"
    super
  end
end

class ChildClass < ParentClass
  prepend ExtraMessageLogging
  def log
    puts "In child"
    super
  end
end
In child, with extra message
In child
In parent

完璧です。

prependの場合 まだ頭を包み込むのは難しいので、次のようなことをしていると考えてください:

class NewChildClass < ChildClass
  include ExtraMessageLogging
end
  
ChildClass = NewChildClass

クラス名を混乱させず、既存のオブジェクトに影響を与えることを除いて。

(はい、Rubyでクラス名を再割り当てできます。いいえ、それはおそらく良い考えではありません。)

これはRailsにとって何を意味しますか?

したがって、alias_method_chainを使用するための最後の言い訳 Ruby2.0ではなくなりました。 alias_method_chainの残りの数少ない例の1つを取ることができます Railsで:

rails / activesupport / lib / active_support / core_ext / range / each.rb
require 'active_support/core_ext/module/aliasing'

class Range #:nodoc:

  def each_with_time_with_zone(&block)
    ensure_iteration_allowed
    each_without_time_with_zone(&block)
  end
  alias_method_chain :each, :time_with_zone

  def step_with_time_with_zone(n = 1, &block)
    ensure_iteration_allowed
    step_without_time_with_zone(n, &block)
  end
  alias_method_chain :step, :time_with_zone

  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

代わりに、モジュールと交換してください:

require 'active_support/core_ext/module/aliasing'

module RangeWithTimeWithZoneSupport #:nodoc:

  def each(&block)
    ensure_iteration_allowed
    super(&block)
  end

  def step(n = 1, &block)
    ensure_iteration_allowed
    super(n, &block)
  end
  
  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

Range.send(:prepend, RangeSupportingTimeWithZone)

よりクリーンなRange#each 名前が変更されず、ensure_iteration_allowed モンキーパッチは適用されません。

パッチではなく継承を使用する

Rubyはあなたにたくさんの柔軟性を与えます、そしてそれが私がそれを愛する理由の1つです。しかし、強力なオブジェクトモデルもあります。 したがって、独自のコードを挿入する場合は、ハッキングする前にモジュールと継承に頼ってみてください。 コードの理解とデバッグがはるかに簡単になり、alias_method_chainなどの検出が難しい副作用の一部を回避できます。 。

alias_method_chain Railsで紹介された最もクールな方法の1つでした。しかし、その日は数えられています。私たちはそれを超えました。そして、それがなくなっても見逃すことはありません。


  1. WindowsVistaサポートを終了するために知っておくべきこと

    Windows Vistaを実行するコンピューターを所有または使用していますか?かなり古いオペレーティングシステムになり始めていますが、それでも日常生活の要求に対しては十分に機能します。そのため、仕事や遊びをVistaOSに依存している人もいます。 残念ながら、2017年4月11日に、MicrosoftはWindowsVistaのサポートを完全に停止します。これは、Vistaの「メインストリームサポート」と呼ばれるものの提供を停止してから5年後のことです。これには、ユーザーへの無料のインシデントサポートが含まれます。ただし、主流のサポートが終了した後も、MicrosoftがVistaをサポー

  2. PythonとMatplotlibを使用して行の終わりに注釈を付ける方法は?

    PythonとMatplotlibを使用して行の終わりに注釈を付けるには、次の手順を実行できます- 図のサイズを設定し、サブプロット間およびサブプロットの周囲のパディングを調整します。 変数、行を初期化します 、行数データを取得します。 長方形の表形式のデータでPandasデータフレームを取得します。 cumsum(累積合計)を計算します データフレームの。 plot()を使用してデータフレームをプロットします メソッド。 行を繰り返す および名前 行の終わりに注釈を付けます。 annotate()を使用する 列の名前、xy座標、線の色、サイズなどを使用したメソッド 図に凡例を配