Back to Home

What I've learn about Rails so far II

Sept 26 2016

It's been 9 months since I worked as a professional rails developer. What have I learned?

In a big picture, what matters the most is...

Instance Variable (@article) vs Calling method

When you working on rails app, and need to create API that contains customized information from database, I was told that make instance variables like @articles, @reviews are the best way. Here is example from my Favorite Food Truck app

def index @review = Review.new @foods = Food.all @foods_page = Food.paginate(:page => params[:page], :per_page => 15) end end

There is nothing wrong with above code. @review is new instance of Review class, @foods represents all Food instance etc. This is definitely how I learned to do.

Compaire with this controller.

def index investors_only! return if performed? end

I guarantee you there are lots of stuff going on index.html.erb file. There are several model rendering, display user information, startup accounts etc. but where are the methods?

They are mostly in the view. Technically speaking, it is in model. Instead of making instance variable, each time when we need information, just call the method and get information from database. Here is the example

# event.rb def upcoming_events other_events.select{ |e| e.time > Time.now}.sort_by { |k,v| k.time } end # event.html.erb <% deal.upcoming_events.each do |event| %> <%= event.name %> <%= event.date %> <% end %>

So Why this is better than having @other_events in controller? First, In case there is an error on the instance method, let's say we called @events, which returns all events and render 1) event.name and 2) event.date. What happen if one of event doesn't have a date? As soon as you load the event.html.erb, it will throw an error. You don't want that. If there is going to be an issue, you want to have it as smallest scale as possible.

Second, functions can be used everywhere but instance variable in the controller isn't. You need to keep specify what @events are in the each controller.

Last, it is much easier to test functions in the model than controller.

Thus, the argument of fat model vs thin controller.

Fat Model and Thin Controller

Controller is the happening place. If route.rb is the map of the app, the controller is the agency to hook up with actions and views. It dictates what to do - like CRUD, and what will render. The reason I am a big fan of thin controller is that it will make easier to figure out what is going on or what is wrong. Because as the app grow, it will have more complicated routes and actions. So I like to have all functions in the model and keep controller small.

DRYer code - Another benefit of moving functions to the model is recyclability of your code(functions). When you made functions in the model, you can use everywhere as long as you can call it via its instance (or class).

Rails Helper Functions

Concerns

One fine afternoon, I've landed in a task to check all the urls in every model and put 'http://' if it is external link. For instance, our startup model has attribute - company_url, document model has video_url, user has facebook_url, linkedin_url etc.

So whenever new instance of these class created, check the external url related attribute and put http:// if there is non (e.g. www.youtube.com).

I had pretty good idea how to check url related attribute and change it but the question is Where am I gonna put this function??

First, my reaction is 'helper' folder - more exact, application_helper.rb. The great thing about application_help is that these functions are available cross the stack. You can call it anywhere! But how can I check the class attributes with functions?

You may have noticed that Rails 4 creates concerns file under 'controller' and 'model'. What is concern and how we can use it?

Here comes 'concerns'

Concerns are essentially modules that allow you to encapsulate model roles into separate files to DRY up your code.

Here you can use concern in the model

class Deal < ActiveRecord::Base include UrlFilter before_save :url_convertor end

This callback(before_save) will run before any instance saved and call url_convertor. This magic is all posible due to ActiveSupport::Concern

If you are wondering why I used self.send(url), please read this.

Services Object

So far I've reasoned why we need to maintain controller skinny. But how? We've already moved all object related methods to the model and we've used ActiveSupport::Concerns to even dry up more for common methods like changing external url address starting to http://. Can we do more?

Happens to be, our app does use lots of mailers. We triggers mail when a user register our website, request to change password, get more points, new deals opened etc. So each controller action, for instance, when we create new 'deal', it triggers "UserMailer:new_deal_announcement". The mailer is different and the email contents are different but put all these details in the controller seem little unfair. Well then, where should I move to?

Service Object implements the user’s interactions with the application

The common way of using service object is 3 stages:

  • Initialization
  • User Input
  • Return Results
  • You might suspect that services will be used on the boundary between user interface and application - and you’d be right! In the context of Rails, this boundary is the controller. An application using services would instantiate them in controller actions, tell them to perform work and respond back to the user. Let’s see an example:

    def publish this_deal.publish! Services::ApprovePublishing.new(this_deal).call redirect_to :back end

    So, we see how we call it from the controller, so what this service object look like:

    The service object's action contains one thing and one thing only. Triggers mailer. But now that we have removed this from the controller, we can keep our controller skinny and focus on what they need to do - publish and redirect. The rest of them will be taken cared by service object. This also help to DRY your code in case you need to send this email from other controller action.

    If you want to read more about Service object, check this article.