{"id":15537317,"url":"https://github.com/nullvoxpopuli/drawers","last_synced_at":"2025-04-06T04:10:58.218Z","repository":{"id":10810561,"uuid":"67083348","full_name":"NullVoxPopuli/drawers","owner":"NullVoxPopuli","description":"Group related classes together. No more silos. A solution to rails dystopia.","archived":false,"fork":false,"pushed_at":"2023-12-15T08:45:37.000Z","size":149,"stargazers_count":147,"open_issues_count":15,"forks_count":6,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-30T02:09:47.665Z","etag":null,"topics":["architecture","drawer","rails","resource","silo","unification"],"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/NullVoxPopuli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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":"2016-09-01T00:18:36.000Z","updated_at":"2024-05-17T16:45:08.000Z","dependencies_parsed_at":"2024-10-30T11:01:53.549Z","dependency_job_id":"3dc62321-72c7-45b1-9fb8-bdbe94f63f2c","html_url":"https://github.com/NullVoxPopuli/drawers","commit_stats":{"total_commits":87,"total_committers":3,"mean_commits":29.0,"dds":0.02298850574712641,"last_synced_commit":"75936a180b6b9c670144338584b1296af264f377"},"previous_names":["nullvoxpopuli/rails_module_unification"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fdrawers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fdrawers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fdrawers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fdrawers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NullVoxPopuli","download_url":"https://codeload.github.com/NullVoxPopuli/drawers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247430870,"owners_count":20937874,"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":["architecture","drawer","rails","resource","silo","unification"],"created_at":"2024-10-02T11:56:04.300Z","updated_at":"2025-04-06T04:10:58.204Z","avatar_url":"https://github.com/NullVoxPopuli.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Drawers\nGroup related classes together. No more silos.\n\n[![Gem Version](https://badge.fury.io/rb/drawers.svg)](https://badge.fury.io/rb/drawers)\n[![Build Status](https://travis-ci.org/NullVoxPopuli/drawers.svg?branch=master)](https://travis-ci.org/NullVoxPopuli/drawers)\n[![Code Climate](https://codeclimate.com/github/NullVoxPopuli/drawers/badges/gpa.svg)](https://codeclimate.com/github/NullVoxPopuli/drawers)\n[![Test Coverage](https://codeclimate.com/github/NullVoxPopuli/drawers/badges/coverage.svg)](https://codeclimate.com/github/NullVoxPopuli/drawers/coverage)\n[![Dependency Status](https://gemnasium.com/badges/github.com/NullVoxPopuli/drawers.svg)](https://gemnasium.com/github.com/NullVoxPopuli/drawers)\n\n\n## What is this about?\n\nWith large rails application, the default architecture can result in a resource's related files being very spread out through the overall project structure. For example, lets say you have 50 controllers, serializers, policies, and operations. That's _four_ different top level folders that spread out all the related objects. It makes sense do it this way, as it makes rails' autoloading programmatically easy.\n\nThis gem provides a way to re-structure your app so that like-objects are grouped together.\n\nAll this gem does is add some new autoloading / path resolution logic. This gem does not provide any service/operation/policy/etc functionality.\n\n**All of this is optional, and can be slowly migrated to over time. Adding this gem does not force you to change your app.**\n\n### The new structure\n\n```ruby\napp/\n├── channels/\n├── models/\n│   ├── data/\n│   │   ├── post.rb\n│   │   └── comment.rb\n│   └── graph_data.rb\n├── jobs/\n├── mailers/\n│   └── notification_mailer.rb\n└── resources/\n    ├── posts/\n    │   ├── forms/\n    │   │   └── new_post_form.rb\n    │   ├── controller.rb  # or posts_controller.rb\n    │   ├── operations.rb  # or post_operations.rb\n    │   ├── policy.rb      # or post_policy.rb\n    │   └── serializer.rb  # or post_serializer.rb\n    └── comments/\n        ├── controller.rb\n        ├── serializer.rb\n        └── views/\n            ├── index.html.erb\n            └── create.html.erb\n\n```\n\nDoes this new structure mean you have to change the class names of all your classes? Nope. In the above example file structure, `app/resources/posts/controller.rb` _still_ defines `class PostsController \u003c ApplicationController`\n\n[Checkout the sample rails app in the tests directory.](https://github.com/NullVoxPopuli/drawers/tree/master/spec/support/rails_app/app)\n\n### The Convention\n\nSay, for example, you have _any_ class/module defined as:\n\n```ruby\nmodule Api                    # {namespace\n  module V3                   #  namespace}\n    module UserServices       # {resource_name}{resource_type}\n      module Authentication   # {class_path\n        class OAuth2          #  class_path/file_name}\n        end\n      end\n    end\n  end\n end\n```\n\nAs long as some part of the fully qualified class name (in this example: `Api::V3::UserServices::Authentication::OAuth2`) contains any of the [defined keywords](https://github.com/NullVoxPopuli/drawers/blob/master/lib/drawers/active_support/dependency_extensions.rb#L4), the file will be found at `app/resources/api/v3/users/services/authentication/oauth2.rb`.\n\nThe pattern for this is: `app/resources/:namespace/:resource_name/:resource_type/:class_path` where:\n - `:namespace` is the namespace/parents of the `UserService`\n - `:resource_type` is a suffix that may be inferred by checking of the inclusion of the defined keywords (linked above)\n - `:resource_name` is the same module/class as what the `resource_type` is derived from, sans the `resource_type`\n - `:class_path` is the remaining namespaces and eventually the class that the target file defines.\n\nSo... what if you have a set of classes that don't fit the pattern exactly? You can leave those files where they are currently, or move them to `app/resources`, if it makes sense to do so. Feel free to open an issue / PR if you feel the list of resource types needs updating.\n\n## Usage\n\n```ruby\ngem 'drawers'\n```\n\nIncluding the gem in your gemfile enables the new structure.\n\n### A note for ActiveModelSerializers\n\nActiveModelSerializers, by default, does not consider your _controller's_ namespace when searching for searializers.\n\nTo address that problem, you'll need to add this to the serializer lookup chain\n\n```ruby\n# config/initializers/active_model_serializers.rb\nActiveModelSerializers.config.serializer_lookup_chain.unshift(\n  lambda do |resource_class, _, namespace|\n    \"#{namespace.name}::#{resource_class.name}Serializer\" if namespace\n  end\n)\n```\nNote: as of 2016-11-04, only [this branch of AMS](https://github.com/rails-api/active_model_serializers/pull/1757) supports a configurable lookup chain\n\nNote: as of 2016-11-16, the `master` (\u003e= v0.10.3) branch of AMS supports configurable lookup chain.\n\n## Migrating\n\nEach part of your app can be migrated gradually (either manually or automatically).\n\nIn order to automatically migrate resources, just run:\n\n```bash\nrake rmu:migrate_resource[Post]\n```\n\nThis will move all unnamespaced classes that contain any of the [supported resource suffixes](https://github.com/NullVoxPopuli/drawers/blob/master/lib/drawers/active_support_extensions.rb#L4) to the `app/resources/posts` directory.\n\n## Configuration\n\n```ruby\n# (Rails.root)/config/initializers/drawers.rb\nDrawers.directory = 'pods'\n```\n\nSets the folder for the new structure to be in the `app/pods` directory if you want the new structure separate from the main app files.\n\n### Co-Location of Tests / Specs\n\nThere are some mixed feelings about co-location of specs, so (by default), this monkey patch is not included.\n\n```ruby\n# config/initializers/gems/rails.rb\nmodule Rails\n  class Engine\n    # https://github.com/rails/rails/blob/5-1-stable/railties/lib/rails/engine.rb#L472-L479\n    # https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/engine.rb#L468\n    def eager_load!\n      config.eager_load_paths.each do |load_path|\n        matcher = /\\A#{Regexp.escape(load_path.to_s)}\\/(.*)\\.rb\\Z/\n        # They key is the !(spec) added to the glob pattern.\n        # You may need to modify this if your tests don't end with spec\n        Dir.glob(\"#{load_path}/**/*!(spec).rb\").sort.each do |file|\n          require_dependency file.sub(matcher, '\\1')\n        end\n      end\n    end\n  end\nend\n```\n\nYour test suite command would then need to change to include the `app` directory.\n```\nrspec app/ spec/\n```\nAnd you'll still want to use the spec folder for `spec_helper.rb`, factories, and other support things.\n\n## Contributing\n\nFeel free to open an issue, or fork and make a pull request.\n\nAll discussion is welcome :-)\n\n\n---------------\n\nThe gem name 'Drawers' was provided by @bartboy011.\nThanks @bartboy011!\n\nThe previous name of this gem was Rails Module Unification -- which, while a homage to its inspiration, Ember's Module Unification app architecture, it's quite a mouthful, and doesn't exactly make for a good gem name.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullvoxpopuli%2Fdrawers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnullvoxpopuli%2Fdrawers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullvoxpopuli%2Fdrawers/lists"}