Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Group related classes together. No more silos.

Gem Version Build Status Code Climate Test Coverage Dependency Status

What is this about?

With 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.

This gem provides a way to re-structure your app so that like-objects are grouped together.

All this gem does is add some new autoloading / path resolution logic. This gem does not provide any service/operation/policy/etc functionality.

All of this is optional, and can be slowly migrated to over time. Adding this gem does not force you to change your app.

The new structure

├── channels/
├── models/
   ├── data/
      ├── post.rb
      └── comment.rb
   └── graph_data.rb
├── jobs/
├── mailers/
   └── notification_mailer.rb
└── resources/
    ├── posts/
       ├── forms/
          └── new_post_form.rb
       ├── controller.rb  # or posts_controller.rb
       ├── operations.rb  # or post_operations.rb
       ├── policy.rb      # or post_policy.rb
       └── serializer.rb  # or post_serializer.rb
    └── comments/
        ├── controller.rb
        ├── serializer.rb
        └── views/
            ├── index.html.erb
            └── create.html.erb

Does 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 < ApplicationController

Checkout the sample rails app in the tests directory.

The Convention

Say, for example, you have any class/module defined as:

module Api                    # {namespace
  module V3                   #  namespace}
    module UserServices       # {resource_name}{resource_type}
      module Authentication   # {class_path
        class OAuth2          #  class_path/file_name}

As long as some part of the fully qualified class name (in this example: Api::V3::UserServices::Authentication::OAuth2) contains any of the defined keywords, the file will be found at app/resources/api/v3/users/services/authentication/oauth2.rb.

The pattern for this is: app/resources/:namespace/:resource_name/:resource_type/:class_path where:

  • :namespace is the namespace/parents of the UserService
  • :resource_type is a suffix that may be inferred by checking of the inclusion of the defined keywords (linked above)
  • :resource_name is the same module/class as what the resource_type is derived from, sans the resource_type
  • :class_path is the remaining namespaces and eventually the class that the target file defines.

So... 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.


gem 'drawers'

Including the gem in your gemfile enables the new structure.

A note for ActiveModelSerializers

ActiveModelSerializers, by default, does not consider your controller's namespace when searching for searializers.

To address that problem, you'll need to add this to the serializer lookup chain

# config/initializers/active_model_serializers.rb
  lambda do |resource_class, _, namespace|
    "#{}::#{}Serializer" if namespace

Note: as of 2016-11-04, only this branch of AMS supports a configurable lookup chain

Note: as of 2016-11-16, the master (>= v0.10.3) branch of AMS supports configurable lookup chain.


Each part of your app can be migrated gradually (either manually or automatically).

In order to automatically migrate resources, just run:

rake rmu:migrate_resource[Post]

This will move all unnamespaced classes that contain any of the supported resource suffixes to the app/resources/posts directory.


# (Rails.root)/config/initializers/drawers.rb = 'pods'

Sets 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.

Co-Location of Tests / Specs

There are some mixed feelings about co-location of specs, so (by default), this monkey patch is not included.

# config/initializers/gems/rails.rb
module Rails
  class Engine
    def eager_load!
      config.eager_load_paths.each do |load_path|
        matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
        # They key is the !(spec) added to the glob pattern.
        # You may need to modify this if your tests don't end with spec
        Dir.glob("#{load_path}/**/*!(spec).rb").sort.each do |file|
          require_dependency file.sub(matcher, '\1')

Your test suite command would then need to change to include the app directory.

rspec app/ spec/

And you'll still want to use the spec folder for spec_helper.rb, factories, and other support things.


Feel free to open an issue, or fork and make a pull request.

All discussion is welcome :-)

The gem name 'Drawers' was provided by @bartboy011. Thanks @bartboy011!

The 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.


Group related classes together. No more silos. A solution to rails dystopia.




Code of conduct


No packages published