How to Use Decent Exposure Gem in Your App
In Rails, the default way of passing objects into views is to set instance variables in the controllers. This is often confusing and breaks encapsulation (instance variables are meant to be private for the object). In addition, a number of variables are set in the common ways, increasing the boilerplate code (e.g. find a record by its id, find a collection scoped to the current user).
Decent_exposure is a small utility gem to improve exposing objects in controllers to views.
Pros
-
Removes the need for instance variables in controllers, improving encapsulation.
-
Makes the variables used in views clearly visible at the top of the controller.
-
Compatible with most common use-cases, e.g. strong_parameters, decorators.
-
Exposes code smells, e.g. controllers with multiple responsibilities (pun not intended).
Cons
-
Lazy-loading of exposed variables may cause reasoning about complex controller flow more difficult (e.g. when exactly is the model fetched from the database?)
Step 1 - Installation
Installing is as simple as adding gem ‘decent_exposure’ to the Gemfile and running bundle install.
Step 2 - Adding a New Controller
In its basic form, decent_exposure provides an expose macro to be used in rails controllers. Based on the name and request params, it will automatically fetch the existing or instantiate the new models. This is what a controller using decent_exposure looks like:
[code]
decent_exposure took care of:
-
instantiating new post object in the new and the edited actions (they are not needed to be defined explicitly anymore);
-
assigning attributes from params in create and update actions (we just have to call save);
-
finding correct post by id (Post.find(params[:id])) for show action and listing posts for index action (again, no need to define these actions explicitly);
-
adding “post” and “posts” methods available both in this controller and the views.
The variables defined by expose are memorised - meaning you can reference ‘post’ variable many times but it will be assigned only once.
Step 3 - Converting Existing Controllers
How to refactor the existing controller with decent_exposure:
-
Make sure there are tests in place to cover the controller’s functionality.
-
Add expose macros at the top of the controller definition.
-
Remove before_actions setting instance variables.
-
Replace all references to instance variables by methods (without @) in controller and views.
-
Modify controller tests using assigns:
assigns(:post) → controller.post
Step 4 - Advanced Features
To scope finders to particular context, e.g. get current_user.posts.find() rather than Post.find(), use ancestor option:
expose(:post, ancestor: :current_user)
To expose objects with nonstandard fetching methods or scoped collections, use the block passed to expose:
expose(:post) { Post.find_by_slug(params[:id]) }
expose(:posts) { Post.recent }
To expose paginated collections, use block with a default argument:
expose(:posts) { |default| default.page(params[:page]).per(params[:per]) }
Tips
Tip 1: Usage with Strong_parameters
If you’re on Rails 4, you probably use strong_parameters. It was added as a default way to prevent accidentally updating sensitive model attributes (i.e. admin field). It requires all the params to be whitelisted - read more about it here.
Decent exposure has built-in support for passing strong parameters: see an example in the documentation.
Tip 2: Long Code in Expose Block
Sometimes expose block contains a lot of code, like in this example:
[code]
In this situation, it is nice to extract the code to a private method, keeping the header of the controller slim:
[code]
Tip 3: Dealing with Overexposure
The ideal number of exposures is 2 per controller (one for singular resource, e.g. post, one for plural collection, e.g. posts). If the top of the controller class is crowded with expose statements, two code smells might occur:
-
Exposing variables not used in view.
When the variable is used not in view but in controller, you should extract the private method instead:[code]
This way, the details regarding controller implementation won't leak on the outside, and it is clearer which variables are used by views. -
Controller with multiple responsibilities
Numerous view exposures in a single controller is often an indication that the controller in question is too big or handles too many actions. Look for the opportunities to extract parts of functionality to separate controllers.
Tip 4: Usage with Decorators
Decorators comprise a great pattern for managing the presentational code of your models. For the tutorial on implementing decorator classes, see draper gem.
You can utilise expose with block feature to decorate the object:
expose(:post) { |default| PostDecorator.new(default) }
Or instead, you can take a look at decent_decoration gem which streamlines the process.