Best Practices For Reusing Code Between Controllers in Ruby on Rails

Best Practices for reusing code between controllers in Ruby on Rails

In my opinion, normal OO design principles apply:

  • If the code is really a set of utilities that doesn't need access to object state, I would consider putting it in a module to be called separately. For instance, if the code is all mapping utilities, create a module Maps, and access the methods like: Maps::driving_directions.
  • If the code needs state and is used or could be used in every controller, put the code in ApplicationController.
  • If the code needs state and is used in a subset of all controllers that are closely and logically related (i.e. all about maps) then create a base class (class MapController < ApplicationController) and put the shared code there.
  • If the code needs state and is used in a subset of all controllers that are not very closely related, put it in a module and include it in necessary controllers.

In your case, the methods need state (params), so the choice depends on the logical relationship between the controllers that need it.
In addition:

Also:

  • Use partials when possible for repeated code and either place in a common 'partials' directory or include via a specific path.
  • Stick to a RESTful approach when possible (for methods) and if you find yourself creating a lot of non-RESTful methods consider extracting them to their own controller.

Reusing code ruby on rails

To get it working you can either mix the module into your class:

require 'search'

class User < ActiveRecord::Base
include Search::Score

def get_user_score
p get_score # => "something"
end
end

Or you can define the method inside your module similar to class methods:

module Search
module Score
def self.get_score
return 'something'
end
end
end

If you do that, you can call get_score like expected:

require 'search'

class User < ActiveRecord::Base
def get_user_score
p Search::Score.get_score # => "something"
end
end

See this tutorial for a more in depth explanation about modules in Ruby.

What's the best practices in code reuse between different Ruby projects?

Besides this, ruby's gems is one of the best way of reusing common parts of code. Gems have names, version numbers, and descriptions and therefore you can easily maintain up-to-date versions of these libraries, install and uninstall, manage your computer’s local installations of gems using the gem command, available from the command line. Gems became standard with Ruby 1.9, but before you have to use require 'rubygems' line in the scripts. There are several tools that help create them, for example, bundler. Bundler not only tool for gem creation, but an awesome application's dependencies manager.

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

Rails re-use: why are controllers not reused within partials?

No, partials don't call the controller. You instantiate the objects you need in the controller. So if you want to render an address partial you'd get the address in the controller.

If you need all 3 on all pages you could create controller concerns which have a before action on them

module GetStuff
extend ActiveSupport::Concern

included do
before_action :set_user
before_action :set_address
end

def set_user
@user = # code for getting user
end
end

class UsersController
include GetStuff
end

Or assuming you inherit from application controller you could simply set a before action for them there (but if you have other controllers inheriting from it that's probably not the best idea.

If you use them frequently you could put methods in application controller and set helper methods to allow the methods to be accessible from views as well as controllers.

class ApplicationController
def current_user
@current_user = #code to get user
end
helper_method :current_user
end

# Some view
<% if current_user %>
<%= current_user.username %>
<% end %>

Or you could have methods in helpers

module ApplicationHelper
def current_user
@current_user = #code to get user
end
end

Or presumably address is linked to user too you could create a facade and simply instantiate that in your controllers

class UserController
def show
@user_facade = UserFacade.new(User.find(params[:id])
end
end

class UserFacade
attr_reader :user

def initialize(user)
@user = user
end

def address
@address ||= user.address
end

def user_history
@user_history ||= user.history
end
end

# users/show.html.erb

<%= render @user_facade.user %>
<%= render @user_facade.address %>
<%= render @user_facade.user_history %>

Rails call same action code from two controllers (with a small difference)

I would add a middleware in the referral route and then call the HomeController#index action. So everything would be done at the routing level.

Don't fall into the trap of sacrificing clarity for cleverness. This can be very unintuitive and frustrating for people (including yourself) coming onto your project at a later stage. This approach also becomes subject to some arbitrary decisions like which controller action gets routed to which?

The best code is the simplest solution that still clearly communicates your intentions to a reader. For this reason, I prefer to use plain old Ruby objects where possible. You can extract the logic into a simple query object, for example:

# app/queries/home_query.rb
class HomeQuery
attr_reader :passion, :workshop, :regions, :categories

def initialize
@passion = Passion.new
@workshop = Workshop.new
@regions = Region.where("workshops_count > 0").order("name ASC")
@categories = Category.where("passions_count > 0 AND parent_id IS NULL").order("name ASC")
end
end

Then use it to pass values from your controller action:

# app/controllers/home_controller.rb
def index
@stuff = HomeQuery.new
end

And in your view, you can use @stuff.passion, @stuff.workshop, etc. to access your stuff.

It is DRY, its intention is clear, and it uses common Ruby constructs that most people are familiar with.

refactoring Rails 4 before_action for code speed with reused controller methods

You asked two different questions here. A good way to abstract methods so they are resuable would be in the form of a Module or a superclass which your subclass inherits behavior from. Think of a rails model inheriting ActiveRecord::Base and how it inherits database access methods etc.

You are probably saying that you defined your before action methods in application_controller which is fine, but limiting them for use in certain controllers won't necessarily improve performance. If they are heavy and you only want them to be instantiate on certain controllers, modules would be a better way to go, but they would have to be sufficiently large and complex to gain performance in controllers that to not include them.

How to share code between a job and a controller

Both concerns and helpers are just modules. And thus can both be used for parallel inheritance.

Helpers have a specific role in Rails which is code-sharing between the view and controller, they are also automatically included into every controller which makes them a bad choice in this case. Helpers are the sweaty armpits of a Rails app and are best used for HTML generation or other view concerns.

"concerns" is really just a Rails convention which adds the folders app/controller/concerns and app/models/concerns to the auto-loading root directories. Unlike helpers they are not automatically included in your controller. If you want to use module mixins this would probably be a good place.

If the functionality is a simple set of methods this could be a good choice.
There are a handful of great uses of modules like Enumerable that really provide a set of behaviors to a wide array of classes but most modules tend to be proverbial junk drawers.

Splitting things up doesn’t make things easier — it only means I can
pretend that my files are small and that my main module doesn’t
introduce that many methods. Even though I wrote the code, I’m
constantly confused about which file holds what method. If anything,
splitting out files in this way makes my job as a maintainer harder.

- Scheems

Sebastian Palma touched on one solution to this problem - Service Objects. Jobs are actually just a more advanced type of service object but you can compose a service object that encapsulates the functionality shared between the controller and job if the functionality warrants it.

public class methods in the job class, and call these from the
controller?

Hell no. Jobs should just do one job and do it well. Create more jobs/service objects if needed. Don't turn a good single purpose object into a junk drawer.

Summary

What kind of objects you create matters just as much as where you place them. There is no quick and easy answer - you need to look carefully at what the code does:

  • Is it a generic behavior that can augment multiple classes? Module mixin and / or macro pattern.
  • Does it take some sort of input and do a unit of work? Service object/job.
  • Does it do HTTP calls or in some other way touch the application boundary? Client object.
  • Does it encapsulate some sort of business logic with rules that act upon it? Model/virtual model.


Related Topics



Leave a reply



Submit