クラスレベルのインスタンス変数の魔法
以前のRubyMagicでは、.new
を上書きすることで、モジュールをクラスに確実に挿入する方法を見つけました。 メソッド、追加の動作でメソッドをラップできるようにします。
今回は、その動作を独自のモジュールに抽出して再利用できるようにすることで、さらに一歩進んでいます。 Wrappable
を作成します クラス拡張を処理するモジュールであり、その過程でクラスレベルのインスタンス変数についてすべて学習します。さっそく飛び込みましょう!
Wrappable
の紹介 モジュール
初期化時にオブジェクトをモジュールでラップするには、使用するラッピングモデルをクラスに通知する必要があります。簡単なWrappable
を作成することから始めましょう wrap
を提供するモジュール 指定されたモジュールをクラス属性として定義された配列にプッシュするメソッド。さらに、new
を再定義します 前の投稿で説明した方法。
module Wrappable
@@wrappers = []
def wrap(mod)
@@wrappers << mod
end
def new(*arguments, &block)
instance = allocate
@@wrappers.each { |mod| instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
新しい動作をクラスに追加するには、extend
を使用します 。 extend
メソッドは、指定されたモジュールをクラスに追加します。その後、メソッドはクラスメソッドになります。このクラスのインスタンスをラップするモジュールを追加するために、wrap
を呼び出すことができます。 メソッド。
module Logging
def make_noise
puts "Started making noise"
super
puts "Finished making noise"
end
end
class Bird
extend Wrappable
wrap Logging
def make_noise
puts "Chirp, chirp!"
end
end
Bird
の新しいインスタンスを作成して、これを試してみましょう。 make_noise
を呼び出します メソッド。
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
すごい!期待どおりに動作します。ただし、Wrappable
を使用して2番目のクラスを拡張すると、状況が少し奇妙になり始めます。 モジュール。
module Powered
def make_noise
puts "Powering up"
super
puts "Shutting down"
end
end
class Machine
extend Wrappable
wrap Powered
def make_noise
puts "Buzzzzzz"
end
end
machine = Machine.new
machine.make_noise
# Powering up
# Started making noise
# Buzzzzzz
# Finished making noise
# Shutting down
bird = Bird.new
bird.make_noise
# Powering up
# Started making noise
# Chirp, chirp!
# Finished making noise
# Shutting down
Machine
Logging
でラップされていません モジュール、それはまだログ情報を出力します。さらに悪いことに、鳥でさえパワーアップとパワーダウンを行っています。それは正しくありませんね
この問題の根本は、モジュールの保存方法にあります。クラス変数@@wrappables
Wrappable
で定義されています モジュールであり、wrap
するクラスに関係なく、新しいモジュールを追加するたびに使用されます で使用されます。
これは、Wrappable
で定義されたクラス変数を見るとより明白になります モジュールとBird
およびMachine
クラス。 Wrappable
クラスメソッドが定義されていますが、2つのクラスは定義されていません。
Wrappable.class_variables # => [:@@wrappers]
Bird.class_variables # => []
Machine.class_variables # => []
これを修正するには、インスタンス変数を使用するように実装を変更する必要があります。ただし、これらはBird
のインスタンスの変数ではありません またはMachine
、ただし、クラス自体のインスタンス変数。
Rubyでは、クラスは単なるオブジェクトです
これは確かに最初は少し気が遠くなるようなものですが、それでも理解するのに非常に重要な概念です。クラスはClass
のインスタンスです class Bird; end
Bird = Class.new
と書くのと同じです 。物事をさらに混乱させるためにClass
Module
から継承します Object
から継承します 。その結果、クラスとモジュールは他のオブジェクトと同じメソッドを持ちます。クラスで使用するほとんどのメソッド(attr_accessor
など) マクロ)は実際にはModule
のインスタンスメソッドです 。
クラスでのインスタンス変数の使用
Wrappable
を変更しましょう インスタンス変数を使用するための実装。物事を少しきれいに保つために、wrappers
を導入します 配列を設定するか、インスタンス変数がすでに存在する場合に既存の配列を返すメソッド。 wrap
も変更します およびnew
その新しい方法を利用するように方法。
module Wrappable
def wrap(mod)
wrappers << mod
end
def wrappers
@wrappers ||= []
end
def new(*arguments, &block)
instance = allocate
wrappers.each { |mod| instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
モジュールと2つのクラスのインスタンス変数を確認すると、両方のBird
がわかります。 およびMachine
現在、独自のラッピングモジュールのコレクションを維持しています。
Wrappable.instance_variables #=> []
Bird.instance_variables #=> [:@wrappers]
Machine.instance_variables #=> [:@wrappers]
当然のことながら、これにより、以前に観察した問題も修正されます。現在、両方のクラスが独自の個別のモジュールでラップされています。
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzzz
# Shutting down
継承のサポート
これはすべて、継承が導入されるまでうまく機能します。クラスはスーパークラスからラッピングモジュールを継承することが期待されます。それが事実かどうかを確認しましょう。
module Flying
def make_noise
super
puts "Is flying away"
end
end
class Pigeon < Bird
wrap Flying
def make_noise
puts "Coo!"
end
end
pigeon = Pigeon.new
pigeon.make_noise
# Coo!
# Is flying away
ご覧のとおり、Pigeon
が原因で、期待どおりに機能しません。 独自のラッピングモジュールのコレクションも維持しています。 Pigeon
用に定義されたラッピングモジュールは理にかなっていますが Bird
では定義されていません 、それは私たちが望んでいるものではありません。継承チェーン全体からすべてのラッパーを取得する方法を考えてみましょう。
幸運なことに、RubyはModule#ancestors
を提供しています クラス(またはモジュール)が継承するすべてのクラスとモジュールを一覧表示するメソッド。
Pigeon.ancestors # => [Pigeon, Bird, Object, Kernel, BasicObject]
grep
を追加する 呼び出して、実際にWrappable
で拡張されたものを選択できます 。最初にチェーンの上位からのラッパーでインスタンスをラップするため、.reverse
を呼び出します。 順序を入れ替えます。
Pigeon.ancestors.grep(Wrappable).reverse # => [Bird, Pigeon]
ルビーの#===
メソッド
Rubyの魔法の一部は#===
に帰着します (またはケースの等式 ) 方法。デフォルトでは、#==
と同じように動作します (または平等 ) 方法。ただし、いくつかのクラスは#===
をオーバーライドします case
で異なる動作を提供するメソッド ステートメント。これが正規表現の使用方法です(#===
#match?
と同等です )、またはクラス(#===
#kind_of?
と同等です )それらのステートメントで。 Enumerable#grep
のようなメソッド 、Enumerable#all?
、またはEnumerable#any?
ケース等式法にも依存します。
これで、flat_map(&:wrappers)
を呼び出すことができます 継承チェーンで定義されているすべてのラッパーのリストを単一の配列として取得します。
Pigeon.ancestors.grep(Wrappable).reverse.flat_map(&:wrappers) # => [Logging]
残っているのは、それをinherited_wrappers
にパックすることだけです。 モジュールを作成し、wrappers
の代わりにそれを使用するように新しいメソッドを少し変更します メソッド。
module Wrappable
def inherited_wrappers
ancestors
.grep(Wrappable)
.reverse
.flat_map(&:wrappers)
end
def new(*arguments, &block)
instance = allocate
inherited_wrappers.each { |mod|instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
最後のテスト実行により、すべてが期待どおりに機能していることが確認されます。ラッピングモジュールは、それらが適用されるクラス(およびそのサブクラス)にのみ適用されます。
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzz
# Shutting down
pigeon = Pigeon.new
pigeon.make_noise
# Started making noise
# Coo!
# Finished making noise
# Is flying away
これで終わりです!
確かに、これらの騒々しい鳥は少し理論的な例です(ツイート、ツイート)。しかし、継承可能なクラスインスタンス変数は、クラスがどのように機能するかを理解するのに便利なだけではありません。これらは、クラスがRubyの単なるオブジェクトであるという優れた例です。
そして、継承可能なクラスインスタンス変数が実際の生活でも非常に役立つ可能性があることを認めます。たとえば、後でそれらを内省する機能を備えたモデルで属性と関係を定義することを考えてみてください。私たちにとって魔法は、これをいじって、物事がどのように機能するかをよりよく理解することです。そして、次のレベルのソリューションに心を開いてください。 🧙🏼♀️
いつものように、このパターンまたは同様のパターンを使用して構築したものを聞くのを楽しみにしています。 Twitterで@AppSignalにチャープするだけです。
-
環境変数へのRubyistsガイド
開発中および本番環境でWebアプリを効果的に管理できるようにするには、環境変数を理解する必要があります。 これは必ずしもそうではありませんでした。ほんの数年前、環境変数を使用してRailsアプリを構成している人はほとんどいませんでした。しかし、その後、Herokuが発生しました。 Herokuは、開発者に12要素のアプリアプローチを紹介しました。 12要素のアプリマニフェストでは、導入が簡単なアプリを作成するための多くのベストプラクティスを示しています。環境変数に関するセクションは特に影響力があります。 12ファクターのアプリは、構成を環境変数に格納します(多くの場合、env varsま
-
字句スコープとRubyクラス変数
Rubyのクラス変数は紛らわしいです。熟練したRubyユーザーでさえ、直感的に理解するのが難しい場合があります。最も明白な例は、継承と関係があります: class Fruit @@kind = nil def self.kind @@kind end end class Apple < Fruit @@kind = apple end Apple.kind # => apple Fruit.kind # => apple kindの変更 子クラスの変数は、親クラスでも変更します。それはかなりめちゃくちゃです。しかし、これはまさにその言語