NetWorkOut: an exercise in rails

Robert Shilcof
5 min readJan 24, 2021

Lifting rails controllers to a higher level

NetWorkOut

NetWorkOut is a web app that I developed to expand my rails coding ability while studying with the Flatiron School. I built a database of muscles and exercises, from which users can build workouts. They can also view those that others have built and star them as favourites for future reference when training.

In this post I cover five of my favourite methods that I implemented while developing this application to DRY up my controllers, and protect my routes from malicious requests.

Setting Objects

One thing you will be familiar with if you have developed web apps is assigning instance variables from ids that are being passed through as parameters.

To do this, in each of your controller actions you may have a line of code that reads:

@object = ObjectClass.find(params[:id])

where you would substitute object for the name of the model in question. For my web app these were, muscle, exercise, user, workout…

I quickly became tired of repeating this line.

While a method could be written once per controller that was called before the required actions, I wanted to take this one step further.

So, in my ApplicationController I wrote the method set_object, which looked like this:

def set_object    if params[:id]        object = _prefixes[0].singularize        instance = object.capitalize.constantize.find(params[:id])        instance_variable_set("@#{object}", instance)    endend

This utilises the ._prefixes method that can be called onto a controller to return its name. When called on the UsersController it would return users, hence the .singularize method is chained on.

With the name of the class retrieved, .constantize was used to turn the string “user” into the class “User” so that methods, specifically .find, could be called upon it.

Having found the desired object, .instance_variable_set could be used with a dynamic variable name @#{object} that would reflect the class of the object found.

Because this was wrapped in an if statement to check for the presence of params[:id], .set_object can be called before every action in the app, and nothing needs be added to any of the additional controllers.

I think you’d agree that this is a welcome reduction in time and effort.

Getting Objects

Building off the previous method to set objects in controllers, I wrote another method .get_object, again in the ApplicationController as shown:

def get_object    if params[:id]        object = _prefixes[0].singularize        instance_variable_get("@#{object}")    endend

operating off the same principals of using ._prefixes. This method could be used to get an object dynamically after it had been set by .set_object, giving valuable flexibility.

Editing Permissions

With objects set, and able to be dynamically retrieved, I wanted a method that would verify whether a user had permission to edit an object regardless of its class.

The rules I set were as follows:

  • a user can edit their own user object,
  • a user can edit any object that belongs to them, and
  • an admin can edit anything.

As a result, the .permission_to_edit? method was born.

def permission_to_edit?(object)    if object.class == User        object == current_user || admin?    else        object.try(:user_id) == current_user.id || admin?    endend

Here the .class method was required to determine the class of the object being passed as a parameter. If it isn’t a user the .try method is called on it with an argument of :user_id. This will return a user id if one is present or nil if not, this could then be compared with the id of the current user.

If the ids do not match, .permission_to_edit? will return false.

This method was useful to have in views as well as controllers. As such it was housed in the ApplicationHelper file, which was then included in the ApplicationController.

Permitted next step

With the prior method in place, its first deployment was to be used to guard actions where objects could be edited, and functions nicely with the .get_object method.

def permitted?    get_object ? permission_to_edit?(get_object) : trueend

Here it can be seen that this method it will return a true or false value based on the current_user (found from the session) and the return of get_object. If .get_object returns falsey, it will be false, else permission_to_edit? will be called.

As no classes have been explicitly specified, this flexible method can be used for any action in any controller.

Putting it all together

The .permitted? method was the missing piece of the puzzle needed for my next method .redirect_if_not_permitted.

def redirect_if_not_permitted     if !admin?        if request.method != "GET"            redirect_to_root unless logged_in? && permitted?        elsif action_name == "edit"            page_not_found unless logged_in? && permitted?         elsif action_name == "new"            page_not_found unless logged_in?         end    endend

This method would first check whether a user was an admin, and proceed with no action if they were. If not, it would first evaluate the request method.

Any request that is not a GET request implies that data information is being passed to the app. As such, this method would redirect any user making one of these requests if they were;

  • firstly, not logged in, or
  • secondly, were not permitted.

Elsif it was a GET request, the .action_name controller method was used to determine the name of the action being used to handle the request.

As the app strongly followed restful routing, controllers shared the action names “edit” and “get”.

For edit actions, the method once again checked a user was logged in, and that they had permission to edit the object in question. However, for new actions, only a logged in check was required as there would be no created object to check for permissions against.

With no specific references to any of the models in the app, .redirect_if_not_permitted could be called before every* controller action… resulting in further savings of time and effort.

*sign up and login routes did have to be omitted

Session over!

I hope this post has helped you by introducing you to a number of new controller methods that I have found extremely satisfying, and that you could adapt to use in your own rails projects.

Do let me know how you get on implementing similar methods, I would love to find even more new ways to abstract my own methods to reduce repetition!

If you would like any further insights into any of the topics covered feel free to get in touch with me, also I have added a link below to a walk through of NetWorkOut if you want to see some of these methods in action.

--

--