I'm a Floridian web engineer who planted his roots in Paris. Some of my favorite past-times are analyzing and optimizing development processes, building solid APIs, mentoring junior devs, and long walks on the beach.

Internationalize your rails app 08-19-2018

I18n - Expected usage after setup

If everything has been properly put into place, then we expect to be able to simply use the t method in our views and have them render in different languages:

<h1><%= t('some.string.value') %></h1>

/// Should render:
<h1>My Value</h1>
<h1>Mi Valor</h1>
<h1>Ma Valeur</h1>

Stateless requests

If we don't know too much about the inner workings a few of the first roadblocks will be dealing with moving between requests. How to we store the locale between requests? How can we store anything between requests?

Cookies

I've actually used this for a simple wordpress site I had. The user carries the cookie with them, so once they choose their locale, it stays with them. It is dirt simple to set up, but there are a few drawbacks.

  1. You can't share locales when sharing a link, since the locale is attached to a user, if you have a spanish user sharing a link, the locale will change back to the default when the link is followed by another user. This is because the locale is tied to the user's browser.
  2. Signing out loses the user's locale setting or at least complicates keeping it
  3. Storing the locale in a cookie will make it harder to reason about. This seems like a minor thing, but when bugs come up dealing with a locale, you will be happier having it in directly in the URL.

URLs

Storing your locale information in the URL is probably the most straight forward. It takes a little more plumbing to implement because since it will be stateless, you'll have to ensure that every link contains the information.

However, it is explicit as it is visible in the URL. It is shareable and debuggable for the same reason.

Where to put my locale

Rails has a few things to say about this but essentially you have a few different possibilities for keeping the locale in the URL.

  1. Subdomains(http://fr.wollydo.com/machins) - can be beneficial if you want to think of each locale as a different site, and it is more cost effective than a TLD
  2. Top Level domains(http://wollydo.fr/machins) - are another way to designate locales as different sites however, you need to buy each domain.
  3. URL parameter(http://wollydo.fr/machins?locale=nl) - a little planned way to keep the locale in the url.
  4. URL path(http://wollydo.fr/fr/machins) - this treats locales as the most important identifier of the site, by putting it first in the path. It is a much more permenant decision than a simple parameter because you have modified your path structure.

I am going to assume that you think that the last option is the best. The rails guide actually has quite a bit of helpful information if you want to look into the other locale options.

Life of a rails request

Here is the life of a rails request for an app that has a machins resource.

Simplified life of a rails request

With this simplified view of the path of a request, we can see that to handle communicating the locale by URLs, we will have to generate a URL that contains the locale, resolve the locale in the routes, set the locale, and call the i18n.t method in our views. Another thing which is not immediately visible by the request path alone is that we will want our user to be able to select a locale.

Generate the locale

The following creates a select box to generate our locale. It uses a method that we will create later called url_for_locale which will modify the rails url_for to reload the current page while passing a new locale.


<label><%= t('change_locale') %></label>
<select class='language_switcher'>
  <% I18n.available_locales.each do |locale| %>
    <option
      value='<%= url_for_locale(locale) %>'
      selected='<%= locale == I18n.locale %>'>
      <%= locale %>
    </option>
  <% end %>
</select>

# We use a simple jQuery method to reload the page on click of the locale in the select box.
$('select.language_switcher')
  .change(function(element){
    window.location.href = $(this).val();
});

As mentioned earlier, Rails comes with a method called url_for we are going to wrap this in our new method url_for_locale to redirect to the current page with the new locale.

Scope incoming locale

Your route file is where we are going to scope our locale. If I think about the solution that I really want, it is one where my site has all of the normal URLs that go to the default locale and then any available secondary locales will be visible in the URL.

http://sillypants.com/machin      # default
http://sillypants.com/fr/machin   # french

This seems like it would be 2x the work in the routes file as you would have to re-define the routes but there is really a simple solution. You define your default routes in a method and then use call it directly in the routes file

# route definitions in routes.rb
def site_endpoints
  namespace :admin do
  resources :posts
  resources :images
end

  resources :posts, only: [:index]
end

scope '/(:locale)', constraints: AvailableLocalesConstraint.new do
  site_endpoints
end
site_endpoints

You may have noticed that I am instantiating a classAvailableLocalesConstraint. This is a standard way to scope more complex matching rules for scopes. I don't often need to use something this powerful but here I find it useful to have this level of expression. I am using this because I need something to only match if there is a locale in the params and it is one of the locales I have translations for, otherwise the constraint will fail and the routes file will match further down.

class AvailableLocalesConstraint
  def matches?(request)
    return unless request.params[:locale]

    locale = request.params[:locale].to_sym
    I18n.available_locales.include? locale
  end
end

Set locale based on the params

Now we know that whatever is in the params[:locale] should be a valid locale. Technically, it could also be nil. In order to set i18n.locale we can set a few things in the ApplicationController.

You'll notice that I have also overridden default_url_options, this allows me to reset the locale in all the path_for, url_for calls so that I don't have to specify the locale in things like form submits and any links in the page, either the user has specified a change or it will keep the locale automatically.

before_action :set_locale

def set_locale
  I18n.locale == params[:locale] || I18n.default_locale
end

def default_url_options(options={})
  { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
end

Tags: i18n Rails

Control your points of modification 10-22-2017

I was reviewing a controller the other day and I came across a piece of code in rails that modified incoming params in multiple spots. What follows is a contrived example of a solution implemented to handle a date that had to have a different format on the front-end and so had to be parsed according to the user's chosen date format.


class ShoesController < AdminController

    def update
        parse_and_format_date
        if shoe.update(shoe_params)
            redirect_to shoe_show_path
        else
            redirect_to shoe_edit_path
        end        
    end

    def shoe_params
        params.require(:shoe).permit(:buy_date, :title, :id)
    end

    def parse_and_format_date
        params[:shoe][:buy_date] = DateTime.strptime(params[:shoe][:buy_date], user.date_format)
    end
end

The problem I had with this controller is that we are modifying the params in multiple places, first in parse_and_format_dateand then in shoe_params. Also, we aren't particularly clear about it. Without looking into parse_and_format_date it is not immediately clear that it modifies the params.

For me, this is a major no-no but I had trouble articulating why. The only real answer that I could come up with was that debugging later I would have to ask myself where the changes to the params was happening, and the more places I put the changes, the more places I would have to hunt down to ensure that I was not making a bug. Also, parse_and_format_date does not clearly show that it updates the params object. In this file, it's not a big deal, but the bigger the file, the worse it gets, and if you add some concerns the next person to read this code will start scratching their head.

Source of truth

The shoe_params method should appear to be frozen in time. I should not be able to call it in multiple different places in my update method and get a different result. What's more is that the manipulations being done to the params variable should not be so heavy that they can't be done repeatedly if necessary. Also, strong parameters are not usually called multiple times in an action, so it's not crazy to think that it can contain these transforms.

If we made parse_and_format_date idempotent then we could do something like this:

def shoe_params
    params[:shoe][:buy_date] = parse_and_format_date
    params.require(:shoe).permit(:buy_date, :title, :id)
end

def parse_and_format_date
    @parsed_date ||= DateTime.strptime(params[:shoe][:buy_date], user.date_format)
end

In that situation, we would always now what shoe_params contains and it would be clear at any point of any action we call in our controller.

STI Models and a Misconception of DRY 02-25-2017

One of the biggest problems in programming is how to handle and simplify the logic that shows up in your application. Logic which shows up uniformly throughout a class tends to be a smell which indicates the need to represent multiple different concepts. Here is an example of how this can manifest:

Example:

class ThingController < ApplicationController
  def create
    if creation_params[:kind] == 'First'
      First.create(creation_params)
    else
      Second.create(creation_params)
    end
  end

  private

  def creation_params
    params.require(:thing).permit!
  end
end

What's happened here is that we have two separate classes, here First and Second. If you are familiar with STI then you probably have seen this sort of thing often. The problem is that the objects share a data schema, but they will have potentially different views, potentially different validations and they will do potentially different things, ie they have different domains.

Keep it DRY

DRY

People often wrongly cite that they are staying DRY by merging two domains or by combining two functions. But the concept is actually not referring to code. The principal is referring to having a single representation knowledge or a single source. So an example of keeping code dry is to not have a constant show up in all of your view code.

.header
  %p Default circle size is 300 mm.
  .blurb
    Space remaining:
    %span = circle.size - 300

Instead you would store the size as a constant or a method in a class.

class Circle
  def self.max_size
    300
  end
end

.header
  %p= "Default circle size is #{Circle.max_size} mm."
  .blurb
    Space remaining:
    %span= circle.size - Circle.max_size

Manifestation of Excess Logic with STI:

If we look back to our original controller example, we see that there are two separate domains that are being handled by one single controller and that while the total amount of code is less(potentially), we haven't actually simplified our lives. As we add more subclasses to the STI model we will see an unhappy pattern emerge. Potentially we will have a case-statement to select our object and to have them do different things. We will have to define functions like this outside of our models that will then manage the models.

The solution is to not be afraid of having more files, not thinking that less code is necessarily more manageable. Treating the two models as separate resources will make maintenance easier from our routes to our views.

The problem with putting two separate models in the same controller is that there is a branch that starts in the route, and goes all the way to the view. The only argument that I have seen is that you can save on the amount of code you write by putting them in the same controller and giving them the same route. Doing so however makes all of the code associated more complicated as there is now a branch for every reference to the model. If there really is no divergent domain code, then there is no reason for STI. STI is not a way for you to treat two separate things as the same, it is a way for you to treat two things with the same data as different.

Tags: dry STI Rails

Definition vs Function/Methods Style 10-15-2011

I was recently reading The Rails 3 way by Obie Fernandez when I came across a section of the book that was talking about how Ruby allows for a definition-style as opposed to a funtion-style. That is to say, using a function call without including the open and closing parentheses. Typically, I like using functions without parentheses because I feel that the function looks more clear.

I much prefer to see:


link_to "Some page", some_page_path

instead of:


link_to( "Some page", some_page_path )

Especially in a haml template, the first style looks nicer. However, "The Rails 3 way" mentions that one of the key benefits parenthesis-less function calls offer is macro-like feeling and should be a signifier that this call will affect other things and not act directly. Indeed when you look at the following code:


class Client < ActiveRecord::Base 
  has_many :billing_codes
  has_many :billable_weeks
  has_many :timesheets, :through => :billable_weeks
end

class Client < ActiveRecord::Base
  has_many(:billing_codes)
  has_many(:billable_weeks)
  has_many(:timesheets, :through => :billable_weeks)
end

You can see that the first one looks cleaner. It also looks more like a declaration. Indeed, the has_many function alters the object of the class that calls it giving all of it's instantiated objects a slew of other functions. This theme happens over and over again in the Rails framework. The filters that are added to controllers, such as before_filter and after_filter, will alter the functionality of actions that get called.

Ambiguity

The problem with the definition-style method is when there are nested functions which take parameters. For instance, if the function for generating the page for some_page requires parameters as well:


some_page_path( :user_id => current_user.id, :name => 'derp' ) # calls the path for some page

This will cause problems when the following call is made:


link_to "Some page", some_page_path :user_id => current_user.id, :name => 'derp'

In the link_to call, it can't be determined whether the :name parameter should be part of the params for link_to or part of the params for some_page_path.

Since consistency is always preferred, it is better to always use parentheses with function calls only leave them off only when the function is being used to alter the class in some way.

Tags: style ruby Rails

Rails 3 Routing: Use match in a Block 12-19-2010

I was trying to set up an easy route that would let me use the following url: "posts/tags/:tagname". As I was playing around I realized that you can use match inside of a block. More importantly you can also give it a path name so that it can be easily referred to. Here is what I did:


  resources :posts do
    collection do
      get 'display'
      match ":name", :to => "posts#tags", :as => 'by_tag_name'
    end
  end

This is nice because in my code I can now call:

=linkto name, bytagnameposts_path(:name => name)

Tags: routes Rails