Ruby on Rails Best Practices - Big Controller VS Small Controller

Ruby on Rails Best practices - Big Controller vs Small Controller

This is more of a long-form comment, since it explains the origin of your dilemma, but provides no solutions.

The problem actually is caused by misinterpretation of MVC, which RoR popularizes.

It is the combination of two factors, which are causing this implosion of controller:

  • On the one side you have anemic model, because, instead of real model layer, RoR uses collection of ORM instances. The reason for it is that Rails was originally created to be a framework for fast prototyping (generation of throw-away code). And prototyping is exactly what active record is best at. Using scaffolding, you can easily generate active record structures from existing databases.

    But this cause some of the domain business logic to leak in your controllers.

  • On the other side you have the non-existent view. Since the goal was prototyping, Rails favored getting rid of views, which can actually contain presentation logic, by merging them into the controllers. The, now missing, views were replace with simple templates, which are just called "views".

    This forces controllers to contain presentation logic.

These two factors would be the reason, why I am tempted to assert, that RoR is not even MVC framework. The resulting pattern is actually closer to Model-View-Presenter. Though it has been simplified to the point at which it starts to break Separation of Concerns.

What are some options for keeping controllers lean in Rails 4?

  1. Depends -- if you have to process that data in the controller, perhaps it's the right way. But as with most things, if you show us the code, we may be able to refactor. You need to be aware of modularity, which means you should split your code as much as possible, to encourage reuse

  2. Concerns are really for providing cross-controller / cross-model modularity. For example, we use friendly_id in several models; we've separated into different concerns, giving us the ability to change the concern once & update all the models

  3. You're looking for class methods


Fat Model Skinny Controller

Although not well documented, one of the core Rails patterns is to keep your controller as lean as possible. To do this, dhh recommends putting lots of your methods into your models (scopes, class methods etc) - allowing you to call them with brevity

This is a general programming pattern


Inherited Resources

One of my favourites - this creates the standard RESTful action interface in your controller, so you don't need to include it yourself:

#app/controllers/posts_controller.rb
class PostsController < InheritedResources::Base
end

This will load index, show, new, create, edit, update, destroy automatically


Callbacks

Finally, you should be aware of callbacks

These allow you to run common code with a single instance method. A good example is find:

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :load_post, only: [:show, :edit]

private
def load_post
@post = Post.find params[:id]
end
end

Ruby on Rails's best practice for main webpage's controller#action?

It depends on your application. If your application's main idea is products, then link it to products#index, if it's about cars, then route it to the cars controller. However if your homepage is going to be completely ambiguous, and just a landing page for example... make it home#index.

Fat models and skinny controllers sounds like creating God models

It might not be the best idea to look at Rails as a staple of MVC design pattern. Said framework was made with some inherent shortcomings (I kinda elaborated on it in a different post) and the community only just now has begun addressing the fallout. You could look at DataMapper2 development as the first major step.

Some theory

People giving that advice seem to be afflicted by a quite common misconception. So let me begin by clearing it up: Model, in modern MVC design pattern, is NOT a class or object. Model is a layer.

The core idea behind MVC pattern is Separation of Concerns and the first step in it is the division between presentation layer and model layers. Just like the presentation layer breaks down into controllers (instances, responsible for dealing with user input), views (instances, responsible for UI logic) and templates/layouts, so does the model layer.

The major parts that the model layer consists of are:

  • Domain Objects

    Also known as domain entities, business objects, or model objects (I dislike that latter name because it just adds to the confusion). These structures are what people usually mistakenly call "models". They are responsible for containing business rules (all the math and validation for specific unit of domain logic).

  • Storage Abstractions:

    Usually implemented using data mapper pattern (do not confuse with ORMs, which have abused this name). These instances usually are tasked with information storage-from and retrieval-into the domain objects. Each domain object can have several mappers, just like there are several forms of storage (DB, cache, session, cookies, /dev/null).

  • Services:

    Structures responsible for application logic (that is, interaction between domain objects and interaction between domain objects and storage abstractions). They should act like the "interface" through which the presentation layer interacts with the model layer. This is usually what in Rails-like code ends up in the controllers.

There are also several structures that might be in the spaces between these groups: DAOs, units of work and repositories.

Oh ... and when we talk (in context of web) about a user that interacts with MVC application, it is not a human being. The "user" is actually your web browser.

So what about deities?

Instead of having some scary and monolithic model to work with, controllers should interact with services. You pass data from user input to a specific service (for example MailService or RecognitionService). This way the controller changes the state of model layer, but it is done by using a clear API and without messing with internal structures (which would cause a leaky abstraction).

Such changes can either cause some immediate reaction, or only affect the data that the view instance requests from model layer, or both.

Each service can interact with any number (though, it's usually only a handful) of domain object and storage abstractions. For example, the RecogitionService could not care less about storage abstractions for the articles.

Closing notes

This way you get an application that can be unit-tested at any level, has low coupling (if correctly implemented) and has clearly understandable architecture.

Though, keep in mind: MVC is not meant for small applications. If you are writing a guestbook page using MVC pattern, you are doing it wrong. This pattern is meant for enforcing law and order on large scale applications.

For people who are using PHP as primary language, this post might be relevant. It's a bit longer description of the model layer with a few snippets of code.

Splitting large Rails controller without adding new models

There is no universal way would describe how to organize the code. How to split it is an individual decision.
But I can give some common recommendation. I have dealt with stuff like this and can imagine what you have there. So you might find them useful:

  • try with small changes

  • find duplicated code and extract it to methods defined in this
    controller first

  • later you can decide what to do with them, but small changes like that help you to grasp the whole picture
  • simplify code if it's possible
  • try to figure out responsibilities of the controller, specifically its actions, that's the final goal
  • once you clearly understand responsibilities of the code you can start to think how to split it
  • the split should be made based on responsibility of the code chunks
  • you can use patterns like form objects, service objects, etc.

Best practice for drying up the display of the same model data on almost every page

You could put this on ApplicationController, but honestly I recommend against that. ApplicationController tends to become a big bloated blob over time, accumulating utility functions that are really not related, definitely not SRP. It can get ugly.

What I've done to keep things DRY is to create a parent controller that related controllers can inherit from. Put your before_filter on that and have the related category-using controllers inherit from it.

Maybe:

class MainPagesController < ApplicationController
before_filter :load_categories

private

def load_categories
@categories = Category.all
end
end

class SomeController < MainPagesController
# etc.
end

If your app is small-ish, won't grow significantly over time, and you truly do load @categories on almost all of your pages, then putting it on ApplicationController might make sense. But I tend to err on the side of over-DRYing my code. Very small classes that have siloed functionality is never a bad thing.

Rails: Calling methods in a model in a different controller

Finally, I got it to work.

I needed to implement service objects using Plain Old Ruby Objects.
For each for the actions, I placed them in a separate service, and then called them in the controllers.

The steps are

  1. Create a folder under the app directory of the application named
    services.
  2. Create individual services for each of the controller actions.
  3. Call each service that you want on the controller.

That's all.



Related Topics



Leave a reply



Submit