Moving Logic from the View to the Model in Rails

This entry aims to shed some light on something I was struggling to get my head around, which now makes a lot more sense since working on a certain task (demonstrated later in this post). The source of this struggle was not knowing how to get information from one part of the Rails application to be usable in another part of the application. Basically the sin I was committing was that I kept writing my logic in the view as opposed to the model (if you don't know what a view or model is please scroll down to my 2nd blog entry entitled “What is MVC?”). I wasn't sure how to move the logic I had written in the view into the model so that the thing still worked.

Why is writing logic in the view bad practice?

Well partly because when you program you don't want to have to write out the same logic multiple times. Instead you could just write a method and call that or perhaps store some information in a variable and use that variable multiple times. Tidying up code in this way is what is known as 'refactoring'. It makes the code look neat and tidy rather than a spaghetti mess with loads of repetition of long lines of logic, but it should still function in the same way (output the same thing as the spaghetti version). This then means that the program runs more efficiently.

The key reason that this is practical is that if we wanted to make a change to the logic, we would just need to change it once in the model, rather than having to change it 5 (or however many times) in the view, which helps make everything more solid (because you might accidentally miss changing something in the view), saves you time and also abolishes the tedious task of having to manually change each value. It also means we can call that method in any other part of the application, whereas we wouldn't have that luxury if the logic was all placed in the view.

Which parts of a Rails application can you call from other parts? And how?? How is the information sent around?

In Rails, we define a class in the model. Each model has a corresponding view and controller.

So to put in place an MVC set up for the invitation part of my application (used to send invitations inviting people to workshops) I would need to name the files containing my code in a certain way.

The model would be:

invitation.rb (saved in: app/models/invitation.rb)

At the top of the file I would need to include this:

Snip20140620_3

Why? Well the model is a subclass of ActiveRecord. We need Active Record because this lets us interact with the database to do stuff within the application.

The controller: invitations_controller.rb (saved in: app/controllers/admin/invitations_controller.rb)

The reason it is saved in controllers/admin/invitations instead of plain controllers/invitation is because we want the functionality to be included within the admin namespace. Therefore it will be viewed by navigating to:

http://localhost:3000/admin/invitations

as opposed to

http://localhost:3000/invitations

At the top of the controller I need to include this:

Snip20140620_6

The controller inherits from something called ApplicationController. This is basically a controller that all the other controllers in the application inherit from, which means all controllers can use methods specified in there. This is where you would usually write methods to be used as filters, because they are usually needed by multiple controllers and it saves us re-writing the method out in each controller.

ApplicationController inherits from ActionController::Base. Controllers are used to dictate which action will be performed depending on the web request received.

The actions are specified as methods (the names are kind of used interchangeably) in the controller. To get the action to actually happen (for example if you wanted the data to actually go to a database and the page refresh when a user submits a form) you would need to write that functionality in a method that relates to a request (aka create... for a post request) in that controller and route it in routes.rb.

So in my invitations_controller.rb I have:

Snip20140620_7

Routed like so: (the :create on the last line before the end keyword!):

Snip20140620_9

Actions in the controller that route as GET HTTP requests (To see which requests relate to which actions or scroll down to my previous blog post 'Routes in Rails' explained) will get a view and display it.

To use data in the view supplied by the controller we need to use instance variables within the controller action relating to the view.

So if I wanted a view to display when a user visited:

http://localhost:3000/admin/invitations/

(This view code would be written in a file called index.html.haml saved in apps/views/admin/invitations)

and needed make use of some functionality specified in the controller I would place an instance variable in the index method of invitations_controller.rb:

Snip20140620_15

Variable Scope

What is an instance variable and why do we need this and not another type of variable? Well in Ruby different variables have different 'scope'. Scope basically defines which parts of the application can use a certain variable... how widely accessible it is.

If I just used local variables (without the @ sign!) like so:

Snip20140627_4

then the stuff stored inside the invitation variable could not be used outside of that index method. We could NOT use it in the view. For GET requests we don't really need to write any logic because the purpose is to get something and display it, therefore we should pretty much only need to pass information into the view using instance variables for GET actions.

Instance variables can be shared amongst specific instances of an object and start with an @ sign so are easy to recognise. Up until now i've gotten by just knowing a variable needs to be an instance variable if I want to use it in the view, but haven't really known why this works... I googled around for the answer and this seems like the answer explained better than I could explain:

https://stackoverflow.com/questions/18855178/how-are-rails-instance-variables-passed-to-views

Why not just define the instance variables to pass into the view in the model?

Well the controller is supposed to serve as the bridge between the view and the model and the model shouldn't know anything about the controllers.

We want to use certain information which is stored in the instance variables for certain requests. In this example I want to store all of the invitations (all records in the Invitation database) in the @invitation variable when the admin/invitations/ page is requested. Therefore defining the instance variable in the controller methods makes it a lot easier to figure out what is going on, otherwise there would be a tonne of instance variables in the model, all with probably quite similar names and it would be very difficult to keep track of where they were all going to be used... it would be horrible and confusing hence why we don't do it.

Quick recap!

So at this point we know that in a Rails MVC set up the model lets us do stuff with the database (create, read, update, delete), the controller deals with the requests coming in from the user and if the user requests a view (a webpage) then that will display. Methods defined in the model can be called on the class from any controller, view or model in the application.

The methods in the controller are generally mapped to CRUD actions. If we had a request that was anything other than a GET request then whatever we wanted to happen as a result of that would be defined within that action just using standard logic, local variables and constructs. Even so if you are writing something that could be in anyway generic that could be reused, try and write it in the model and keep the controller fairly sparse.

If the action relates to a GET request we need to display a view. To get information from the model into the view as a specific result of the user making that request, we would want to use instance variables in the controller so that information can be used in the view.

Wait you just said I could call the model from anywhere... why do I need to even bother with the controller? Why write @invitation = Invitation.all in the controller action when I could just write Invitation.all in the view?

This is because logic needs to stay separate from the view and that information being passed into the view is generated when a certain request is made, so it makes sense to put it in the controller.

Demonstration of how I moved logic from the view to the model

So to demonstrate the main focus of this post (moving logic from the view to the model) i'll use an example of some code that I wrote, and re-wrote and re-wrote again the other week.

My task was to make this page:

Snip20140627_6

(sidenote! ... That wireframe was created using balsamiq. I have found these really useful to have in explantations of tasks that I need to carry out.)

So originally I got it to work and display everything to look how it does in the diagram like so:

https://gist.github.com/Rosa-Fox/f9afba2cf4bf6cc7a5b4

Even though it works it is an example of badly written code because the logic is being set in the view instead of the controller.

As we can see in that gist linked above, to get the stuff to display in the first table shown on the wireframe (sent invitations that have yet to expire or be redeemed) I wrote this:

Snip20140620_19

It looks quite long and messy. Another flaw is that to show when something is expired I look at the time the invitation was created at (by checking the 'created_at' column in the invitations database table) to see if that was more than 2 weeks ago, if so then it has expired. So if we wanted to change the expiration date to say 1 week instead of 2 weeks, already I would have to manually change the number 2 to a number 1 twice in that little section of code... and more times throughout the whole view, which is going to be a lot of effort.

To sort this out I needed to write a couple of methods in the view, redeemed? and expired? Originally the thing I was struggling to understand was how to get each iteration of invitation to be usable in the methods in my model. The solution actually turned out to be quite simple. The methods just needed the enumerator |invitation| in the view to be passed in as an argument like so:

https://gist.github.com/Rosa-Fox/b36839798c01a7cef9d2

Snip20140621_20

The methods in the model looked like this:

Snip20140627_8

This was a step in the right direction, and it shows it is actually really easy to just call the methods and pass in the |invitation| as an argument. It does fix the problem of having to manually change each number 2 if the expiry date were to change but it could still be refactored further.

To tidy this up even more my boss suggested to me that I didn't need to pass an object into a method called on itself to have access to it from within the method, when I could kill two birds with one stone through using the self keyword. I had read about using self before but it wasn't until now that it made a lot of sense... so hopefully it will for you as well:

Here are the updated methods using the self keyword:

Snip20140627_9

Snip20140627_10

So to compare:

BEFORE:

Snip20140627_11

AFTER:

Snip20140627_9

By using self I didn't need to specify any parameters. If there are no parameters specified then the method can just be called on itself. I then needed to use the self keyword to call created_at on that object as opposed to the passed in invitation argument.

So to me, after doing that suddenly using the self keyword seemed really clear and simple. Though to make things a bit more complicated, it is important to know that self can be a different object depending on the context. I'm going to cheat and copy this from the documentation (http://ruby-doc.org/docs/keywords/1.9/Object.html):

  • In a method, the object on which the method was called is self
  • In a class or module definition (but outside of any method definition contained therein),self is the class or module object being defined.
  • In a code block associated with a call to class_eval (aka module_eval), self is the class (or module) on which the method was called.
  • In a block associated with a call to instance_eval or instance_exec, self is the object on which the method was called.

So finally, just to tidy things up and to follow better Ruby practice I negated the condition using ! Instead of == false and used && instead of the and keyword. This is because and has the loosest operator precedence so in Ruby it is more often used for control (aka do, and) rather than for conditionals.

This meant that in my view, instead of having the logic specified there like so:

Snip20140627_12

I now have this in my view:

Snip20140627_13

Model (not whole thing!... just relevant methods for this demonstration):

Snip20140627_14

View: https://gist.github.com/Rosa-Fox/7623ad656085aee45245

I hope that this post helped give an explanation as to how different parts of the MVC get and make use of information. I also described in basic detail how to use them to work together to make use of this information. I also discussed reasons why it is bad practice to put logic in the view and why it is best to try and keep the controllers light. Finally through demonstrating using a task that I worked on, I showed how it actually turned out to be quite easy to move logic from the view to the model by writing a few methods and utilising the key word self.

Please let me know if you have any comments/questions.