Model Methods & Scopes

Free Ruby on Rails Tutorial

Learn how to enhance your Ruby on Rails skills with this comprehensive tutorial that covers topics such as creating a model method for runtime, Scopes, DRYing up the scopes, and adding tab highlight behavior.

This exercise is excerpted from Noble Desktop’s past web development training materials. Noble Desktop now teaches JavaScript and the MERN Stack in our Full Stack Development Certificate. To learn current skills in web development, check out our coding bootcamps in NYC and live online.

Topics covered in this Ruby on Rails tutorial:

Creating a model method for runtime, Scopes, Optional bonus: DRYing up the scopes, Additional bonus: adding the tab highlight behavior

Exercise Preview

preview model methods scopes

Photo courtesy of istockphoto, © Bliznetsov, Image #20982716

Full-Stack Web Development Certificate: Live & Hands-on, In NYC or Online, 0% Financing, 1-on-1 Mentoring, Free Retake, Job Prep. Named a Top Bootcamp by Forbes, Fortune, & Time Out. Noble Desktop. Learn More.

Exercise Overview

Perhaps over the course of writing validations you started thinking, “We have this nice big model file, movie.rb, and it’s so empty! What else can we put in it?” For one: model methods, which, like the name suggests, pertain to behaviors of models and their components.

Another popular saying in the Rails community is “fat model, skinny controller”—in other words, the bulk of an application’s logic should be in its models, not in its controllers or views. The assumption underlying this principle is that model code is more reusable than controller code, thus more DRY.

  1. If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercises before starting this one. If you haven’t finished them, do the following sidebar.

    If You Did Not Do the Previous Exercises (3A–4D)

    1. Close any files you may have open.
    2. Open the Finder and navigate to Class Files > yourname-Rails Class
    3. Open Terminal.
    4. Type cd and a single space (do NOT press Return yet).
    5. Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
    6. Run rm -rf flix to delete your copy of the Flix site.
    7. Run git clone https://bitbucket.org/noble-desktop/flix.git to copy the Flix git repository.
    8. Type cd flix to enter the new directory.
    9. Type git checkout 4D to bring the site up to the end of the previous exercise.
    10. Run bundle to install any necessary gems.
    11. Run yarn install --check-files to install JavaScript dependencies.

Getting Started

  1. Open the Finder and navigate to Class Files > yourname-Rails Class

  2. Open Terminal.

  3. Type cd and a single space (do NOT press Return yet).

  4. Drag the flix folder from the Finder to the Terminal window.

  5. Make sure you’re in Terminal and hit Return to change into the new folder.

  6. Type the following in Terminal:

    rails server
    

    The Rails server is now running, so let’s get to work exploring model methods.

Creating a Model Method for Runtime

The Flix site currently has runtimes listed in minutes—but honestly, who wants to mentally calculate the equivalent of 238 minutes? Let’s make these values more user-friendly by displaying them in hours and minutes. This would be a good use for a model method.

  1. We suggest opening the flix folder in your code editor if it allows you to (like Sublime Text does).

  2. In your code editor, open flix > app > models > movie.rb

  3. Let’s add a new method to the model file. Type the following above the last end:

    def runtime_hours
       unless runtime.nil?
    
       end
    end
    u    

    We already have a value called runtime, so we have to name this one something different, like runtime_hours. Also, it’s always a good idea to add an unless statement right away; in the case that there is no runtime entered for a given movie, we’ll avoid an error that will crash the whole page.

  4. Add the following bold code to the unless statement:

    unless runtime.nil? 
       "#{runtime / 60} hrs."
    end
    

    A Rails quirk to be aware of: when Rails divides using / it completely discards the remainder of the division equation. Conversely, the modulus (with %) produces ONLY the remainder of the division equation; let’s use it to take care of the minutes.

  5. Add the following bold code to the line you just added:

    "#{runtime / 60} hrs. #{runtime % 60} min."
    
  6. Save the file.

  7. Open app > views > movies > index.html.erb

  8. Find the following code, around line 22:

    <div><%= movie.mpaa_rating %>, <%= movie.runtime %> minutes</div>
    
  9. Edit the line as shown below, deleting the word minutes because we added that in the runtime_hours model method:

    <div><%= movie.mpaa_rating %>, <%= movie.runtime %></div>
    
  10. Add the following bold code:

    <div><%= movie.mpaa_rating %>, <%= movie.runtime_hours %></div>
    
  11. Save the file.

  12. We need to make the same change in the show file. Open app > views > movies > show.html.erb

  13. Find the following piece of code, around line 8:

    <p class="runtime">Runtime: <%= @movie.runtime %> minutes </p>
    
  14. Edit the code as follows, making sure to delete the word minutes:

    <p class="runtime">Runtime: <%= @movie.runtime_hours %></p>
    
  15. Save the file.

  16. Switch to the browser, navigate to localhost:3000 and notice that all of the runtimes now display in user-friendly hours and minutes!

    What other methods could we add to the model? Well, we’ve listed MPAA ratings in a number of places—including the controller, where they don’t really belong! Let’s DRY up the code by storing the complete list of MPAA ratings as a class method in the model.

DRYing Up the Code with Another Model Method

  1. In your code editor, open app > views > movies > _form.html.erb

  2. Find the following code, around line 41:

    <% mpaa_ratings = options_for_select ["G", "PG", "R", "NR"], selected: @movie.mpaa_rating %>
    
  3. Select the following portion, the array of MPAA ratings, and cut it (Cmd–X):

    ["G", "PG", "R", "NR"]
    
  4. The remaining code should look like this:

    <% mpaa_ratings = options_for_select(, selected: @movie.mpaa_rating) %>
    
  5. Add the name of the class method we’re about to create:

    <% mpaa_ratings = options_for_select(Movie.all_mpaa_ratings, selected: @movie.mpaa_rating) %>
    

    Remember, class methods always start with the name of the class itself.

  6. Save the file.

  7. Switch to movie.rb in your code editor.

  8. Add the following bold code above the start of the mpaa_rating validation method:

    def self.all_mpaa_ratings
    
    end
    
    validates :mpaa_rating, inclusion: { in: self.all_mpaa_ratings }
    

    You may be wondering, why did we use self here, and not earlier? Well, because the list of possible MPAA ratings is common to all movies and not particular to any one movie, we are creating a class method here rather than an instance method. Class methods are called by invoking the class directly, rather than the particular instance. So when we want to invoke the list of possible MPAA ratings, we can call Movie.all_mpaa_ratings and we don’t need to rely on a particular instance of the movie class.

    You may also be wondering, why does this method come above the validation? The answer is because the validation needs this method to exist first—otherwise you’ll get an error.

  9. Paste the code within the method so the code reads as follows:

    def self.all_mpaa_ratings
       ["G", "PG", "R", "NR"]
    end
    

    We decided to make a class method because there is just one list that will be the same for every movie; we only would have made an instance method if there were somehow a different list of MPAA ratings for each movie.

  10. Finally, we have the opportunity to change the array syntax to a simpler version, so let’s do it!

    def self.all_mpaa_ratings
       %w(G PG R NR)
    end
    

    This alternative Ruby array syntax can only be used when your array is comprised of simple strings, without spaces.

Scopes

Another way to add logic to your app, besides model methods, is to utilize scopes. Scopes provide a handy way to select only the records that match a specific pattern.

  1. In a browser, navigate to localhost:3000 and try clicking the tabs that say All Movies, In Theaters, Coming Soon, and Go Now. The tabs currently lack functionality, but we can get them working with scopes!

    We already have a field to handle this element (placement), which contains information about each movie’s placement category. We just need to create a scope for the record and a bit of controller logic to route the movies appropriately.

  2. Switch to your code editor.

  3. Open flix > config > routes.rb

  4. On line 4, define a new route as follows:

    get 'movies/recommended/:placement' => 'movies#recommended'
    resources :movies
    

This’ll pass a parameter called :placement to the controller action recommended.

  1. Save the file.

  2. Open app > controllers > movies_controller.rb

  3. Add the following method around line 11. It doesn’t really matter where it goes, in fact, as long as it is above the private method:

    def recommended
       @placement = params[:placement]
    end
    

    We created the instance variable @placement in case we wanted to display it on the screen or use it for another purpose. Because we have three placement values, and we want it to do something different for each of those values, this is a good opportunity to use a case statement.

  4. Add the following bold code to create a case statement:

    def recommended
       @placement = params[:placement]
       case @placement
          when 'in_theaters'
          when 'coming_soon'
          when 'go_now'
       end
       render 'index'
    end
    

    You might be wondering: isn’t this where we would create a new view file, e.g. app/views/movies/placement.html.erb? But, why create duplicate code? We already have a view (the index page) that is capable of showing a list of movies as a grid. So, we’re using the render command to ask Rails to just use an existing view.

    We left the three cases blank because we haven’t written the scopes yet. Let’s create them now.

  5. Save the file.

  6. Switch to movie.rb

  7. Add the following code around line 6:

    validate :mpaa_rating_must_be_in_list
    
    scope :in_theaters,
    
    def runtime_hours
    

    A scope definition always starts with the word scope followed by the name of the scope (in this case, :in_theaters).

  8. Add the following code to the scope definition:

    scope :in_theaters, -> { where(placement: 'in_theaters') }
    

    The little arrow -> is referred to as a lambda and indicates that some Ruby code is about to follow. We can then invoke the Ruby where method to find the exact movies we want to display. This scope tells Rails to load all movies where placement is equal to in_theaters. Let’s define the other two scopes.

  9. Copy the entire line you just wrote and paste it twice below (or in Sublime Text you can duplicate the current line by pressing Cmd–Shift–D) so you have a total of three identical definitions as follows:

    scope :in_theaters, -> { where(placement: 'in_theaters') }
    scope :in_theaters, -> { where(placement: 'in_theaters') }
    scope :in_theaters, -> { where(placement: 'in_theaters') }
    
  10. Make the following changes shown in bold:

    scope :in_theaters, -> { where(placement: 'in_theaters') }
    scope :coming_soon, -> { where(placement: 'coming_soon') }
    scope :go_now, -> { where(placement: 'go_now') }
    
  11. Save the file.

  12. Switch back to movies_controller.rb

  13. Add the following bold code to the case statement:

    @placement = params[:placement]
    case @placement
       when 'in_theaters'
          @movies = Movie.in_theaters
       when 'coming_soon'
          @movies = Movie.coming_soon
       when 'go_now'
          @movies = Movie.go_now
    end
    render 'index'
    

    This means that when the placement is in_theaters, the @movie instance variable should be equal to Movie.in_theaters, and so on for the other cases.

  14. Save the file.

  15. Last but not least, let’s update the index view. Switch back to index.html.erb.

  16. Find the code for the tabs, which starts around line 7:

    <li class="active"><a href="#">All Movies</a></li>
    <li><a href="#">In Theaters</a></li>
    <li><a href="#">Coming Soon</a></li>
    <li><a href="#">Go Now</a></li>
    
  17. Delete the opening and closing anchor tags so the code looks like this:

    <li class="active">All Movies</li>
    <li>In Theaters</li>
    <li>Coming Soon</li>
    <li>Go Now</li>
    
  18. Right now the All Movies tab is always highlighted in orange. Remove the class="active" code as follows:

    <li>All Movies</li>
    
  19. Add the following bold embedded Ruby code to the All Movies tab:

    <li><%= link_to " All Movies", movies_path %></li>
    

    We are using the link_to helper. As you can see from the code, the link_to helper first takes the text for the link, then takes the link itself.

    You might be wondering where the movies_path part came from; it’s the same as typing "/movies". Remember, to see all the existing routes in the application you can switch to the Terminal and type rake routes. Append _path to any of the listed routes and a little bit of Rails magic will happen; Rails will know the exact path to that particular route! There is no route for recommended, though, so we’ll have to write out the other links longhand.

  20. Add the embedded Ruby for the other links as follows:

    <li><%= link_to "All Movies", movies_path %></li>
    <li><%= link_to " In Theaters", "/movies/recommended/in_theaters" %></li>
    <li><%= link_to " Coming Soon", "/movies/recommended/coming_soon" %></li>
    <li><%= link_to " Go Now", "/movies/recommended/go_now" %></li>
    
  21. Save the file.

  22. Switch to the browser, navigate to localhost:3000/movies and click on each tab, which should display certain movies based on their placement value. Great!

    That was a pretty easy feature to add to the site, primarily because we didn’t have to modify the logic of the index view. In fact, let’s look at the index view a bit closer.

  23. Switch back to index.html.erb in your code editor.

    This view was written in a fairly generic way. It simply expected to receive an instance variable called @movies which contained some number of movie objects, as you can see in line 15:

    <% @movies.each do |movie| %>
    

    The view doesn’t care whether @movies contains all movies, or just a subset, as long as each object is a movie. It’s the controller’s job to indicate precisely which collection of movies is shown. Let’s take a quick look at the controller code.

  24. Switch to movies_controller.rb and take a look at this method around line 7:

    def index
       @movies = Movie.all
    end
    

    This means that by default, all movies should be sent to the index.

  25. Look at the recommended method that we wrote a few minutes ago (it starts around line 11).

    This method allows the controller to receive user input (i.e., whichever tab they select) and make a decision about which group of movies to feed back to the view. The view then goes on to display what it’s told to display. This is an example of MVC all working together in harmony!

  26. Back in Terminal hit Ctrl–C to shut down the server.

Optional Bonus: DRYing Up the Scopes

We did a really good job here leveraging the power of Rails to create this feature… but we can always do better. We have three very similar-looking scopes; let’s DRY them up into one simplified scope.

  1. Switch to movie.rb in your code editor.

  2. Delete the scopes for coming_soon and go_now so this is the only one left:

    scope :in_theaters, -> { where(placement: 'in_theaters') }
    
  3. Make the following substitutions shown in bold:

    scope :with_placement, -> { where(placement: placement) }
    
  4. Add the following bold code after the lambda:

    scope :with_placement, -> (placement) { where(placement: placement) }
    

    This is a really idiosyncratic bit of Rails syntax, meaning that the scope receives placement as a variable.

    We have just replaced three scopes that corresponded to specific placement values with one scope that can take any value for placement, receive it as a variable and filter the set of models by placement value. This level of abstraction allows us to DRY up the code easily.

  5. Save the file.

  6. Switch to movies_controller.rb

  7. Find the following area of code:

    def recommended
       @placement = params[:placement]
       case @placement
          when 'in_theaters'
             @movies = Movie.in_theaters
          when 'coming_soon'
             @movies = Movie.coming_soon
          when 'go_now'
             @movies = Movie.go_now
       end
       render 'index'
    end
    
  8. Delete the case statement so the code reads as follows:

    def recommended
       @placement = params[:placement]
    
       render 'index'
    end
    
  9. Add the following bold code:

    def recommended
       @placement = params[:placement]
       @movies = Movie.with_placement(@placement)
       render 'index'
    end
    

    Notice that we are able to call the scope with_placement just like we would call a regular method.

  10. Save the file.

  11. Switch to Terminal and type the following to launch the server:

    rails server
    
  12. Switch to the browser, reload localhost:3000/movies and click on any of the tabs. They should still be working perfectly, but now our code is much DRYer.

  13. Back in Terminal hit Ctrl–C to shut down the server.

Additional Bonus: Adding the Tab Highlight Behavior

Let’s get the tab highlight behavior working properly so the placement tabs turn orange when selected.

  1. Switch to index.html.erb in your code editor.

  2. Find the following code, starting around line 7 and add the bold changes. Make sure to nest these within the <li> tags and type a single space before each class=active; (The spaces below are not typos.)

    <li<%= ' class=active' if @placement.nil? %>><%= link_to "All Movies", movies_path %></li>
    <li<%= ' class=active' if @placement == "in_theaters" %>><%= link_to "In Theaters", "/movies/recommended/in_theaters" %></li>
    
  3. Copy and paste the in_theaters bit of code as shown below:

    <li<%= ' class=active' if @placement.nil? %>><%= link_to "All Movies", movies_path %></li>
    <li<%= ' class=active' if @placement == "in_theaters" %>><%= link_to "In Theaters", "/movies/recommended/in_theaters" %></li>
    <li<%= ' class=active' if @placement == "in_theaters" %>><%= link_to "Coming Soon", "/movies/recommended/coming_soon" %></li>
    <li<%= ' class=active' if @placement == "in_theaters" %>><%= link_to "Go Now", "/movies/recommended/go_now" %></li>
    
  4. Make the changes shown in bold below:

    <li<%= ' class=active' if @placement.nil? %>><%= link_to "All Movies", movies_path %></li>
    <li<%= ' class=active' if @placement == "in_theaters" %>><%= link_to "In Theaters", "/movies/recommended/in_theaters" %></li>
    <li<%= ' class=active' if @placement == "coming_soon" %>><%= link_to "Coming Soon", "/movies/recommended/coming_soon" %></li>
    <li<%= ' class=active' if @placement == "go_now" %>><%= link_to "Go Now", "/movies/recommended/go_now" %></li>
    

    NOTE: Rails automatically (and somewhat inexplicably) wraps class=active in quotes (so it looks like class="active") which saves us a little bit of work.

  5. Save the file.

  6. Switch to Terminal and type the following to launch the server:

    rails server
    
  7. Switch to the browser and navigate to localhost:3000

  8. Click on the placement tabs. They should work perfectly, turning orange when selected. The Flix site is really coming along!

  9. Back in Terminal hit Ctrl–C to shut down the server.

Noble Desktop Publishing Team

The Noble Desktop Publishing Team includes writers, editors, instructors, and industry experts who collaborate to publish up-to-date content on today's top skills and software. From career guides to software tutorials to introductory video courses, Noble aims to produce relevant learning resources for people interested in coding, design, data, marketing, and other in-demand professions.

More articles by Noble Desktop Publishing Team

How to Learn Coding

Master coding with hands-on training. Learning how to code in JavaScript, Python, and other popular languages can pave the way to a job in tech, such as web development, data science & analytics, or software engineering.

Yelp Facebook LinkedIn YouTube Twitter Instagram