Anyone who has used Ruby has most likely internalized one truth:
Everything in Ruby is an object.
This truth is inescapable, undeniable, and persistent. Ruby’s ability to store anything as an object is immensely powerful.
However, when writing to different file types from within Ruby, how can we store the objects created in our program and access them later?
Let’s say we had a
.txt file, and we wrote to it from within our Ruby program. How could we store an object in that file?
One method for storing objects from Ruby is to use a YAML file.
YAML stands for “YAML Ain’t Markup Language”, and is a human friendly data serialization standard for all programming languages. We can use YAML files in conjunction with our Ruby program to store objects, and in the case of web browsing, maintain state by storing data that persists in external files. Additionally, by overwriting YAML files, we can modify the values inside of our stored Ruby objects.
Installing the Project(s) and Dependencies
Download the following projects from Github:
Keep in mind, this project is not meant to be pretty. The goal of this project is to store objects from our Ruby program in an external YAML file. This YAML file will allow our application to maintain state when run in a web browser. There will be extremely little CSS styling.
bundler install from within the root directory of the project to install the necessary dependencies.
If this step didn’t go well and you’re running into problems installing dependencies with Bundler, check out the free book from Launch School which includes how to use Bundler (Link to Book).
Now, we’ve got all the dependencies installed and can use the
bundle exec ruby app.rb command to run our application in a web browser. Don’t click on any of the links on the page yet; they won’t work as we haven’t created the necessary methods for making this application work.
Note: This command should be written when inside the root directory of the project.
When I ran the aforementioned command, it told me that the application was running on port
4567. I can access this port by typing the following into the web browser:
localhost:4567. The port number may differ between different machines.
Take this time to review the various methods within the
app.rb file. These methods are already complete and are given to you, free of charge; however, understanding what they do is essential to understanding the methods we create in the next few steps. Additionally, look at the
User class within the
user_obj.rb file. We will be storing user data in instances of the
User class, so having a firm grasp of that class is also essential.
Creating a User’s YAML file
For this project, each user will have their own YAML file. This YAML file will contain the instance of the class
User that holds that particular user’s data (username, password, and interests). A database is a much easier and secure way of storing user information, but for the purposes of this project, we will pretend there aren’t obvious security flaws in our storing of user objects in separate, easily-found files.
So, in our application, a user will be able to register with the following credentials:
1. Username: Tim
2. Password: secret
Then, these credentials will be put through validation methods. If they pass the validation methods, an instance of the
User class will be instantiated with the correct attributes for
@pw. This instance of the
User class will be stored in a variable,
obj, and then somehow written to a new YAML file within the
Now that we know the general flow of our registration process, we’ve really got our work cut out for us!
Before moving ahead, be aware that the users already stored in the
usersdirectory have the same username as their file name, and their passwords are both:
From here, we’re going to delve into the
post “/register” route.
In the Github version of this project, there will be comments next to a few of the sections of the aforementioned route. They are removed from the above snippet for readability.
The goal of this section will be to write the missing method,
create_user_yml(obj), and successfully write a
User object to a YAML file and then store that YAML file in the
users directory. To do this, we’re going to need to modify our existing method,
create_user_yml(obj). We will do so after reviewing the
First, let’s think about what the
@users instance variable’s value is. If we look at the
all_users method, we will see the following:
This method doesn’t take any parameters. On the first line of this method, we assign the variable
pattern to the value of the
data_path method plus the string object:
data_path method always returns the absolute path to the correct directory, and by concatenating the string object
”/*” to it, we are specifying which type of files to search for in a directory. In this case, the file we’re going to search for is
*, which matches any file in our directory. Since we are only creating YAML files in this directory, it’s safe to include all files in our search.
As an extra challenge, try refactoring this method to only look for YAML files, which in our project use the
Take this time to review the class method
.globfor the class
Dir: Ruby: Dir.
When we invoke the
map method, we are storing only the file name of the file, and not the rest of its absolute path, in an array object within the
users local variable. Next, we are loading each YAML file from the
users variable. This returns an array of
User objects in the same order as they are stored in the
Don’t feel bad about if multiple re-reads of that paragraph are necessary for understanding. This is the trickiest part of the article.
Let’s modify the
create_user_yml method now.
What do we need first? Well, we’ve seen that in order to interact with the
users directory, we need to establish the absolute path to it. We do this by invoking the
data_path method and storing its return value in a variable. Let’s name it
Now our Ruby program will know where to go. But how do we name our new YAML file?
Since we’ve passed the
User object as an argument to this method, we can also access that object’s attributes and instance methods. Each user’s name is unique as per the validation methods. So, we can use the user’s name as the file name, and set the extension to
Now, we need to construct the path to the exact file we want to create. This path will include the file name and the absolute path we generated earlier. Luckily for us, we’ve already instantiated local variables with the values we need,
file_name. We’re going to use
File.join to concatenate our two string objects with
/ to construct the path to our new file.
We’ve got the final path for our file. Let’s actually write this file into existence. For our purposes here, we will use
File.open as it is a synonym for
File.new. Ruby allows developers to open files with different modes. These modes can be found at this link: Ruby File Modes.
We will use the
”w” mode, as this allows us to create and write to a new file.
The key to understanding the fourth line of code here is the
to_yaml method invoked on
to_yaml converts a Ruby object into YAML. As you can see from the following screenshot of a successful registration, different data types are stored differently.
The different types of data are written as
key:value pairs in specific YAML format. In the next section, we will see how to append new objects to an array within our YAML file.
Before moving on, take this time to play around with
to_yamlwith different data types in
Appending String Objects to an Instance Variable
In the following picture, one can see that the
interests instance variable is an array. There are many ways to add data to an array; however, in our case, simply concatenating string objects into our array won’t be enough. Since the
yml file must be rewritten in order to store the new
To do this, we need to:
1. Load the yml file.
2. Convert the User object out of YAML format.
3. Invoke the instance method(s) available to objects of the User class to append an interest to the specific User object.
4. Rewrite the yml file that we initially loaded from the users directory.
We have an idea of how to do 1, 2, and 4 as we’ve already loaded
User objects from the
users directory and written a
yml file in the previous section. So, #3 should be relatively simple to figure out, as we’re only invoking the instance method that involves appending an object onto the
@interests instance variable’s array.
First, have a glance at the
get “/add_interest” route.
This form is submitting a single string object to the
post “/add_interest” route. This string object can be accessed by the
params hash and the key
:interest. We know this because the
<select> tag has the
name attribute set to
interest becomes a key in the
params hash in the
Let’s take a look at our
post “/add_interest” route, as this is where we will rewrite the
On the third line of the aforementioned route, we are initializing an instance variable
@user which contains the specific
User object pertaining to the username stored in our session data. This session data,
:curr_user, is used to ensure that we don’t append interests to a different user. We need access to this
User object so we can append a string object to it’s
@interests instance variable.
So, now we need to do the following:
1. Add interest to the object stored in
2. Rewrite and store this modified User object as a yml file.
Take a look at the file
user_obj.rb to see which instance methods we have available to us.
We have two instance methods pertaining to the
@interests instance variable and they seem simple enough to use. Let’s invoke the instance method
add_interest on our
User object stored in the
@user instance variable.
This is all simple OOP right now. We’ve successfully appended the string object stored in the
params hash with the key
:interest onto the
@interests instance variable. Now, we need to write this User object onto a
.yml file. If we didn’t rewrite the
.yml file, the actual User object wouldn’t change as it pertains to the state of this application, since the file we use to access the object, its
.yml file, only changes when it is opened and rewritten.
How did we do this before? Can we simply repeat the same method we used in the previous section?
Yes, we can. In my example, I selected the value
Golf to be added to
admin’s interests. Our post route handled all the rest of the work. Anyone following this tutorial should have a
.yml file that looks similar to the following screenshot:
Now, when we look at the index page, we can see more proof that the
@interests of the
User object we’re updated and rewritten.
As one can see, this project is extremely simple. For anyone wishing to extend their skills as it pertains to this project, explore the following ideas using this project as a skeleton:
1. Allow users to input their own interests.
2. Add more user data to the User class and use it in the application somehow.
3. Style the pages.
4. Add custom objects, maybe a
Message class, that allows users to send messages to each other.
5. Create stronger input validation.
6. Refactor the logic out of the
post routes and into simpler methods.
7. Add method(s) to remove interests from User objects.