In this project, you’ll get to spend some quality time with models. We will be doing some data modeling and putting your active record knowledge to practice by building your own light weight Reddit clone.
The first step to building a good data model is usually not on the computer. You’ll want to take a minute and think through what you’re going to need. You may not really know everything when you first start, but mapping out an approach path is crucial to avoiding costly mistakes later.
In this warmup, you’ll be given a variety of scenarios and you’ll be asked to map out what the data models will look like for each one. You can do it with a pen and paper or, if you’re lucky enough to be around one, a whiteboard. There are a few specific accepted techniques for how to map out models and associations but just do whatever comes naturally to you. One way to do it is with a list of models and another is to create a visual diagram where each model is represented by a box and you connect them with appropriate arrows.
For each scenario, you’ll be asked to write down the data, associations, and validations necessary to build it. That means which models (data tables) will be necessary to store the data (and which columns you will need), which fields of those tables will be subject to validation (e.g. minimum password length or username uniqueness). Don’t worry if you’re not quite sure how to implement a given thing, the point here is to start thinking about how the data would be structured.
Example: You are building a blog for your startup which will have multiple authors and each author can write multiple posts.
This might look like:
Note: I’ll include the
:updated_at columns but you can safely assume they’re always there since Rails or the database gives them to you automatically*
username:string [unique, 4-12 chars, present] email:string [unique, present] password:string [6-16 chars, present] id:integer created_at:datetime updated_at:datetime has_many posts
title:string [unique, present] body:text [present] author_id:integer [present] id:integer created_at:datetime updated_at:datetime belongs_to author
Use whatever format feels best to you.
For each of the following scenarios, write down the models, columns, validations and associations you might use to implement it. Some of these are more difficult than others and you’ll have to use a bit of creativity to infer which columns might need to be present for the scenario to make sense in the real world.
The trick is identifying what should be a different model and how these models will relate to each other via simple associations (all the ones below are
belongs_to relationship). If you can’t quite figure out how it might look, keep the scenario in mind as you go through the next few lessons.
Remember, if you feel like you will be hard coding data multiple times, it’s probably a sign that you should create a separate table. A common example is address information – you could write down the city and state explicitly for each user. How about making separate City and State models and relating them to each other?
Let’s build Reddit. Well, maybe a very junior version of it called
micro-reddit. In this project, you’ll build the data structures necessary to support link submissions and commenting. We won’t build a front end for it because we don’t need to… you can use the Rails console to play around with models without the overhead of making HTTP requests and involving controllers or views.
$ rails _5.2.1_ new micro-reddit) and open it up. We’ll use the default SQLite3 database so you shouldn’t have to change anything on that front.
$ rails db:migrate. You can use
$ rails db:rollbackif you realize you forgot anything or just create a new migration for the correction (which might involve the
#change_columncommands). See the Rails API Documentation for details on syntax and available methods.
$ rails console. Try asking for all the users with
> User.all. You should get back an empty array (no users yet!). Now create a blank new user and store it to a variable with
> u = User.new. This user has been created in the ether of Ruby’s memory but hasn’t been saved to the database yet. Remember, if you’d used the
#createmethod instead of the
#newmethod, it would have just gone ahead and tried to save the new user right off the bat. Instead, we now get to play with it.
> u.valid?will run all the validations. It comes up
true… surprise! We haven’t written any validations so that’s to be expected. It’s also a problem because we don’t want to have users running around with blank usernames.
app/models/user.rbfile. These might involve constraints on the size of the username and that it must be present (otherwise you’ll potentially have users with no usernames!) and that it must be unique.
> reload!. You’ll need to do this every time you make changes to your app so the console can reload the current version. If it still seems broken, just
> quitout of it and relaunch (sometimes
#reload!doesn’t seem to do the trick). Build another new user but don’t save it yet by using
> u2 = User.new. Run
> u2.valid?again to run the validations and it should come up false. Good.
#errorsmethod. Try out
> u2.errorsto see the errors or, better,
> u2.errors.full_messagesto return a nice friendly array of messages. If you wrote custom messages into your validations, they will show up here as well.
> u3 = User.new(your_attributes_here)and run the validations. They should come up true. Save your user with the
#savemethod so you’ve got your first user in the database.
user_id) in your posts table? If not, you can just add a new migration (
$ rails generate migration yourmigrationname) and use the
#add_columnmethod mentioned above.
> User.first.posts. It should be an empty array since you haven’t created posts, but it shouldn’t throw an error at you.
p1, something like
> p1 = Post.new(your_attributes_here). Don’t forget to include the ID of the user in your
#buildand run through the association instead –
p2 = User.first.posts.build. Don’t fill in any fields yet. Examine the object that was created and you’ll see that the ID field already got filled out for you, cool! This is a neat trick you’ll learn about in the lesson on associations.
p1so your user has officially written something. Test that you can use the other side of the association by trying
> Post.first.user, which should return the original User object whose ID you pointed to when building the post. All has come full circle!
> u2 = User.find(2)
> c1 = u2.comments.firstshould return that user’s comment.
#commentsreturns an array with comments, which is why we need to use
#firstto actually retrieve the comment itself.
> c1.usershould return that comment’s author User (
> p1 = Post.first
> p1.comments.firstshould return the comment
> c1.postshould return the post
If any of those don’t work, double check your associations. Sometimes the error messages can be helpful in prompting you for how to set up those associations.
Send us your solution by editing this file so we can show others! Submit a link to the Github repo with your files in it here using any of the methods listed on the contributing page. Please include your partner’s github handle somewhere in the description if they would like attribution.
This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something.