{"id":16834645,"url":"https://github.com/alexander-senko/magic-decorator","last_synced_at":"2026-02-27T16:05:13.270Z","repository":{"id":257824280,"uuid":"871836717","full_name":"Alexander-Senko/magic-decorator","owner":"Alexander-Senko","description":"SimpleDelegator on steroids: automatic delegation, decorator class inference, etc.","archived":false,"fork":false,"pushed_at":"2025-05-18T09:06:57.000Z","size":44,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-21T23:57:45.411Z","etag":null,"topics":["decorators","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/Alexander-Senko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-13T04:35:12.000Z","updated_at":"2025-05-18T09:07:00.000Z","dependencies_parsed_at":"2025-04-10T06:41:31.267Z","dependency_job_id":"6fb438ab-3dce-4743-aae9-da6ef3a1a8db","html_url":"https://github.com/Alexander-Senko/magic-decorator","commit_stats":null,"previous_names":["alexander-senko/magic-decorator"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Alexander-Senko/magic-decorator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexander-Senko%2Fmagic-decorator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexander-Senko%2Fmagic-decorator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexander-Senko%2Fmagic-decorator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexander-Senko%2Fmagic-decorator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Alexander-Senko","download_url":"https://codeload.github.com/Alexander-Senko/magic-decorator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexander-Senko%2Fmagic-decorator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29903617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T14:46:13.553Z","status":"ssl_error","status_checked_at":"2026-02-27T14:46:10.522Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["decorators","ruby"],"created_at":"2024-10-13T12:07:13.116Z","updated_at":"2026-02-27T16:05:13.255Z","avatar_url":"https://github.com/Alexander-Senko.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪄 Magic Decorator\n\n![GitHub Actions Workflow Status](\n\thttps://img.shields.io/github/actions/workflow/status/Alexander-Senko/magic-decorator/ci.yml\n)\n![Code Climate maintainability](\n\thttps://img.shields.io/codeclimate/maintainability-percentage/Alexander-Senko/magic-decorator\n)\n![Code Climate coverage](\n\thttps://img.shields.io/codeclimate/coverage/Alexander-Senko/magic-decorator\n)\n\nA bit of history:\nthis gem was inspired by digging deeper into [Draper](https://github.com/drapergem/draper) with an eye on a refactoring.\n\nIt implements a general decorator logic. It’s not meant to be a _presenter_.\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add magic-decorator\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install magic-decorator\n\n## Usage\n\n`Magic::Decorator::Base` is a basic decorator class to be inherited by any other decorator.\nIt further inherits from [`SimpleDelegator`](\n\thttps://docs.ruby-lang.org/en/master/SimpleDelegator.html\n) and is straightforward like that.\n\n```ruby\nclass PersonDecorator \u003c Magic::Decorator::Base\n  def name = \"#{first_name} #{last_name}\"\nend\n\nPerson = Struct.new :first_name, :last_name do\n  include Magic::Decoratable\nend\n\nperson = Person.new('John', 'Smith').decorate\nperson.name # =\u003e \"John Smith\"\n```\n\n### `Magic::Decoratable`\n\nThis module adds three methods to decorate an object.\nDecorator class is being inferred automatically.\nWhen no decorator is found,\n- `#decorate`  returns `nil`,\n- `#decorate!` raises `Magic::Lookup::Error`,\n- `#decorated` returns the original object.\n\nOne can test for the object is actually decorated with `#decorated?`.\n\n```ruby\n'with no decorator for String'.decorated\n    .decorated? # =\u003e false\n['with a decorator for Array'].decorated\n    .decorated? # =\u003e true\n```\n\n### Extending decorator logic\n\nWhen extending `Magic::Decoratable`, one may override `#decorator_base` to be used for lookup.\n\n```ruby\nclass Special::Decorator \u003c Magic::Decorator::Base\n  def self.name_for object_class\n    \"Special::#{object_class}Decorator\"\n  end\nend\n\nmodule Special::Decoratable\n  include Magic::Decoratable\n\n  private\n\n  def decorator_base = Special::Decorator\nend\n\nclass Special::Model\n  include Special::Decoratable\nend\n\nSpecial::Model.new.decorate # looks for Special::Decorator descendants\n```\n\n## 🪄 Magic\n\n### Decoratable scope\n\n`Magic::Decoratable` is mixed into `Object` by default. It means that effectively any object is _magically decoratable_.\n\nOne can use `Magic::Decoratable.classes` to see all the decoratable classes.\n\n### Decoration expansion\n\nFor almost any method called on a decorated object, both its result and `yield`ed arguments get decorated.\n\n```ruby\n'with no decorator for String'.decorated.chars\n    .decorated? # =\u003e false\n['with a decorator for Array'].decorated.map(\u0026:chars).first.grep(/\\S/).group_by(\u0026:upcase).transform_values(\u0026:size).sort_by(\u0026:last).reverse.first(5).map(\u0026:first)\n    .decorated? # =\u003e true\n```\n\n#### Undecorated methods\n\nSome methods aren’t meant to be decorated though:\n\n- `deconstruct` \u0026 `deconstruct_keys` for _pattern matching_,\n- _converting_ methods: those starting with `to_`,\n- _system_ methods: those starting with `_`.\n\n#### `undecorated` modifier\n\n`Magic::Decorator::Base.undecorated` can be used to exclude methods from being decorated automagically.\n\n```ruby\nclass MyDecorator \u003c Magic::Decorator::Base\n  undecorated %i[to_s inspect]\n  undecorated :raw_method\n  undecorated :m1, :m2\nend\n```\n\n### Decorator class inference\n\nDecorators provide automatic class inference for any object based on its class name\npowered by [Magic Lookup](\n\thttps://github.com/Alexander-Senko/magic-lookup\n).\n\nFor example, `MyNamespace::MyModel.new.decorate` looks for `MyNamespace::MyModelDecorator` first.\nWhen missing, it further looks for decorators for its ancestor classes, up to `ObjectDecorator`.\n\n### Default decorators\n\n#### `EnumerableDecorator`\n\nIt automagically decorates all its decoratable items.\n\n```ruby\n[1, [2], { 3 =\u003e 4 }, '5'].decorated\n    .map \u0026:decorated? # =\u003e [false, true, true, false]\n\n{ 1 =\u003e 2, [3] =\u003e [4] }.decorated.keys\n    .map \u0026:decorated? # =\u003e [false, true]\n{ 1 =\u003e 2, [3] =\u003e [4] }.decorated.values\n    .map \u0026:decorated? # =\u003e [false, true]\n\n{ 1 =\u003e 2, [3] =\u003e [4] }.decorated[1]\n    .decorated? # =\u003e false\n{ 1 =\u003e 2, [3] =\u003e [4] }.decorated[[3]]\n    .decorated? # =\u003e true\n```\n\n##### Side effects for decorated collections\n\n- enables _splat_ operator: `*decorated` ,\n- enables _double-splat_ operator: `**decorated`,\n- enumerating methods yield decorated items.\n\n## Overriding the magic\n\nWhen one needs more complicated behavior than the default one or feels like [_explicit is better than implicit_](\n\thttps://peps.python.org/pep-0020/#the-zen-of-python\n).\n\n### Decorator class inference\n\nOne may override `#decorator` for any decoratable class, to be used instead of Magic Lookup.\n\n- That could be as straightforward as a constant:\n\n\t```ruby\n\tclass Guest\n\t  private\n\t\n\t  def decorator = UserDecorator\n\tend\n\t\n\tguest.decorate # =\u003e instance of UserDecorator\n\t```\n\n- Or, that could be virtually any logic:\n\n\t```ruby\n\tclass User\n\t  private\n\t\n\t  def decorator = admin? ? AdminDecorator : super\n\tend\n\t\n\tuser.decorate  # =\u003e instance of UserDecorator\n\tadmin.decorate # =\u003e instance of AdminDecorator\n\t```\n\n## Testing decorators\n\nTesting a decorator is much like testing any other class.\n\nTo test whether an object is decorated one can use `#decorated?` method.\n\n\u003e [!NOTE]\n\u003e A decorated object equals the original one (`object.decorated == object`).\n\u003e Thus, any existing tests shouldn’t break when the objects being tested get decorated.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/Alexander-Senko/magic-decorator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/Alexander-Senko/magic-decorator/blob/main/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Magic Decorator project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Alexander-Senko/magic-decorator/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexander-senko%2Fmagic-decorator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexander-senko%2Fmagic-decorator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexander-senko%2Fmagic-decorator/lists"}