Integrate Virtual Attributes to Extract First and Last Name Data from a User
This guide explains how to implement virtual attributes in Rails 5. Specifically we'll walk through the steps needed to add first and last name attributes without having to add new columns to the database.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide we're going to finish up our last item and our last task of authentication for the app and we're going to use virtual attributes. If you've never heard of a virtual attribute, essentially what it means is an attribute that we call on some model that isn't actually a column name.

Let's open up our schema file and we're going to do this with the user's table. Here you can see our users have e-mail, password, and a name attribute but what happens if we want to do something on our site like grab their first name. We don't have a first name attribute.

what we can do is use what's called a virtual attribute and it creates something without having to do a migration without having to ask the user for their first name. We can work with the data that's provided to us and then we can call it however we want.

Open sublime and go to user.rb. Inside of the user model, we're going to create a couple new methods. One is going to be called first_name, the other is going to be called last_name. Inside of these we're actually going to create some method implementations that take the data from our name value and then it's going to then go and grab the first name put it inside of here and then the last name and put it inside of here and then we can call it however we need.

One thing I forgot to mention, devise by default has the requirement that the e-mail address and the password are required. We really need to have a validates_presence_of_name because we're going to be running some methods on these. We don't want users to fill out a registration form, leave the name blank and then they'll run into an error because we're trying to call a method on a value that doesn't exist. With validates presence of here we can always be confident that a name will be supplied.

large

Now we can just do some string manipulation and say self not name dot split dot first. A review of ruby, you may kind of wonder what's going on right here. Let's go through it one at a time. Self.name is saying the specific name for the user that we're talking about. In other words, if we do a database query of user.last and it brings in John Smith then we're talking about John Smith. When we're calling self we're saying that whenever we're instantiating a specific user, we're talking about them.

I'm going to say self.name.split. What split is going to do is it is actually going to take a string it's a method in Ruby that you can call on a string and it splits that string into its own array. If I come to the terminal and we can just open up irb to take a look, let's have some names say "John Smith".split. You can see that it gives us an array that splits the names in half. By default, split is going to split it via a space. Split can take an argument, if for some reason your names came in with this kind of format where it was "Smith, John".split(", ") like that then we could say split and then we want to split it at every spot where there is a comma and a space.

"Smith, John".split(", ")

This is going to give us the same type of behavior, but we're expecting our users to go more with typing in their first name and their last name and then we can call split.

Now that we've called split we actually have a collection we can work with. Our first name is just the first element in the collection, we have to call our "John Smith".split.first this is does not mean first name. This means the first element in the array. This is a Ruby method and this brings back. John.

If you wanted to use a different syntax you could grab the first element using the bracket 0 syntax and this will bring back John. John Smith.split[0] This brings the exact same thing. that's one thing I wanted to be clear on because technically this method is going to look a little bit weird because we're going to be calling first on the first element and then last on the last element. Those are going to be a method called first name and last name. I don't want you to think that there is some kind of special first name and last name method inside of Ruby. This is something specific to arrays, if I created an array of integers and called .first on it, it would bring back the first element.

With this in mind with string which we know has our name.split.first we pretty much have our virtual attribute implementation. I can just grab this and for a last name I can do the same thing. I say split.last. Now we can call first name and last name.

large

Let's try this out in the rails console, let's look at our last user. I'm going to say u = user.last user is stored inside of a variable called "u" and you can see that is me with my name Jordan Hudgens. If I type u.first_name you can see that it pulls in Jordan. If I do u.last_name it pulls in Hudgens.

I am going to update this and if I say u.update!(name: "Cher"). I'm taking my last record which is me I'm saying .update, this is going to update the user exactly like how we have the update code inside of our controllers. This is the same thing I'm just doing it here in the console and I'm changing my name to "Cher" in the console. This has updated, if I type u, you can see it says "Cher."

The cool thing about our method implementation here is and this is the reason why I wanted to show you what happened if you have a user who enters only a single name. This still works and I'm going to show you why and this is a very cool thing with ruby, if I do u.first_name you can see it pulls "Cher" if I do u.last_name you can see it still pulls "Cher." What it's doing is it splits this first and last actually are going to be the same value if it only has one item. That may not seem like the most intuitive thing let's just run a very basic test.

If I create an array of just one element and it has the integer one inside of it, if I call first on this then the first element in the array is one. Now if I call last on this it's still 1. That's how it works that's exactly how it works with our "Cher". That is the reason why I chose to go with first and last instead of the more computer science 0 and 1. This would work if every user types in their first and last name that a would not work if they only entered one name in. I like to use this kind of implementation because it is much less buggy.

Start up the rails console and let's have a little bit of fun with this. I'm going to go into the home page, instead of saying "homepage" lets say Hi, <% current_user.first_name %>. It's going to say "Hi current user first name"

One thing to know, this will throw an error if there is not a current user. I'm also going to put in if current_user. The reason why is because first name is a method and if current user doesn't exist which means that if there is no user logged in the site or the sites being accessed by someone who's not logged in, then when you call first name, current user is going to be nil and then you're going to get a method error that says you can't call a method onto a nil value.

We're saying "only say this if there is a current user" I'm not going to completely build out this full implementation like doing something like saying "Hi" current user first name if current user and then say else say this. The reason why is because I have a cool little treat for us planned for the next section where we're going to build a much better interface than what we're doing right here.

Let's open this, we're logged in right now so we shouldn't get any error, we should see our first name. It should say "Hi, Cher" if I'm logged in as Cher. Hit refresh, and it says "Hi, Cher" If I go and change the name from Cher to John Snow, I have to update the password, now it says "Hi, John."

That is how we can take our attributes and we can work with the values. You saw how easy it is, we can just create plain old ruby methods here. If we create methods then they can be called via our whole system. Our system now can call first name. That's how we were able to say current_user.first_name. Current user is something devise gives us and devise wires this up through the user model.

To give you an idea of how the data flow works, the current user is something we're going to talk a lot more about in the next section and we're going to dive into exactly what it is. Right now just know there is a devise method that checks to see if a user is logged in and if so it gives us a direct connection to that users database record. That's how we were able to say current_user.first_name.

This went and queried the database and then showed that this user had a name and then it called the first name method on it. All we're really doing here is just string manipulation. That's really all we're doing in this sense for the virtual attribute, this says first name but this could say asdf right here.

If I hit save and come back and call it this, hit refresh, you can see it still works exactly the same way. There's nothing magical about calling it first name, that's just the way I did it because I think that's an intuitive way to do it.

This is a nice implementation for being able to now have multiple attributes that we can call. If you notice we didn't force the user to type in their first name and their last name they could just type in their full name and we made them do less work and then our application did exactly what we wanted on it and now we can split this up and use that how ever we need.

  • git status
  • git add .
  • git commit -m "Completed authentication with virtual attrs for user or model."
  • git push origin authentication.

This time since we're done let's go and we can do this one in github. Come to the root of our application. You can see that we have a new push that just went up less than a minute ago. I can click on compare and pull request. This is going to show all of the commits that we just added. I can say "Implemented devise based authentication along with custom attributes for user model" I use back tics so this will actually look like code.

This is pretty much what we did. Obviously if we ever need to look back we can see all of our commits for the detail breakdown.

Hit pull request, if I click merge this is going to merge everything into our master branch. I'm going to keep this here just because I know that you will want to reference these items.

The last thing is to come to the terminal switch to git checkout master run to git pull

This is going to bring down all of the changes that we made. I'm on the master branch and if I were to run the rails server and come to our application, all of our authentication things are still working.

There is one little bug. Notice how we just got this undefined method. This is not a bug with the code. This is actually a bug with our data. The user that I just tried to log in as doesn't have any data for their name. This is a great example, I'm actually glad this came up. This is a reason why it's important to put validates presence of a name because if you didn't have that this is the error that you would have.

We can fix this pretty easily. If you come to the rails console, type User.update_all(name: "Jordan Hudgens"). That changed all eight records.

You wouldn't do that in production because you wouldn't want to assign everyone a name that would overwrite the people to use it. That would give everyone some kind of hard coded user name.

Usually the way you would do this is you would create some type of script that created some type of custom user name based off of their email address or something like that. If you look at the sql code right here all it did is it said Update users and set the name to Jordan Hudgens it updated all the users in the table.

If I come here it refresh everything is working now.

In the next section, we're going to get into controllers in Rails.

Resources