{"id":18631760,"url":"https://github.com/esotericpig/init_copy","last_synced_at":"2025-04-11T07:30:40.801Z","repository":{"id":50712716,"uuid":"245341595","full_name":"esotericpig/init_copy","owner":"esotericpig","description":":beginner::bug::gemini: Easily use the appropriate clone/dup method in initialize_copy of Ruby.","archived":false,"fork":false,"pushed_at":"2022-08-01T01:39:57.000Z","size":65,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-24T17:42:36.334Z","etag":null,"topics":["clone","copy","dup","duplicate","init","initialize","ruby","ruby-gem","rubygem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esotericpig.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-06T05:59:19.000Z","updated_at":"2022-08-01T00:06:19.000Z","dependencies_parsed_at":"2022-09-18T08:51:03.532Z","dependency_job_id":null,"html_url":"https://github.com/esotericpig/init_copy","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esotericpig%2Finit_copy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esotericpig%2Finit_copy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esotericpig%2Finit_copy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esotericpig%2Finit_copy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esotericpig","download_url":"https://codeload.github.com/esotericpig/init_copy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223460670,"owners_count":17148760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clone","copy","dup","duplicate","init","initialize","ruby","ruby-gem","rubygem"],"created_at":"2024-11-07T05:08:31.823Z","updated_at":"2024-11-07T05:08:33.978Z","avatar_url":"https://github.com/esotericpig.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# InitCopy\n\n[![Gem Version](https://badge.fury.io/rb/init_copy.svg)](https://badge.fury.io/rb/init_copy)\n[![CI Status](https://github.com/esotericpig/init_copy/actions/workflows/ci.yml/badge.svg)](https://github.com/esotericpig/init_copy/actions/workflows/ci.yml)\n\n[![Source Code](https://img.shields.io/badge/source-github-%23211F1F.svg)](https://github.com/esotericpig/init_copy)\n[![Changelog](https://img.shields.io/badge/changelog-md-%23A0522D.svg)](CHANGELOG.md)\n[![License](https://img.shields.io/github/license/esotericpig/init_copy.svg)](LICENSE.txt)\n\nEasily use the appropriate method in `initialize_copy`, either `clone` or `dup`.\n\nOn the one hand, there is *Bob*. He likes to define his `initialize_copy` using `clone`:\n\n```Ruby\nclass Bob\n  attr_reader :name\n\n  def initialize\n    super\n\n    @name = 'Bob'\n  end\n\n  def initialize_copy(orig)\n    super(orig)\n\n    @name = @name.clone # Use clone.\n  end\nend\n```\n\nOn the other hand, there is *Fred*. He likes to use `dup`:\n\n```Ruby\nclass Fred\n  attr_reader :name\n\n  def initialize\n    super\n\n    @name = 'Fred'\n  end\n\n  def initialize_copy(orig)\n    super(orig)\n\n    @name = @name.dup # Use dup.\n  end\nend\n```\n\nWho is right? Who is wrong? Both lead to unexpected behavior.\n\n`clone` and `dup` have several differences, but as an example, one difference is copying over extensions. If a Rubyist uses `clone` or `dup` expecting the standard documented behavior, they'll be surprised.\n\nHere, Bob gets surprised:\n\n```Ruby\nmodule BobExt\n  def bob_rules\n    \"#{self} rules!\"\n  end\nend\n\nbob  = Bob.new\nfred = Fred.new\n\nbob.name.extend(BobExt)\nfred.name.extend(BobExt)\n\nbob  = bob.clone\nfred = fred.clone\n\nputs bob.name.bob_rules  # OK!\nputs fred.name.bob_rules # Error! Even though clone() should preserve BobExt!\n```\n\nAnd here, Fred gets surprised:\n\n```Ruby\nmodule FredExt\n  def to_s\n    \"Fred's password is 1234!\"\n  end\nend\n\nbob  = Bob.new\nfred = Fred.new\n\nbob.name.extend(FredExt)\nfred.name.extend(FredExt)\n\nbob  = bob.dup\nfred = fred.dup\n\nputs bob.name.to_s  # What!? dup() should have removed FredExt!\nputs fred.name.to_s # OK!\n```\n\nSo there are several solutions. The obvious one is to define `initialize_clone` and `initialize_dup` and call the appropriate methods, but that means you have duplicate code doing basically the same thing! And leads to copy \u0026 pasting as well as forgetting about one of the methods.\n\n```Ruby\ndef initialize_clone(orig)\n  super(orig)\n\n  @name = @name.clone\n  # 100 more lines...\nend\n\ndef initialize_dup(orig)\n  super(orig)\n\n  @name = @name.dup\n  # 100 more lines...\nend\n```\n\nMarshalling also won't solve this, as it will produce the same result for both `clone` and `dup`, when they should be different (according to the standard documentation) as illustrated earlier with `BobExt` and `FredExt`.\n\nWell, then along comes *George*. He's a pretty nice guy. He likes to do it this way:\n\n```Ruby\ndef initialize_copy(orig)\n  super(orig)\n\n  copy = caller[0].include?('clone') ? :clone : :dup\n\n  @name = @name.__send__(copy)\n  # More lines...\nend\n```\n\nAdmittedly, it's pretty hacky, but that's basically what this Gem does.\n\nFor speed, there is also a mixin (doesn't check the `caller`).\n\nSee the [Using](#using) section for more info.\n\n## Contents\n\n- [Setup](#setup)\n- [Using](#using)\n- [Hacking](#hacking)\n- [License](#license)\n\n## [Setup](#contents)\n\nPick your poison...\n\nWith the *RubyGems* CLI package manager:\n\n`$ gem install init_copy`\n\nIn your *Gemspec* (*\u0026lt;project\u0026gt;.gemspec*):\n\n```Ruby\n# Pick one...\nspec.add_runtime_dependency 'init_copy', '~\u003e X.X'\nspec.add_development_dependency 'init_copy', '~\u003e X.X'\n```\n\nIn your *Gemfile*:\n\n```Ruby\n# Pick one...\ngem 'init_copy', '~\u003e X.X'\ngem 'init_copy', '~\u003e X.X', group: :development\ngem 'init_copy', git: 'https://github.com/esotericpig/init_copy.git', tag: 'vX.X.X'\n```\n\nFrom source:\n\n```\n$ git clone 'https://github.com/esotericpig/init_copy.git'\n$ cd init_copy\n$ bundle install\n$ bundle exec rake install:local\n```\n\n## [Using](#contents)\n\n### With the Copier Class\n\n```Ruby\nrequire 'init_copy'\n\nclass George\n  attr_reader :name\n  attr_reader :cool\n\n  def initialize\n    super\n\n    @name = 'George'.dup\n    @cool = true\n  end\n\n  def initialize_copy(orig)\n    super(orig)\n\n    ic = InitCopy.new()\n\n    @name = ic.copy(@name)\n    @cool = ic.copy(@cool)\n  end\nend\n\nog = George.new\nng = og.dup\n\nng.name \u003c\u003c ' drools!'\n\nputs og.name #=\u003e \"George\"\n```\n\nIn the constructor, you can set the default fallback to `:clone` instead (in case the `caller` cannot be determined):\n\n```Ruby\nic = InitCopy.new(:clone) # Default (no args) is :dup\n```\n\nThere is also a `safe_copy` in case the Object does not have a `clone` or `dup` method:\n\n```Ruby\n@name = ic.safe_copy(@name)\n@cool = ic.safe_copy(@cool)\n```\n\nIf you want to store the class in an instance variable for some reason (**not recommended**), you can call `update_name`:\n\n```Ruby\nclass George\n  attr_reader :name\n  attr_reader :cool\n\n  def initialize\n    super\n\n    @ic = InitCopy.new(:butterfly)\n\n    @name = 'George'.dup\n    @cool = true\n  end\n\n  def initialize_copy(orig)\n    super(orig)\n\n    puts \"Copy method name: #{@ic.name}\" # :butterfly\n    @ic.update_name()\n    puts \"Copy method name: #{@ic.name}\" # :clone or :dup\n\n    @name = @ic.copy(@name)\n    @cool = @ic.copy(@cool)\n  end\nend\n```\n\nUnder the hood, this uses `InitCopy::Copier`, which also has an alias `InitCopy::Copyer`.\n\n### With the Copyable Mixin\n\nThe mixin is faster than the above class way; because instead of relying on searching \u0026 parsing the `caller`, it sets `@init_copy_method_name` to either `:clone` or `:dup` appropriately in the following methods that it defines:\n\n- `initialize_clone`\n- `initialize_dup`\n- `clone`\n- `dup`\n\nThen in your `initialize_copy`, use one of these *private* methods that it also defines:\n\n- `copy`\n- `safe_copy`\n\n**Note**: If your class and/or its descendants redefine any of these methods, they must call `super`, else it will not work!\n\n```Ruby\nrequire 'init_copy'\n\nclass George\n  include InitCopy::Copyable\n\n  attr_reader :name\n  attr_reader :cool\n\n  def initialize\n    super\n\n    @name = 'George'.dup\n    @cool = true\n  end\n\n  def initialize_copy(orig)\n    super(orig)\n\n    @name = copy(@name)\n    @cool = safe_copy(@cool)\n  end\nend\n\nog = George.new\nng = og.dup\n\nng.name \u003c\u003c ' drools!'\n\nputs og.name # \"George\"\n```\n\nYou can set the default fallback in your constructor:\n\n```Ruby\ndef initialize\n  super\n\n  @init_copy_method_name = :clone\nend\n```\n\n**Note**: `@init_copy_method_name` could be `nil` in `copy` if `super` was not called in your `initialize` method, and this will cause an error. `safe_copy` checks for this scenario and will default to `:dup` if `nil`. In general, it should be safe to not define a default value, assuming correct coding practices.\n\n`InitCopy::Copiable` is an alias to this class.\n\n## [Hacking](#contents)\n\n```\n$ git clone 'https://github.com/esotericpig/init_copy.git'\n$ cd init_copy\n$ bundle install\n$ bundle exec rake -T\n```\n\n### Testing\n\n```\n$ bundle exec rake test\n```\n\n### Generating Doc\n\n```\n$ bundle exec rake doc\n```\n\n## [License](#contents)\n\n[MIT](LICENSE.txt)\n\n\u003e Copyright (c) 2020-2022 Jonathan Bradley Whited  \n\u003e \n\u003e Permission is hereby granted, free of charge, to any person obtaining a copy  \n\u003e of this software and associated documentation files (the \"Software\"), to deal  \n\u003e in the Software without restriction, including without limitation the rights  \n\u003e to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  \n\u003e copies of the Software, and to permit persons to whom the Software is  \n\u003e furnished to do so, subject to the following conditions:  \n\u003e \n\u003e The above copyright notice and this permission notice shall be included in all  \n\u003e copies or substantial portions of the Software.  \n\u003e \n\u003e THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  \n\u003e IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  \n\u003e FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  \n\u003e AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  \n\u003e LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  \n\u003e OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  \n\u003e SOFTWARE.  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesotericpig%2Finit_copy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesotericpig%2Finit_copy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesotericpig%2Finit_copy/lists"}