{"id":20672064,"url":"https://github.com/workarea-commerce/rails-decorators","last_synced_at":"2025-04-19T18:49:45.775Z","repository":{"id":56890530,"uuid":"87116585","full_name":"workarea-commerce/rails-decorators","owner":"workarea-commerce","description":"Rails::Decorators provides a clean, familiar API for decorating the behavior of a Rails engine.","archived":false,"fork":false,"pushed_at":"2024-03-20T18:25:31.000Z","size":64,"stargazers_count":10,"open_issues_count":1,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-31T14:52:16.712Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/workarea-commerce.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2017-04-03T20:14:34.000Z","updated_at":"2022-01-18T20:45:51.000Z","dependencies_parsed_at":"2022-08-21T00:50:35.291Z","dependency_job_id":null,"html_url":"https://github.com/workarea-commerce/rails-decorators","commit_stats":null,"previous_names":["weblinc/rails-decorators"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workarea-commerce%2Frails-decorators","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workarea-commerce%2Frails-decorators/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workarea-commerce%2Frails-decorators/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workarea-commerce%2Frails-decorators/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/workarea-commerce","download_url":"https://codeload.github.com/workarea-commerce/rails-decorators/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224970216,"owners_count":17400292,"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":[],"created_at":"2024-11-16T20:31:35.739Z","updated_at":"2024-11-16T20:31:36.296Z","avatar_url":"https://github.com/workarea-commerce.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rails::Decorators\nUsing [Rails engines](http://guides.rubyonrails.org/engines.html) extensively, we needed a way to customize behavior for implementations of the platform. Rails provides a great mechanism for views and assets, but doesn't offer any functionality to handle controller/model/lib overriding/customizing. The suggested mechanism in the documentation is `class_eval` or ActiveSupport::Concern. Our preferred method for this is module prepending, which is cleaner than `class_eval` in that it avoids alias method chaining and becomes part of the ancestor chain.\n\nWe also want junior developers to be able to jump in and be productive ASAP - not necessarily requiring they learn the techniques of `class_eval` to fix a bug or get a simple task done. Yes, we educate and promote this as the developer grows, but we want the barriers to contribution as low as possible.\n\nWe like the [`ActiveSupport::Concern`](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html) API, so this library mimics the API of `ActiveSupport::Concern` and layers on top some niceties for the specific task of customizing a Rails engine. Also, we don't like having to store decorated behavior in a separate directory (like https://github.com/parndt/decorators). Storing decorations in the same place as new classes makes it much easier to see the overall picture of how the engine has been customized/extended in one shot.\n\n## Usage\nSay we have a class in an engine like so:\n```ruby\n# in ecommerce/app/models/ecommerce/product.rb\nmodule Ecommerce\n  class Product \u003c ApplicationRecord\n    def price\n      skus.first.price\n    end\n  end\nend\n```\n\n`Rails::Decorators` allows customizing this class like so:\n```ruby\n# in Rails.root/app/models/ecommerce/product.decorator\nmodule Ecommerce\n  decorate Product do\n    decorated do\n      # Class methods may be called here. Evaluates after prepending module.\n      attr_writer :on_sale\n    end\n\n    class_methods do\n      # Methods defined here extend/decorate static methods on the\n      # decorated class.\n      def sorts\n        super + ['discount_rate']\n      end\n    end\n\n    # Methods defined here extend/decorate instance methods on the\n    # decorated class.\n    def price\n      result = super\n      result *= discount_rate if on_sale?\n      result\n    end\n  end\nend\n```\n\n`Rails::Decorators` achieves this in a manner similar to `ActiveSupport::Concern` - it dynamically creates a module out of the block passed, and then prepends that into the original class. The above is equivalent to (note that this would need to be required as well):\n```ruby\n# in Rails.root/app/models/ecommerce/product_decorator.rb\nmodule Ecommerce\n  class Product\n    module Decorator\n      module ClassMethods\n        # Methods defined here extend/decorate static methods on the\n        # decorated class.\n        def sorts\n          super + ['discount_rate']\n        end\n      end\n\n      # Methods defined here extend/decorate instance methods on the\n      # decorated class.\n      def price\n        result = super\n        result *= discount_rate if on_sale?\n        result\n      end\n    end\n  end\n\n  Product.send(:prepend, Product::Decorator)\n  Product.singleton_class.send(:prepend, Product::Decorator::ClassMethods)\n  Product.class_eval do\n    # Class methods may be called here. Evaluates after prepending module.\n    attr_writer :on_sale\n  end\nend\n```\n\nYou can also decorate more than one class at a time:\n```ruby\n# in Rails.root/app/models/ecommerce/navigable.decorator\nmodule Ecommerce\n  decorate Product, Category, Page do\n    def generate_slug\n      # my custom logic here\n    end\n  end\nend\n```\n\nOther engines may want to namespace their customizations so as not to collide with further customizations:\n```ruby\n# in ecommerce_blog/app/models/ecommerce/product.decorator\nmodule Ecommerce\n  decorate Product, with: 'blog' do\n    def blog_entries\n      BlogEntry.for_product(id)\n    end\n  end\nend\n```\n\nIt is strongly suggested you update your editor of choice to use Ruby syntax highlighting on \\*.decorator files.\n\n### Case Study: Modifying Devise\n\nLet's examine a more real-world case: In the [Devise OmniAuth How-To\nArticle](https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview),\nthe maintainers instruct users to create a new controller named\n`Users::OmniauthCallbacksController` that inherits from\n`Devise::OmniauthCallbacksController`, and override the entire class in\nthe `devise_for` method by supplying the name of the controller to use.\nThis has always been a point of contention for some as it's quite\ndifficult to understand why you need to do this if you don't have a lot\nof experience with Rails applications.\n\nHowever, if you had `rails-decorators` installed, you don't need to do\nany of that. Just decorate the controller supplied by Devise in\n**app/controllers/devise/omniauth_callbacks_controller.decorator**!\n\n```ruby\nmodule Devise\n  decorate OmniauthCallbacksController do\n    # We're using Facebook as an example here so it can be directly\n    # translated from the aforementioned article...\n    def facebook\n      # You need to implement the method below in your model (e.g. app/models/user.rb)\n      @user = User.from_omniauth(request.env[\"omniauth.auth\"])\n\n      if @user.persisted?\n        sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated\n        set_flash_message(:notice, :success, kind: \"Facebook\") if is_navigational_format?\n      else\n        session[\"devise.facebook_data\"] = request.env[\"omniauth.auth\"]\n        redirect_to new_user_registration_url\n      end\n    end\n\n    def failure\n      redirect_to root_path\n    end\n  end\nend\n```\n\nWith `rails-decorators`, you get to skip about 3 or 4 paragraphs of docs\nand go right to the `.from_omniauth` definition. Decorating saves both\ntime and mental energy so you can get back to actually writing your\napplication.\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rails-decorators'\n```\n\nAnd then execute:\n```bash\n$ bundle\n```\n\nOr install it yourself as:\n```bash\n$ gem install rails-decorators\n```\n\n## Contributing\nContribution directions go here.\n\n## License\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkarea-commerce%2Frails-decorators","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fworkarea-commerce%2Frails-decorators","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkarea-commerce%2Frails-decorators/lists"}