Super v0.0.16 – Removing Controls, a retrospective

This latest release of Super, an admin framework for Rails, removes the Super::Controls class altogether. Controls used to be where most of Super's customizations lived. I moved those methods into the controller itself.

# before
class Admin::PostsController < AdminController
  class Controls < AdminControls
    def model
      Post
    end
  end
end

# after
class Admin::PostsController < AdminController
  private
  def model
    Post
  end
end

Super's Controls class was inspired by Administrate's Dashboard. In your controller's dashboard, you could specify how to display and edit records (which fields to show, and what type the column was). Administrate, I assume, would instantiate the controller's respective dashboard and call various methods on it.

I liked this idea in theory. The controller would "do" everything while the dashboard would "define" everything. It would force an interface between configuration and implementation, and it would be possible to replace a dashboard with anything that had the same methods as a dashboard.

But there were also a couple parts about Administrate's implementation that I didn't like as much. First was its reliance on constants. Constants have their place, but one shortcoming in Ruby is that they are generally set at startup—on class definition—and not at runtime. This makes it hard, for example, to allow junior employees to edit a subset of columns while allowing senior employees to edit all columns (but model-level ACL seems to be possible in Administrate). Second was a personal gripe, that the dashboard was in a separate file. I find that switching between files is time consuming.

For Super's Controls, I took all of the benefits I saw with Administrate while improving on the "shortcomings". It only used methods so it was as flexible as Ruby is; and it was embedded within the controller which removed the need to switch between as many files.

As I started using Super in more apps, I realized that this interface, this delineation between "do" and "define" added a stress point. For example, params and Rails' route helpers were available in the controller, but not in the controls that were in the same file. There was no reason that the line needed to exist, there were only reasons that the line should "technically exist".

And so I removed it. Controls and controllers are now one. A layer of abstraction, a sharp edge, a non-Rails concept—gone. I think any Rails developer will find it much more familiar and pleasant to use.

Working on Super has been showing me how "good software development" doesn't necessarily lead to "good developer experience". Early on, I avoided inheritance since inheritance is the tightest form of coupling. Then I came across some shortcomings and almost re-implemented the super keyword, but that immediately made me realize that inheritance was definitely the correct solution to my problem.

Maybe there's a tension between developer experience and project maintainability. Keeping the public API small is a helpful tip since there's less chance that the maintainer would breaking backwards compatibility, but an object that has more methods might have a better chance at doing useful things.

When in Rome Ruby, when in Rails, use inheritance, use view helpers and concerns, use callbacks, have a large interface, avoid monads, write DSLs, and raise exceptions. In moderation. I'll be sure to push Super along in that direction.

Posted on 2021-05-15 09:58 PM -0400
Contact
hello(at)zachahn(dot)com
© Copyright 2008–2021 Zach Ahn