- Read Tutorial
- Watch Guide Video
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.