5.1. DRYing Out MVC: Partials, Validations and Filters

One of the core tenets of Rails is DRY—Don’t Repeat Yourself. In this section we introduce three mechanisms Rails provides to help you DRY out your code: model validations, view partials, and controller filters.

We start with views. A partial is Rails’ name for a reusable chunk of a view. When similar content must appear in different views, putting that content in a partial and “including” it in the separate files helps DRY out repetition. Our simple app already presents one opportunity: the Index (list all movies) view includes a chunk of HTML that is repeated for each movie in the list. We can factor out that code into a partial, and include it by reference, as Figure 5.1 shows.

<!--  ...other code from index.html.erb here... -->
<div class="row bg-dark text-white">
    <div class="col-6 text-center">Title and More Info</div>
    <div class="col-2 text-center">Rating</div>
    <div class="col-4 text-center">Release Date</div>
</div>
<%= render partial: 'movie', collection: @movies %>
<div class="row">
    <div class="col-8"> <%= link_to movie.title, movie_path(movie) %> </div>
    <div class="col-2"> <%= movie.rating %> </div>
    <div class="col-2"> <%= movie.release_date.strftime('%F') %> </div>
</div>
Figure 5.1: (Top) Main view that uses a partial for each row of the movies table; (Bottom) Partial containing the code to render one row. To leverage convention over configuration, we name it _movie.html.erb: Rails uses the filename (without the underscore) to set a local variable (movie) to each item of the @movies collection in turn.

Partials rely heavily on convention over configuration. Their names must begin with an underscore (we used _movie_form.html.erb) which is absent from the code that references the partial. A partial may be in a different directory as the view that uses it, in which case a path such as ’layouts/footer’ would cause Rails to look for app/views/layouts/_footer.html.erb. A partial can access all the same instance variables as the view that in- cludes it, but partials that may be used from different views usually do not reference controller instance variables, since those may be set differently (or not at all) by different controller actions. A particularly nice use of a partial is to render a table or other collection in which all elements are the same, as Figure 5.1 demonstrates.

Partials are simple and straightforward, but the mechanisms provided by Rails for DRYing out models and controllers are more subtle and sophisticated. It’s common in SaaS apps to want to enforce certain validity constraints on a given type of model object or constraints on when certain actions can be performed. For example, when a new movie is added to RottenPotatoes, we may want to check that the title isn’t blank, that the release year is a valid date, and that the rating is one of the allowed ratings. (You may think there’s no way for the user to specify an invalid rating if they’re choosing it from a dropdown menu, but the request might be constructed by a malicious user or a bot.) With SaaS, you can’t trust anyone: the server must always check its inputs rather than trust them, or risk attack by methods we’ll see in Chapter 12.

As another example, perhaps we want to allow any user to add new movies, but only allow special “admin” users to delete movies. Both examples involve specifying constraints on entities or actions, and although there might be many places in an app where such con- straints should be considered, the DRY philosophy urges us to centralize them in one place. Rails provides two analogous facilities for doing this: validations for models and filters for controllers.

 1class Movie < ActiveRecord::Base
 2    def self.all_ratings ; %w[G PG PG-13 R NC-17] ; end #  shortcut: array of strings
 3    validates :title, :presence => true
 4    validates :release_date, :presence => true
 5    validate :released_1930_or_later # uses custom validator below
 6    validates :rating, :inclusion => {:in => Movie.all_ratings},
 7        :unless => :grandfathered?
 8    def released_1930_or_later
 9        errors.add(:release_date, 'must be 1930 or later') if
10        release_date && release_date < Date.parse('1 Jan 1930')
11    end
12    @@grandfathered_date = Date.parse('1 Nov 1968')
13    def grandfathered?
14        release_date && release_date < @@grandfathered_date
15    end
16end
17# try in console:
18m = Movie.new(:title => '', :rating => 'RG', :release_date => '1929-01-01')
19# force validation checks to be performed:
20m.valid?  # => false
21m.errors[:title] # => ["can't be blank"]
22m.errors[:rating] # => [] - validation skipped for grandfathered movies
23m.errors[:release_date] # => ["must be 1930 or later"]
24m.errors.full_messages # => ["Title can't be blank", "Release date must be 1930 or later"]
Figure 5.2: Lines 3–5 use predefined validation behaviors in ActiveModel::Validations::ClassMethods3. Lines 6–15 show how you can create your own validation methods, which receive the object to be validated as an argument and add error messages describing any problems. Note that we first validate the presence of release_date, otherwise the comparisons in lines 10 and 14 could fail if release_date is nil.

Model validations, like migrations, are expressed in a mini-DSL embedded in Ruby, as Figure 5.2 shows. Validation checks are triggered when you call the instance method valid? or when you try to save the model to the database (which calls valid? before doing so). Any validation errors are recorded in the ActiveModel::Errors object associated with each model; this object is returned by the instance method errors. As line 6 shows,validations can be conditional: the movie’s rating is validated unless the movie was released before the ratings system went into effect (in the USA, 1 November 1968).

We can now understand lines 10–12 and 23–25 from Figure 4.9 in the last chapter. When creating or updating a movie fails (as indicated by a falsy return value from create or update_attributes), we set flash[:alert] to an error message informed by the contents of the movie errors object. We then render (not redirect to) the form that brought us here, with @movie still holding the values the user entered the first time, so the form will be prepopulated with those values. A redirect would start an entire new request cycle, and @movie would not be preserved.

In fact, validations are just a special case of a more general mechanism, Active Record lifecycle callbacks, which allow you to provide methods that “intercept” a model object at various relevant points in its lifecycle. Figure 5.3 shows what callbacks are available; Figure 5.4 illustrates how to use this mechanism to “canonicalize” (standardize the format of) certain model fields before the model is saved. We will see another use of lifecycle callbacks when we discuss the Observer design pattern in Section 11.7 and caching in Chapter 12.6.

5.3
Figure 5.3: The various points at which you can “hook into” the lifecycle of an ActiveRecord model object. All ActiveRecord operations that modify the database (update, create, and so on) all eventually call save, so a before_save callback can intercept every change to the database. See this Rails Guide6 for additional details and examples.
 1class Movie < ActiveRecord::Base
 2    before_save :capitalize_title
 3    def capitalize_title
 4        self.title = self.title.split(/\s+/).map(&:downcase).
 5        map(&:capitalize).join(' ')
 6    end
 7end
 8# now try in console:
 9m = Movie.create!(:title => 'STAR  wars', :release_date => '27-5-1977', :rating => 'PG')
10m.title  # => "Star Wars"
Figure 5.4: This before_save hook capitalizes each word of a movie title, downcases the rest of the word, and compresses multiple spaces between words to a single space, turning STAR wars into Star Wars. Coincidentally, Rails’ ActiveSupport::Inflector#titleize provides this functionality
1class ApplicationController < ActionController::Base
2    before_filter :set_current_user
3    protected # prevents method from being invoked by a route
4    def set_current_user
5        # we exploit the fact that the below query may return nil
6        @current_user ||= Moviegoer.where(:id => session[:user_id])
7        redirect_to login_path and return unless @current_user
8    end
9end
Figure 5.5: If there is a logged-in user, the redirect will not occur, and the controller instance variable @current_user will be available to the action and views. Otherwise, a redirect will occur to login_path, which is assumed to correspond to a route that takes the user to a login page, as Section 5.2 explains. (and is just like && but has lower precedence, thus it parses as ((redirect_to login_path) and (return)) unless...)

Analogous to a validation is a controller filter—a method that checks whether certain conditions are true before an action is run, or sets up common conditions that many actions rely on. If the conditions are not fulfilled, the filter can choose to “stop the show” by rendering a view template or redirecting to another action. If the filter allows the action to proceed, it will be the action’s responsibility to provide a response, as usual.

As an example, an extremely common use of filters is to enforce the requirement that a user be logged in before certain actions can be performed. Assume for the mo- ment that we have verified the identity of some user and stored her primary key (ID) in session[:user_id] to remember the fact that she has logged in. Figure 5.5 shows a filter that enforces that a valid user is logged in. In Section 5.2 we will show how to combine this filter with the other “moving parts” involved in dealing with logged-in users.

Filters normally apply to all actions in the controller, but as the documentation on filters states, :only or :except can be used to restrict a filter to guarding only certain actions. You can define multiple filters: they are run in the order in which they are declared. You can also define after-filters, which run after certain actions are completed, and around-filters, which specify actions to run before and after, as you might do for auditing or timing.

Self-Check 5.1.1. Why didn’t the Rails designers choose to trigger validation when you first instantiate a movie using Movie#new , rather than waiting until you try to persist the object?

As you’re filling in the attributes of the new object, it might be in a temporarily invalid state, so triggering validation at that time might make it difficult to manipulate the object. Persisting the object tells Rails “I believe this object is ready to be saved.”

Self-Check 5.1.2. In line 5 of Figure 5.2, why can’t we write validate released_1930_or_later , that is, why must the argument to validate be either a symbol or a string?

If the argument is just the “bare” name of the method, Ruby will try to evaluate it at the moment it executes validate, which isn’t what we want—we want released_1930_or_later to be called at the time any validation is to occur.

5.6
Figure 5.6: Third-party authentication enables SSO by allowing a SaaS app to request that the user authenticate himself via a third-party provider. Once the user has done so, the provider sends a token to the requesting app proving that the user authenticated themselves correctly and possibly encoding additional privileges the user grants to the requesting app. The flow shown is a simplified version of OAuth, an evolving (and mildly controversial) open standard for authentication and authorization used by Twitter, Facebook, Microsoft, Google, Netflix, and many others. Twitter logo and image copyright 2012 Twitter Inc., used for instructional purposes only.