How to Set Default Values in Rails 5
This guide walks through multiple ways that you can set defaults in a Rails 5 application, including a discussion on when to use the various options and the various stages of callbacks.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Before we move on, I want to make a statement regarding project management as it relates to this application. In a regular project, you are constantly going to be experiencing change. There will be updates from clients, changes based off of requirements, changes due to the fact you thought you were going to implement a feature one way but you end up needing to doing it in another way.

Right after I finished filming the last guide, I realized that such a change had occurred for our application. When I was planning this course, I thought I would focus on virtual attributes at this point because we are talking about data management, and that seemed like an appropriate stage to address this topic. However, I then looked at the data and the application's current progress, and I realized it would be pointless to introduce virtual attributes with the type of data that we have right now. I try to keep my guides as practical as possible, so that you can come back and use them later for reference. So, instead of doing something convoluted, I’m going to talk about it later. I'm going to wait till we come to RubyGems integration, and will discuss virtual attributes and integrate them once we have integrated devise.

So, I'm going to delete that 'virtual attributes' from the current task list, and recreate it in the sprint ‘Ruby Gems Integration’, just under ‘integrate devise’. This position will be a more natural fit for this topic. This way, we can keep our examples as practical as possible.

I wanted to make a statement about this because change and redirection is something that happens in real projects. I’ve never had a project where I built out my list of tasks and sprint items and then never had to change something I had planned. That's just not reality. In fact, you'll probably do it a much more than we have done in this course because I spent quite a bit of time planning this out. Also, we don't have any clients here. So, the requirements aren't going to change very much, but still wanted to give you an idea of how changes occur and that they are a natural part of the development process.

Data Defaults

With that in mind, we can see how to setup data defaults. We've already introduced this topic a little bit. If you go to the schema file, and look at the blogs table, specifically our status, you will see a default in place.

 t.integer  "status",     default: 0

We gave it a default value of 0, which is the standard procedure when working with any type of enum. So that is one way you can set a default.

Open the migration file where we added this enum.

class AddStatusEnumToBlogs < ActiveRecord::Migration[5.0]
  def change
    add_column :blogs, :status, :integer
  end
end

We added a status of integer, then added a default value to it.

This is one way of doing it, and this is perfectly fine if you think you're never going to have to change it. That's the case with enum. I could see no scenario where we would want to change this default from 0 to 1.

However, there are times when you will definitely want to change your defaults. So, there is a another way of creating these types of default values, and all of it happens in the model file.

Callbacks

Before you would set the default during the migration process. Now, we can set it in the model file directly. We're going to implement this using something called a callback. Go ahead and open the portfolio.rb file. Under the scope add our callback. For this we will use after_initialize and then pass a symbol :set_defaults

after_initialize :set_defaults

This indicates that after a portfolio item has been initialized, the defaults should be set.

If you want to consider how this workflow operates, open the portfolio controller. A portfolio item would be initialized at the point when the new action is called. To be direct, when the new action is called the default would be set at that point.

There are many types of Rails callbacks. Right now we are going to discuss after_initialize and after_create.

To reiterate, after_initialize occurs right when the
@portfolio_item = Portfolio.new process is run, which is when the form is created.

Conversely, after_create runs after after all the code in create method

def create
    @portfolio_item = Portfolio.new(params.require(:portfolio).permit(:title, :subtitle, :body))

    respond_to do |format|
      if @portfolio_item.save
        format.html { redirect_to portfolios_path, notice: 'Your portfolio item is now live.' }
      else
        format.html { render :new }
      end
    end
  end

has run.

We'll talk about why after_create would not be the best place to set defaults. For now, let's write the code to set the defaults.

After_initialize

after_initialize is a process that automatically runs whenever a user accesses a form. :set_defaults is just the name of a method that we need to create.

So, next we're going to create this method.

def set_defaults
end

Inside this method, we're setting default values for main_image and thumb_image. I don't like the fact that a body and title can be created, but the main and thumb images are not set, so I want to have the ability to set default values for the images.

There is a really efficient way to do this using self. Remember, every time when we say self, it denotes that we are referencing a particular portfolio item. When I use self I am declaring that when ever you create a new portfolio by going to the form, you are referencing that specific creation process for that one item.

So we want to use self.main_image. We will follow that with ||=. This is an odd looking operator, but it is super beneficial. Let’s open the seeds file to grab our defaults ”http://placehold.it/600x400" for the main and ”http://placehold.it/350x200" for the thumb. So our method should look like this:

  def set_defaults
    self.main_image ||= "http://placehold.it/600x400"
    self.thumb_image ||= "http://placehold.it/350x200"
  end

Let's first see if this works, and then we'll go through the code and I will explain how it works.

Testing the Callback

Start the rails server, go to the browser and navigate to portfolio items. Our seeds files are creating our portfolios the way we want, but when we were creating portfolios in the browser, they wouldn't have the thumb_image, and if you clicked to the show page, it won't have the main_image either.

So, let's start by creating a new item. Even if there is no field in the new form to set an image, it should now set it automatically. Go ahead and go through the process of creating a new portfolio.

With that done you should be able to scroll to the bottom of the page and see your new portfolio and it should display the thumb image. Also, go to the show page, you should be able to see the main image now. And all that happened because of our callback code in the portfolio model file. So, this is the second way of setting the default. As a reminder, the first way is in the migration file.

How the Callback Functions

Now, we'll go through what's happening. You have to set the callback up exactly like we have it in our portfolio model file.

class Portfolio < ApplicationRecord
  validates_presence_of :title, :body, :main_image, :thumb_image

  def self.angular
    where(subtitle: 'Angular')
  end

  scope :ruby_on_rails_portfolio_items, -> { where(subtitle: 'Ruby on Rails') }

  after_initialize :set_defaults

  def set_defaults
    self.main_image ||= "http://placehold.it/600x400"
    self.thumb_image ||= "http://placehold.it/350x200"
  end
end

Let’s say we have an item that can be edited and I just had =, in the defaults like this:

def set_defaults
  self.main_image = “http://placehold.it/600x400”
  self.thumb_image = "http://placehold.it/350x200"
end

A problem would arise because after_initalize would then simply override the image values. For example, if we were editing a portfolio item and it already contained the images we wanted, this method, with just = would indiscriminately override our images with these default images. That would be a bad thing in many scenarios.

This double pipe ||= is essentially a shortcut for saying If the item is nil, then set the default for that item:

if self.main_image == nil
  self.main_image = “http://placehold.it/600x400”
end

So, ||= is a type of conditional that will set the value only if it is nil. This way, it opens the form and will look inside. Next, it will check if the main image is nil or does it already exist? If it exists, then the entire code is skipped. It'll say we're already good and we won't have to do anything because this attribute already has a value. On the other hand, if it initializes the item and the value is nil, it sets the default value and when it gets saved, the default is saved. Again, ||= is just a shortcut that allows you to write a conditional in a single line of code.

So now you know the two different ways to set conditionals in Rails.

Next, let’s upload the files to GitHub. Running git status shows we modified the portfolio.rb file. Add that in with git add . Commit it with git commit -m ‘Implemented image defaults for portfolio items’ And finish the process with git push origin data-feature

Go to our project management tool and check the next task as complete.

In the next guide, we're going to talk about integration concerns in our application.

Resources