Rubyの#dupと#cloneに飛び込む
今日の投稿では、Rubyの#dup
について調べます。 および#clone
。この関心を引き起こした実際の例から始めましょう。その後、#dup
の方法を学ぶことを目標に、さらに深く掘り下げていきます。 Rubyで実装され、#clone
との比較 。次に、独自の#dup
を実装して終了します。 方法。行きましょう!
Dupの使用を開始した方法
NGOが寄付を集めるキャンペーンの立ち上げを専門とする会社で働いていたとき、私は定期的にキャンペーンをコピーして新しいキャンペーンを作成しなければなりませんでした。たとえば、2018年のキャンペーンが終了した後、2019年の新しいキャンペーンが必要でした。
キャンペーンには通常、たくさんの設定オプションがありましたが、私はそれを再度設定する気にはなりませんでした。かなり時間がかかり、エラーが発生しやすくなりました。そこで、DBレコードをコピーすることから始めて、そこから始めました。
最初のいくつかのキャンペーンでは、実際にそれを手作業でコピーしました。次のようになりました:
current_campaign = Campaign.find(1)
new_campaign = current_campaign
new_campaign.id = nil
new_campaign.created_at = nil
new_campaign.updated_at = nil
new_campaign.title = "Campaign 2019"
new_campaign.save!
これは機能しますが、エラーが発生しやすいことは言うまでもなく、多くの入力が必要です。 created_at
を設定するのを忘れました nil
へ 過去に数回。
これは少し苦痛のように感じたので、それが最善の方法であるとは想像できませんでした。そして、結局のところ、より良い方法があります!
new_campaign = Campaign.find(1).dup
new_campaign.title = "Campaign 2019"
new_campaign.save!
これにより、IDとタイムスタンプがnil
に設定されます 、まさにそれが私たちが達成したいことです。
これが私が最初に#dup
を使い始めた方法です 。それでは、#dup
がどのように機能するかを詳しく見ていきましょう。 実際に機能します。
フードの下で何が起こっているのですか?
#dup
のデフォルトのRuby実装 メソッドを使用すると、オブジェクトが#dup
を介して初期化されたときにのみ呼び出される特別な初期化子をオブジェクトに追加できます。 方法。これらの方法は次のとおりです。
-
initialize_copy
-
initialize_dup
これらのメソッドの実装は、デフォルトでは何も実行しないため、実際には非常に興味深いものです。これらは基本的に、オーバーライドするためのプレースホルダーです。
これはRubyのソースコードから直接取得されます:
VALUE
rb_obj_dup(VALUE obj)
{
VALUE dup;
if (special_object_p(obj)) {
return obj;
}
dup = rb_obj_alloc(rb_obj_class(obj));
init_copy(dup, obj);
rb_funcall(dup, id_init_dup, 1, obj);
return dup;
}
私たちにとって興味深い部分は11行目で、Rubyが初期化メソッド#intialize_dup
を呼び出しています。 。
rb_funcall
ルビーCコードでよく使われる関数です。オブジェクトのメソッドを呼び出すために使用されます。この場合、id_init_dup
を呼び出しますdup
で 物体。1
引数がいくつあるかを示します。この場合は、obj
の1つだけです。
もう少し深く掘り下げて、その実装を見てみましょう:
VALUE
rb_obj_init_dup_clone(VALUE obj, VALUE orig)
{
rb_funcall(obj, id_init_copy, 1, orig);
return obj;
}
この例でわかるように、id_init_copy
を呼び出す以外は実際には何も起きていません。 。うさぎの穴を掘り下げたので、その方法も見てみましょう:
VALUE
rb_obj_init_copy(VALUE obj, VALUE orig)
{
if (obj == orig) return obj;
rb_check_frozen(obj);
rb_check_trusted(obj);
if (TYPE(obj) != TYPE(orig) || rb_obj_class(obj) != rb_obj_class(orig)) {
rb_raise(rb_eTypeError, "initialize_copy should take same class object");
}
return obj;
}
より多くのコードがありますが、内部で必要とされるいくつかのチェックを除いて、特別なことは何も起こっていません(しかし、それはまた別の機会に良い主題かもしれません)。
つまり、実装で何が起こるかというと、Rubyはエンドポイントを提供し、独自の興味深い動作を実装するために必要なツールを提供します。
Railsの重複実装
これはまさにRailsが多くの場所で行ったことですが、今のところ、id
がどのように行われるかにのみ関心があります。 タイムスタンプフィールドがクリアされます。
IDはActiveRecordのコアモジュールでクリアされます。主キーが何であるかが考慮されるため、それを変更してもリセットされます。
# activerecord/lib/active_record/core.rb
def initialize_dup(other) # :nodoc:
@attributes = @attributes.deep_dup
@attributes.reset(self.class.primary_key)
_run_initialize_callbacks
@new_record = true
@destroyed = false
@_start_transaction_state = {}
@transaction_state = nil
super
end
タイムスタンプは、タイムスタンプモジュールでクリアされます。 Railsが作成と更新に使用できるすべてのタイムスタンプをクリアするようにRailsに指示します(created_at
、created_on
、updated_at
およびupdated_on
。
# activerecord/lib/active_record/timestamp.rb
def initialize_dup(other) # :nodoc:
super
clear_timestamp_attributes
end
ここで興味深い事実は、Railsが意図的に#initialize_dup
をオーバーライドすることを選択したことです。 #initialize_copy
の代わりにメソッド 方法。なぜそれをするのでしょうか?調べてみましょう。
Object#initialize_copyの説明
上記のコードスニペットでは、Rubyが#initialize_dup
を呼び出す方法を確認しました。 .dup
を使用する場合 メソッドについて。ただし、#initialize_copy
もあります 方法。これがどこで使用されているかをよりよく説明するために、例を見てみましょう:
class Animal
attr_accessor :name
def initialize_copy(*args)
puts "#initialize_copy is called"
super
end
def initialize_dup(*args)
puts "#initialize_dup is called"
super
end
end
animal = Animal.new
animal.dup
# => #initialize_dup is called
# => #initialize_copy is called
これで、呼び出し順序が何であるかを確認できます。 Rubyは最初に#initialize_dup
を呼び出します 次に、#initialize_copy
を呼び出します 。 super
への呼び出しを続けていたとしたら #initialize_dup
から メソッドの場合、initialize_copy
を呼び出すことはありません。 、したがって、それを維持することが重要です。
何かをコピーする他の方法はありますか?
この実装を確認したので、2つの#initialize_*
を使用する場合のユースケースは何か疑問に思われるかもしれません。 メソッド。答えは次のとおりです。#clone
と呼ばれるオブジェクトをコピーする別の方法があります 。通常は#clone
を使用します 内部状態を含むオブジェクトをコピーする場合。
これは、Railsが#dup
で使用しているものです。 ActiveRecordのメソッド。 #dup
を使用します 「内部」状態(IDとタイムスタンプ)なしでレコードを複製し、#clone
を残すことができるようにします。 実装するRubyまで。
この追加のメソッドを使用すると、#clone
を使用するときに特定の初期化子も要求されます 方法。このために、#initialize_clone
をオーバーライドできます 。このメソッドは、#initialize_dup
と同じライフサイクルを使用します #initialize_copy
に向かって呼び出します 。
これを知っていると、イニシャライザメソッドの名前付けはもう少し意味があります。 #initialize_(dup|clone)
を使用できます #dup
を使用するかどうかに応じた特定の実装 または#clone
。両方に使用される包括的な動作がある場合は、それを#initialize_copy
内に配置できます。 。
動物のクローン作成
(ほんの一例ですが、このブログ投稿で怪我をした動物はいませんでした)
それでは、実際にどのように機能するかの例を見てみましょう。
class Animal
attr_accessor :name, :dna, :age
def initialize
self.dna = generate_dna
end
def initialize_copy(original_animal)
self.age = 0
super
end
def initialize_dup(original_animal)
self.dna = generate_dna
self.name = "A new name"
super
end
def initialize_clone(original_animal)
self.name = "#{original_animal.name} 2"
super
end
def generate_dna
SecureRandom.hex
end
end
bello = Animal.new
bello.name = "Bello"
bello.age = 10
bello_clone = bello.clone
bello_dup = bello.dup
bello_clone.name # => "Bello 2"
bello_clone.age # => 0
bello_dup.name # => "A new name"
bello_dup.age # => 0
ここで実際に起こっていることを分析してみましょう。 Animal
というクラスがあります 、そして動物をどのようにコピーするかに応じて、異なる動作をする必要があります:
- 動物のクローンを作成しても、DNAは同じままで、その名前は元の名前に2が追加されたものになります。
- 動物を複製するときは、元の動物に基づいて新しい動物を作成します。独自のDNAと新しい名前を取得します。
- すべての場合において、動物は赤ちゃんとして始まります。
これを実現するために、3つの異なる初期化子を実装しました。 #initialize_(dup|clone)
メソッドは常に#initialize_copy
を呼び出します 、したがって、年齢が0に設定されていることを確認します。
クローンと他の動物の切り上げ
自分自身を傷つける必要があるかゆみを説明することから始めて、データベースレコードをコピーすることを検討しました。キャンペーンの例で手作業でコピーすることから、#dup
に移行しました。 および#clone
。次に、それを実用的なものから魅力的なものに変え、これがRubyでどのように実装されているかを調べました。 #clone
も試してみました ingと#dup
動物を飼っています。私たちが書いたのと同じくらい、私たちの深いダイビングを楽しんでいただけたことを願っています。
-
RuboCopを使用したRubyコードのリンティングと自動フォーマット
リンティングは、プログラムおよびスタイルのエラーについてソースコードを自動チェックすることです。このチェックは、リンターと呼ばれる静的コード分析ツールによって実行されます。ただし、コードフォーマッタは、事前に構成された一連のルールに厳密に準拠するようにソースコードをフォーマットするためのツールです。リンターは通常違反を報告しますが、問題を修正するのは通常プログラマー次第ですが、コードフォーマッターはそのルールをソースコードに直接適用する傾向があるため、フォーマットの間違いを自動的に修正します。 プロジェクトでより一貫性のあるコードスタイルを作成するタスクでは、通常、個別のリンティングツールと
-
LoggerとLogrageを使用してRubyにログインする
Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使