Using the Controller Generator to Build the Topic Features
Presently, our application has a model for topics, however there is no view component that can be accessed by users. In this guide we'll build out the Topic feature and leverage the controller generator so that topics can be shown on the app.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

With our topics now paired with our blogs for our forms and for the required validations. We can start building now some topic views. What I am picturing, if we come here is to be able to have some spots where all the topics can be located. So, an index page as it were for topics. Also, to have every topic have its own page. For example, if you came to topics/1 then you would be able to see all the blog posts for that specific topic.

The very first thing that I'm going to do is come down to the terminal, hit clear and let's create a controller. This is a cool thing about Rails and its part of the reason why I wanted to wait until the end to add our topics is because many times students, especially the ones that are learning Rails for the first time is they think that you have to build an entire feature set, all at once. For example if you build a topic model like we did you have to build the views of controller you have to run all the generators all at the same time. But that's not actually the case. We can run those at any time we want. We have a topic model but we don't have any views, we don't have any controllers, we don't have any routes, anything like that. But we can create those now. I'm going to say..

rails g controller Topics index show

This is going to generate only a controller along with the views that we tell it. This is going to be for topics and it's going to have an index and a show action. If I hit return I can run this. If you want to build out a full crud kind of concept for your topics where you can log in and add new topics and do things like that then definitely more power to you. I personally have no problem diving into the Rails console. You saw before, it takes about two seconds. I don't really plan on using a lot of different topics I'll probably only have four or five and I'm not going to have a ton of them because I want them to be kind of more generic. If you want to have a ton and you want the ability to add new ones, to edit them, to do those kinds of things and you can definitely create additional views. With that in place let's start up the Rails server. Let's also see what we have available here. First, in our routes(routes.rb) we can see that we now have a topic for our show and index. I think that I just want to get rid of these and I'm going to show you how we can do resources for topics and then we can say only the ones we want which is going to be index and show.

large

I think this is a little bit cleaner and there's one hidden benefit. The show action when we had it there before when it was just the 'get' notice that it didn't have the id. It didn't have that concept of parameters. We'd have to manually enter that in. By using resources this should do it for us automatically. If I actually stopped the Rails terminal and run rake routes | grep topic this should show us that we have a couple of routes and it should automatically give us that id parameter. As you can see right here, it does we have topics which is our index action. We have show which is going to give us a single show and it takes in the id by default. That's exactly what I wanted. That's perfect! If we switch into Sublime. Let's open up our topic's controller. You can see that we have an index and a show which is what we want if I say...

def index
  @topics = Topic.all
end

def show
  @topic = Topic.find(params[:id])
end

This is going to bring in all of the topics. Here I can say topic = topic_find and then we can pass in params and look for the id. These are going to be the two items. As a quick review, remember the params is a 'params hash' and the id is going to grab the id value in it because if we do something like this(localhost:3000/topics/1) where we go to topic's/1 this is going to take us and it's going to grab that parameter of 1 and then it's going to go run a database query and say. Hey, bring me back the topic that has an id of 1. Let's see why this isn't working. If I come here(terminal) it looks like we got to 200. But for some reason, it looks like we have some type of little server hang up. Ok, there we go. That's fine it just took a little while for it to render. But we have a few issues here. One, notice that it's showing this layout. We do not want that way. Let's come back and change this up, say...

large

layout and we just want it to use the blog layout. We come back and hit refresh that should show us the blog layout. Perfect! It shows us all of this-this is obviously not what we're wanting. For our show page what we want, it's bringing this in but I want to show the title of the topic and then I want to show the blog post associated with that. Let's open up our topics show action(show.html.erb). In this view right here I'm going to first inside of an . I'm going to grab the topic so I can say...

<h1><%= @topic.title %></h1>

topic.title close this out(you can delete everything underneath it). Come back, hit refresh and let's make sure that's working. Perfect! That says topic 0. Because this is going to be so similar. Let's go to the blogs index action(index.html.erb). Grab all of this, paste it in and what we can do is we can actually just kind of make this fit our needs so I can come here and we have our topic title and now we want to actually un-indent that, now we want blogs. One thing to notice, we do not have access to the instance variable blogs but we can make it possible inside of the show action. What I can do is I can just say @topic.blogs and remember that we have some very specific kind of things that we want to bring in. We only want to bring in the publish blogs and then we only want them to be the recent ones so let's reference our blogs controller for the index. We also have this concept of being logged in and everything like that. I'm going to grab all of this coming down into our show action. We check to see, is the user logged in as a site admin? If so then we want our blogs to be able to be recent and that's it. If it is not a site admin just a regular user then we want the published ones. How can we do that how we can we merge these in? Well, we are going to keep the instance variable blogs because I really like being able to call @blogs. I don't want to, in the view call @topic.blogs and then implement the query there. That would be a pretty bad practice. What I want to do here, is just kind of replicate this. I'm going to grab what we have here. We're always going to find the topic and we're going to show the page so we don't want to do redirects or anything like that. I'm going to say if they're logged in then I want to say @topic.blogs.recent and everything there is perfect. If they're not logged in, then we want to replicate what we have here. I want this to be @topic.blogs.published.recent. This is going to be very similar to what we have on our index but not exactly because this focuses on the concept of a topic. So, topic is leading the chain hierarchy. There are some ways to refactor and I may come back and refactor this later to do something such as like create a custom scope where I could do something like this where it says blogs and then it is 'blog.by_topic' something like that. Then you could pass in the topic id and then build the query from there. That is definitely one way of doing it. I am not a big fan of refactoring before I have things working so for right now this should give us everything we need.

large

Let's take a look at this we have our topic show and we have our topics show action and these should all be mapped. Let's take a look. If I hit refresh. For topic 0 this works we have one topic here. If I come back to our blog index and change this one to be topic 0. Oh, and looks like we have a little bit of a bug. Undefine method 'id' for nil:NilClass. This is a bug but I dont believe its with our code. Let me go and test another one. Yeah, see this is working and I will walk through it in a second why this is the problem. For right now let me just change this so lets actually update this new one, so we have Rails. We can check our params in the terminal. Let's see exactly what that got updated to. If I come up here we have blogs and we have, this is on the load I believe let's see select blogs right there. OK, scroll up a little bit. OK, here are the parameters. In the form we sent the blog of that and the topic id is 5. I'm not coding right now I'm just referencing the terminal. The terminal like I've said a few times is your best friend. This is going to give you all the information you need in regards to what is passed. Right here, I just wanted to know what the id for the Rails topic was. If I go to topics and say 5(localhost:3000/topics/5) this is going to bring me Rails, that's perfect. Let's come and update another one. Let's go with this was 8. Let's update 5 to also be Rail's. If I hit submit, close it out and hit refresh. There we go. This is now working. We have a topic page and this is a topic show page for Rails. Now it is showing all of the Rails content. If you go to any of the other ones like topic 3 which is actually topic two with an id of 3, this one has the majority of them. Notice one thing that we also have access to the pagination which is pretty cool. That is everything. That's fully functional now. We now have the ability to have a concept of topics so our topics are working nicely. Let's see, let's finish out the other feature for this index action.

For this one I'm going to borrow this(show.html.erb) and I'm not going to bring the whole thing in just the top part. If I go to topics/index.html.erb I'm going to paste that in...

<div class="col-sm-8 blog-main">

  <h1>Topics</h1>

  <%= render @topics %>
</div>

Here we can just say topics. I'm not going to paginate this, you can paginate if you plan on having a ton of topics. Like I said I don't really plan on having a ton. Just kind of a basic set of them and we're not going to render blogs we're going to render topics. If you remember we are going to use our collection iterator here so I'm going to say app/views/topics I will create a new file, hit save. This is going to be _topic.html.erb. Inside of this, we can put our topic content. Here, I'm going to just, I don't know the exact styles I'm going to use but for right now let's just make sure this part's working. So I'm going to say...

<div>
  <%= link_to topic.title, topic_path(topic)
</div>

link_to and I wanted it to be the topic.title and remember because we're in this we're in this partial, that is getting passed in with the render and then just the collection. We have access to a topic variable. It's a topic local variable. I'm going say topic_path and pass in topic. Let's take a look and see if this works. If I come just to topics and check out the index page it looks like it works. We now have a link where we can go and grab all of those topics. If I come here this shows me all the posts for topic zero which is none. If I come to 2 it shows me all the ones for 2, and coding exercises so on and so forth. You may wonder what was it exactly that was causing an issue with that first one. It's because we did this before the validation occurred. We created this one but we created it without a topic and we can test this out. Let's go and grab this one, I believe if I even just do Blog.last. You can see, yes this is correct. This is the post we're talking about. But notice where it says topic id. This is set to nil. That is where the problem was, was that right here when we went and we actually tried to access it then the code was looking for a topic id, but it was not getting one because it didn't exist. If I click edit right here you can see it says selected and blog.topic.id is coming up with an undefined id for nil class. This is one of those error messages as a new Rails developer you're going to see a lot. It's because anytime you go to a page where you call a method which in this case is .id on something that doesn't exist which in this case is a topic, because for this specific one it doesn't have a topic. Part of the reason why I wanted to walk through this is to show you how important data validations are. You would not want a user to go and click on edit and just because you added a data validation late then they aren't able to access that page. Let's talk about how we can do this. There would be one way of fixing this, where we just deleted the item. That would definitely fix it. But let's pretend this is a production application, simply deleting the item is not going to be satisfactory. You wouldn't want to have you know you wouldn't want to be working on some blog post and because the developer had forgotten the validation and added it later you wouldn't want him to go and delete your posts. How can you do this? Well, we were almost there when we were in the Rails console. I can say rails c. Let's open up the Blog.last. this is going to give us that same item where topic_id is nil and from here I can say Blog.last.update!(topic_id: 1). Let's also add it with a bang(!) in case there's an error, we'll know about it and also do topic_id and then I can just pass in 1. That looks like it works. If I hit rails s it should have fixed the error by giving a topic. In a production app, so imagine that you are working on a blogging application that other users are utilizing what you'd want to do is probably create something like a topic called untitled or uncategorized or something like that and then you'd run a script for any topics that are nil and then you would simply put them in that uncategorized column. I wanted to walk through that because it's one thing to build an app from scratch it's a whole other thing to deal with problems when they arise and it takes a different kind of thinking, a different type of methodology for fixing bugs that arise in production. That is one that you don't want to go and simply delete the action that would be the easy way of doing it. Instead, you can just update the data. If I click refresh this page works. If I click on edit, this is updated and we have a topic 0 we could change it to coding exercises and we're all good to go. Let me come back here. We now have a topic index and show page. One thing actually before we close it out let's update a few items on the page. For our blog layout, we have a few items in the blog masthead this partial I actually want to add a new link. Right here we have these items and let's see, we have some content we need to update before we push it up. Then we have the ability to write a new blog if there is a site admin. But let's also put a link to our categories or to our topics right here. I think this would be a good spot for it. Let's come down, bring this down just so it's a little bit easier to read and we'll create another span tag here and let's create a link_to. Here we could just pass in as a string so, topics and then pass in topics_path. Close it out and let's see how this looks...

large

If I hit refresh now we have topics. Now, remember this write a new blog is only going to be shown to you. What users are going to see coming to your site is actually this right here where it says topics and you may want to turn this into a button, a label you know. Definitely, explore with the bootstrap classes and see if there's something that you'd rather do. You may also want to turn these into better-looking items. I will leave that up to you. That's not really as much coding as it is just personal preference and how you want to style these. I definitely recommend for you to play with them a little bit because it doesn't really look great. You wouldn't want this to be in your final version. But feel free to explore with the various bootstrap classes maybe putting them in cards maybe adding a description so it could describe exactly what it is. As long as you don't put in a table you'll be fine. Let's see what we have here. We completed the topic index and show we next are going to need to implement a topic for the blog widget with a scope for topics with blogs. That may not seem very clear. Don't worry that's totally fine we'll walk through exactly what that is in the next guide. Let's clear this out. Check out all the work we did. That was a decent amount of work in this one. Here we can say that we git commit -m "built out topic views" and git push origin final-changes. I will see in the next guide, where we build out our topic widget.

Resources