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

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_atcreated_onupdated_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 動物を飼っています。私たちが書いたのと同じくらい、私たちの深いダイビングを楽しんでいただけたことを願っています。


  1. RuboCopを使用したRubyコードのリンティングと自動フォーマット

    リンティングは、プログラムおよびスタイルのエラーについてソースコードを自動チェックすることです。このチェックは、リンターと呼ばれる静的コード分析ツールによって実行されます。ただし、コードフォーマッタは、事前に構成された一連のルールに厳密に準拠するようにソースコードをフォーマットするためのツールです。リンターは通常違反を報告しますが、問題を修正するのは通常プログラマー次第ですが、コードフォーマッターはそのルールをソースコードに直接適用する傾向があるため、フォーマットの間違いを自動的に修正します。 プロジェクトでより一貫性のあるコードスタイルを作成するタスクでは、通常、個別のリンティングツールと

  2. LoggerとLogrageを使用してRubyにログインする

    Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使