{"id":13879928,"url":"https://github.com/saturnflyer/casting","last_synced_at":"2025-05-16T12:03:12.575Z","repository":{"id":3400023,"uuid":"4449612","full_name":"saturnflyer/casting","owner":"saturnflyer","description":"Delegate methods in Ruby and preserve self. Add behaviors to your objects without altering their superclass hierarchy.","archived":false,"fork":false,"pushed_at":"2023-01-07T18:18:31.000Z","size":257,"stargazers_count":359,"open_issues_count":0,"forks_count":21,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-10-14T16:09:11.516Z","etag":null,"topics":["delegation","ruby"],"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/saturnflyer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-05-25T20:03:48.000Z","updated_at":"2024-09-07T04:29:18.000Z","dependencies_parsed_at":"2023-01-13T12:29:17.728Z","dependency_job_id":null,"html_url":"https://github.com/saturnflyer/casting","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturnflyer%2Fcasting","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturnflyer%2Fcasting/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturnflyer%2Fcasting/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturnflyer%2Fcasting/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saturnflyer","download_url":"https://codeload.github.com/saturnflyer/casting/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254050825,"owners_count":22006369,"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":["delegation","ruby"],"created_at":"2024-08-06T08:02:39.441Z","updated_at":"2025-05-16T12:03:12.477Z","avatar_url":"https://github.com/saturnflyer.png","language":"Ruby","readme":"# Casting\n\n[![Code Climate](https://codeclimate.com/github/saturnflyer/casting.png)](https://codeclimate.com/github/saturnflyer/casting)\n[![Test Coverage](https://codeclimate.com/github/saturnflyer/casting/badges/coverage.svg)](https://codeclimate.com/github/saturnflyer/casting/coverage)\n[![Gem Version](https://badge.fury.io/rb/casting.png)](http://badge.fury.io/rb/casting)\n\n## Add behavior to your objects without using extend\nDo it for the life of the object or only for the life of a block of code.\n\nCasting gives you real delegation that flattens your object structure compared to libraries\nlike Delegate or Forwardable. With casting, you can implement your own decorators that\nwill be so much simpler than using wrappers.\n\nHere's a quick example that you might try in a Rails project:\n\n```ruby\n# implement a module that contains information for the request response\n# and apply it to an object in your system.\ndef show\n  @user = user.cast_as(UserRepresenter)\nend\n```\n\nTo use proper delegation, your approach should preserve `self` as a reference\nto the original object receiving a method. When the object receiving the forwarded\nmessage has its own and separate notion of `self`, you're working with a wrapper (also called\nconsultation) and not using delegation.\n\nThe Ruby standard library includes a library called \"delegate\", but it is\na consultation approach. With that \"delegate\", all messages are forwarded to\nanother object, but the attendant object maintains its own identity.\n\nWith Casting, your defined methods may reference `self` and during\nexecution it will refer to the original client object.\n\nCasting was created while exploring ideas for [cleaning up ruby programs](http://clean-ruby.com).\n\n## Usage\n\nTo use Casting, you must first extend an object as the delegation client:\n\n```ruby\nactor = Object.new\nactor.extend(Casting::Client)\n```\n\nOr you may include the module in a particular class:\n\n```ruby\nclass Actor\n  include Casting::Client\nend\nactor = Actor.new\n```\n\nYour objects will have a few additional methods: `delegation`, `cast`, and if you do not *already* have it defined (from another library, for example): `delegate`. The `delegate` method is aliased to `cast`.\n\nThen you may delegate a method to an attendant object:\n\n```ruby\nactor.delegate(:hello_world, other_actor)\n```\n\nOr you may create an object to manage the delegation of methods to an attendant object:\n\n```ruby\nactor.delegation(:hello_world).to(other_actor).call\n```\n\nYou may also delegate methods without an explicit attendant instance, but provide\na module containing the behavior you need to use:\n\n```ruby\nmodule GreetingModule\n  def hello_world\n    \"hello world\"\n  end\nend\n\nactor.delegate(:hello_world, GreetingModule)\n# or\nactor.delegation(:hello_world).to(GreetingModule).call\n```\n\nPass arguments to your delegated method:\n\n```ruby\nactor.delegate(:verbose_method, another_actor, arg1, arg2)\n\nactor.delegation(:verbose_method).to(another_actor).with(arg1, arg2).call\n\nactor.delegation(:verbose_method).to(another_actor).call(arg1, arg2)\n```\n\n_That's great, but why do I need to do these extra steps? I just want to run the method._\n\nCasting gives you the option to do what you want. You can run just a single method once, or alter your object to always delegate. Even better, you can alter your object to delegate temporarily...\n\n### Temporary Behavior\n\nCasting also provides an option to temporarily apply behaviors to an object.\n\nOnce your class or object is a `Casting::Client` you may send the `delegate_missing_methods` message to it and your object will use `method_missing` to delegate methods to a stored attendant.\n\n```ruby\nclass Actor\n  include Casting::Client\n  delegate_missing_methods\nend\nactor = Actor.new\n\nactor.hello_world #=\u003e NoMethodError\n\nCasting.delegating(actor =\u003e GreetingModule) do\n  actor.hello_world #=\u003e output the value / perform the method\nend\n\nactor.hello_world #=\u003e NoMethodError\n```\n\nThe use of `method_missing` is opt-in. If you don't want that mucking up your method calls, just don't tell it to `delegate_missing_methods`.\n\nBefore the block is run in `Casting.delegating`, a collection of delegate objects is set in the current Thread for the provided attendant. Then the block yields, and an `ensure` block cleans up the stored attendant.\n\nThis allows you to nest your `delegating` blocks as well:\n\n```ruby\nactor.hello_world #=\u003e NoMethodError\n\nCasting.delegating(actor =\u003e GreetingModule) do\n  actor.hello_world #=\u003e output the value / perform the method\n\n  Casting.delegating(actor =\u003e OtherModule) do\n    actor.hello_world #=\u003e still works!\n    actor.other_method # values/operations from the OtherModule\n  end\n\n  actor.other_method #=\u003e NoMethodError\n  actor.hello_world #=\u003e still works!\nend\n\nactor.hello_world #=\u003e NoMethodError\n```\n\nCurrently, by using `delegate_missing_methods` you forever mark that object or class to use `method_missing`. This may change in the future.\n\n### Manual Delegate Management\n\nIf you'd rather not wrap things in the `delegating` block, you can control the delegation yourself.\nFor example, you can `cast_as` and `uncast` an object with a given module:\n\n```ruby\nactor.cast_as(GreetingModule)\nactor.hello_world # all subsequent calls to this method run from the module\nactor.uncast # manually cleanup the delegate\nactor.hello_world # =\u003e NoMethodError\n```\n\nThese methods are only defined on your `Casting::Client` object when you tell it to `delegate_missing_methods`. Because these require `method_missing`, they do not exist until you opt-in.\n\n### Duck-typing with NullObject-like behavior\n\nCasting has a few modules built in to help with treating your objects like null objects.\nTake a look at the following example:\n\n```ruby\nmodule SpecialStuff\n  def special_link\n    # some link code\n  end\nend\n\nspecial_user.cast_as(SpecialStuff)\nspecial_user.special_link # outputs your link\n```\n\nIf your app, for example, generates a list of info for a collection of users, how do you manage the objects which don't have the expected behavior?\n\n```ruby\n[normal_user, other_user, special_user].each do |user|\n  user.special_link #=\u003e blows up for normal_user or other_user\nend\n```\n\nYou can cast the other objects with `Casting::Null` or `Casting::Blank`:\n\n```ruby\nnormal_user.cast_as(Casting::Null)\nother_user.cast_as(Casting::Blank)\nspecial_user.cast_as(SpecialStuff)\n\n[normal_user, other_user, special_user].each do |user|\n  user.special_link #=\u003e normal_user yields nil, other_user yields \"\", and special_user yields the special_link\nend\n```\n\n## I have a Rails app, how does this help me?\n\nWell, a common use for this behavior would be in using decorators.\n\nWhen using a wrapper, your forms can behave unexpectedly\n\n```ruby\nclass UsersController\n  def edit\n    @user = UserDecorator.new(User.find(params[:id]))\n  end\nend\n\n\u003c%= form_for(@user) do |f| %\u003e #=\u003e \u003cform action=\"/user_decorators/1\"\u003e\n```\n\nRuby allows you to hack this by defining the `class` method:\n\n```ruby\nclass UserDecorator\n  def class\n    User\n  end\nend\n```\n\nThat would solve the problem, and it works! But having an object report that\nits class is something other than what it actually is can be confusing\nwhen you're debugging.\n\nInstead, you could cast the object as a module and your form will generate properly:\n\n```ruby\nclass UsersController\n  def edit\n    @user = User.find(params[:id]).cast_as(UserDecorator) # as a module\n  end\nend\n\n\u003c%= form_for(@user) do |f| %\u003e #=\u003e \u003cform action=\"/users/1\"\u003e\n```\n\nThis keeps your code focused on the object you care about.\n\nCheck out [Characterize](http://github.com/saturnflyer/characterize) for hooking into Rails automatically.\n\n## Oh, my! Could this be used to add behavior like refinements?\n\nYou can apply methods from a delegate to all instances of a class.\n\n```ruby\nperson.hello_world #=\u003e NoMethodError\n\nCasting.delegating(Person =\u003e GreetingModule) do\n  person.hello_world #=\u003e output the value / perform the method\nend\n\nperson.hello_world #=\u003e NoMethodError\n```\n\nBy default, the `delegate_missing_methods` method will set delegates on instances so you'll need to opt-in for this.\n\n```ruby\nclass Person\n  include Casting::Client\n  delegate_missing_methods :class\nend\n```\n\n_But what happens when you have method clashes or want a specific instance to behave differently?_\n\nYou can have your objects look to their instance delegates, their class delegates, or in a particular order:\n\n```ruby\nclass Person\n  include Casting::Client\n  # default delegation to instances\n  delegate_missing_methods\n\n  # delegate methods to those defined on the class\n  delegate_missing_methods :class\n\n  # delegate methods to those defined on the class, then those defined on the instance\n  delegate_missing_methods :class, :instance\n\n  # delegate methods to those defined on the instance, then those defined on the class\n  delegate_missing_methods :instance, :class\nend\n```\n\n## What's happening when I use this?\n\nRuby allows you to access methods as objects and pass them around just like any other object.\n\nFor example, if you want a method from a class you may do this:\n\n```ruby\nclass Person\n  def hello\n    \"hello\"\n  end\nend\nPerson.new.method(:hello).unbind #=\u003e #\u003cUnboundMethod: Person#hello\u003e\n# or\nPerson.instance_method(:hello) #=\u003e #\u003cUnboundMethod: Person#hello\u003e\n```\n\nBut if you attempt to use that `UnboundMethod` on an object that is not a `Person` you'll get\nan error about a type mismatch.\n\nCasting will bind an UnboundMethod method to a client object and execute the method as though it is\ndefined on the client object. Any reference to `self` from the method block will refer to the\nclient object.\n\nRather than define methods on classes, you may take any method from a module and apply it to any object regardless of its class.\n\n```ruby\nGreetingModule.instance_method(:hello).bind(actor).call\n```\n\nCasting provides a convenience for doing this.\n\n## What if my modules create instance variables on the object? Can I clean them up?\n\nYup.\n\nIf you need to set some variables so that your module can access them, it's as easy as defining `cast_object` and `uncast_object` on your module. Here's an example:\n\n```ruby\nmodule Special\n  def self.cast_object(obj)\n    obj.instance_variable_set(:@special_value, 'this is special!')\n  end\n  \n  def self.uncast_object(obj)\n    obj.remove_instance_variable(:@special_value)\n  end\n  \n  def special_behavior\n    \"#{self.name} thinks... #{@special_value}\"\n  end\nend\n\nobject.cast_as(Special)\nobject.special_method\nobject.uncast\n# object no longer has the @special_value instance variable\n```\n\nYou'll be able to leave your objects as if they were never touched by the module where you defined your behavior.\n\n## It doesn't work!\n\nYou might be trying to override existing methods. Casting can help you apply behavior to an object using `delegate_missing_methods` but that depends on the methods being missing. In other words, if you have an `as_json` method that you want to change with a module, you won't be able to just `cast_as(MyJsonModule)` and have the `as_json` method from it be picked up because that will never hit `method_missing`.\n\nIf you want to override an existing method, you must do so explicitly.\n\nThis will _not_ work:\n\n```ruby\nmodule MyJsonModule\n  def as_json\n    super.merge({ extra: 'details' })\n  end\nend\nsome_object.cast_as(MyJsonModule)\nsome_object.as_json\n```\n\nInstead, you'll need to explicitly override existing methods:\n\n```ruby\nsome_object.cast(:as_json, MyJsonModule)\n```\n\n## How can I speed it up?\n\nAre you looping over lots of objects and want see better performance?\n\nIf you want to make things a bit faster, you can prepare the method delegation ahead of time and change the client object.\n\n```ruby\nprepared_delegation = some_object.delegation(:some_delegated_method).to(MySpecialModule)\n# Some looping code\nbig_list_of_objects.each do |object|\n  prepared_delegation.client = object\n  prepared_delegation.call\nend\n```\n\nPreparing the delegated method like this will probably speed things up for you but be sure to verify for yourself.\n\n## Installation\n\nIf you are using Bundler, add this line to your application's Gemfile:\n\n```ruby\ngem 'casting'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install casting\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\nBuilt by Jim Gay at [Saturn Flyer](http://www.saturnflyer.com)\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaturnflyer%2Fcasting","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaturnflyer%2Fcasting","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaturnflyer%2Fcasting/lists"}