- Read Tutorial
- Watch Guide Video
I think we're moving along our list of items nicely. The next one that we're going to go through is our topic for blog widget with scope for topics with blogs. I realize that I probably could have written that a little bit better. It actually is completely accurate but it's definitely not the most explicit. If you're going through that and you tried to get through the task and that was a little blurry do not worry I should have made that one a little bit more clear. Let's walk through what I actually meant by it. What I want is, we have our about and our elsewhere widgets here on the sidebar. What I want is, I want any topics that have blogs I want them to be listed out here. What that means is I don't want a topic that is empty because I think that would be kind of pointless you could have all your topics listed there if you want and that's totally fine.
But I think it makes more sense to only list the ones that have blog posts associated with them because for example say that you go and you create 10 different topics and you have them all listed right here and you haven't written blog posts for each one of those topics yet. Then you're going to run into a situation where someone clicks on one, say it's sports or say it's Rail's, or SQL or anything like that and it doesn't have any blog posts. That's going to be kind of confusing. What I want to do is create a scope so that our blog posts are or I should say the topics that are shown there are only shown if they happen to have a blog. That's also a good excuse to build out kind of a cool, custom scope. Let's open up the topic model and coming down here we can create a new scope. I'm going to say...
def self.with_blogs includes(:blogs).where.not(blogs: { id: nil }) end
def self.with_blogs. This is going to only return topics that have blogs so, say includes blogs which is going to give us a little performance boost. It's really not that big of a deal when we're talking about topics because unless you're planning on building out hundreds or thousands of topics it's really not a big deal but this is more of just a habit that I will do. I'm going to say include blogs where.not and this is going to say blogs, pass in a hash id is set to nil. Ok, that is actually a lot of code that may look kind of weird. Let's actually come and grab all of this and let's come into the terminal.
Start up a Rail's console session. Let's actually run this I'm going to say Topic.includes(:blogs).where.not(blogs: { id: nil })
. If I hit return what that is going to do is it's going to run a query and it is going to check and see if the blogs that got returned had an id of nil or I should say, in other words, if they didn't exist. This is going to run a query on topic but also because I included blogs it's going to include that in the query and then because of that we are going to know if the topic has blogs or if it's empty. Essentially, what we are kind of replicating but we're doing it in a much cleaner way is you could imagine doing a query like this where you say create a variable called topics and say topic.all
and then go and iterate and say topics.each do |topic|
. You'd also have to create another array something like blog_filled_array = []
or something like that and set the = to an empty array and then inside of this(topics.each do |topics|
, you could pipe in topic. So, you could say topic and send this one in if topic.blogs.count > 0
. That would essentially be doing what we're doing. This would be a bad piece of code to write. This would essentially be telling, say if you did this in a hiring interview.
This would essentially be telling the people that you don't really understand how ActiveRecord
works because what you're doing is very manual and it's creating a number of different queries. Not only would you be creating the query on all the topics but you also, each time that you go and you hit the topic you're going to go and check all of the blogs and you're going to count the size. But this is replicating a pretty similar piece of functionality. All we're checking to do is say. Ok, I want you to only bring me back the topics that have blogs and some of this. The reason why I'm spending a little bit more time talking about this is because any time where we use double negatives I know that can be a little confusing to read where I'm saying where.not.blogs
. Then the hash the id is not nil. I know we have two negatives there and that may seem a little bit confusing but I think that it's actually, it makes more sense than trying to do something like this. But I did want to write this out so you could have an idea. This is essentially saying I'm going to iterate over all the topics but I only want to keep the ones that have a topic where the count of blogs is greater than zero. Let's hit save here.
Now that we have this we can use this query in our app. I'm going to open up our topic's controller and we need to have this available for every single action because every single action is going to have to have this widget. I'm going to create a before action that's going to be called :set_sidebar_topics
and it's going to be for all of them so I don't have to be explicit about saying which one it's there for and which one it's not. Inside of a private method, I'm going to say...
def and then :set_sidebar_topics and simply :set_sidebar_topics = Topic.with_blogs
. The reason for this naming is very purposeful. One, is I don't want to run into any issues where say that I called this 'topics'. When we hit the index page, this would conflict so we'd be essentially naming two items with the same variable name which would be a bad idea. This is saying I only want this to...I'm being very explicit that this is only going to be showing sidebar topics and then we're running the database query here. This is one of the very, very, very, VERY few times you're ever going to have duplicate code and that is because our topic's controller and our blog layout are shared by the blogs controller. We also need to put this over here...
I'm going to say before action. The one thing I will say is I'm going to add a little except block here just for the sake of performance. The reason is we don't have any need to call this inside of our action items that are not 'get' requests. In other words, we need it for index, we need it to show, we need it for new, we need it for edit. But we don't need it for create, update, or destroy, or toggle_status. But the way 'before action' works if we don't pass any items to it, just like we didn't here then it's going to get run for every one of these actions and that would be just a waste for these ones. So, here we don't need it for our update, we don't need it for create, we don't need it for destroy, and we don't need it for toggle_status.
Coming down we'll add the same one here...
The reason for this why this is necessary is because we need to have access in the layout file and we need to know that we always have access to this instance variable. I'm going to come here, paste all this in, indent it and now, either the blogs controller or the topics controller. Which, both utilize the blog layout will now make this available. If we come to our sidebar. So, our _blog_sidebar.html.erb
now we can add a new module here so I'm going to paste that in. I can grab this class, create one more div. Now, our topics will be shown right here. I can create a little heading here called topics and now we can iterate over it. I'll say...
our sidebar_topics here.each |do|. Then here we can just call it topic because. Whenever you have a block variable it is only accessible inside of the block so this isn't going to conflict with anything else. I'm going to say end and now with this in place inside of the block we can simply print out a link. Inside of a p_tag I'm going to go and say link_to and then it's going to be a topic.title and then topic_path passing in topic close out our erb. This should be all that we need. Let me hit save and coming to the terminal looks like we already have this open so we should be able to test this out. Hit refresh and see if this is working. There we go! As you can see now we have a list of topics.
This is this is pretty much exactly what I want. Notice here and if you're wondering how we have all of our topics here but in other words, we're listing them here but this isn't the full set of them. This means that our database query is working. Notice here we have Topic 2, coding exercises and rails. But we have a topic 0 and we have a topic 1. But if you click on these you may notice that they don't actually have them and we can definitely if we don't want even show them in the index page we can simply put in our with blogs scope and this page could be updated as well. I'll leave that up to you if that's something you want to do or not. Let's just make sure this is working. I'm pretty sure it is.
But if we come to a topic 2, click on edit and say topic 0. Topic 0 should now be populated. If I hit submit there we go! Topic zero is now listed and feel free to order these however you want. But for right now I believe they're just going to be shown up with whichever one has been updated last. As you can see that is now working properly. This is in the blog layout or I should say both of them are in the blog layout. This is using the blogs controller, so we're on the blog show page. If I click on one of these we're using a different controller, the same layout but notice how this is still working perfectly and it's very seamless. Which is exactly what we're wanting to do.
Before I end, I want to take one little sidebar and talk about what we did with these controller actions a huge part of building Rails applications is utilizing the methodology of 'do not repeat yourself' but we have a little bit of duplicate code here. We have this @sidebar_topics we have it here and we have it here. There's a little bit of duplication and there are a few ways that we could change this if we wanted to. I'm going to leave it up to you if you want to or not. There is no actual rule that says that you can't have a little bit of duplicate code in your application.
There are times when it makes logical sense to have it there because...and this is what my rule of thumb is if it's a very trivial kind of feature like something we have here it's not going to require very much change or anything like that then I don't have a huge problem with it. How are some ways that we could refactor this? One, we could come to the controller and we could create a controller concern and then we could simply call it from here. That's definitely one way I don't have a problem with doing that. Additionally, we could have instead of having a topic's controller we could have simply created a topic index and a topic show put that inside of the blogs controller. I don't love that that implementation, mainly because then our blogs controller is going to break a very important rule. Which is the 'single responsibility principle'. Our blogs controller would be managing blogs and topics which I don't think is the best way of doing it. I like having the topics have its own controller and the other reason why I haven't built out a concern or why I didn't follow that is just because right here you may notice it's fine for us to have this before action running but with our other controller we only wanted it running for certain items.
It would be 100% fine to create a concern and then call it in here the same way that we've done with our application controller. We could just create another concern, call it something like blog sidebar, topic sidebar or something like that. Then we wouldn't call it from the application controller we'd call it from the blogs and then the topics controller. I'll leave it up to you if you want to refactor it. Then you could do that and then you wouldn't need to have this. I personally kind of like being able to pick and choose the actions that I want this shown on and that's the reason why I'm going to keep it like this for my own app. But I didn't want to just do that and say trust me, that's why I'm doing it. I did put some thought into it and I did consider and weigh both the options it mainly came down to the fact that I like being able to add some exceptions for different actions I didn't want it called on and that's because it does run a database query, it runs this. I would rather it not run on every single action. This is just more of a straightforward approach. Let's switch back to here. Let's close this one off. I think we're good to go on that. If I type git status
(it'd probably help if I typed it right). I can say, git commit -m "Implemented the blog widget for topics"
. Let's push this up and we're good to go. In the next guide we're going to walk through how we can work with our blog status update inside of the blog form.