As you know, I’m working on a non-profit app using Ruby on Rails (RoR). I recently worked through a feature on the site that involved Model-View-Controller interaction, and wanted to share what I’d learned about how Rails implementation of MVC works. This is something that was difficult to discern from the documentation, at least from a high level perspective.
I’ve found that I can work through a problem better if I understand how all the pieces interact. With Rails, and MVC in general, I was having a tough time visualizing the interactions, so I wanted to write out my simiplified version, with a contrived example.
On to the example!
Say we want a webpage that displays all the items in our store that are for sale.
We would set up the View with the HTML and CSS to structure a simple list view.
<body> <div class="item-list"> <ul class="list-of-items"></ul> </div> </body>
This gives us a very simple page with one div and one unordered list.
For the Model, we can just stick with the default of
class Store < ApplicationRecord end
Then, in the Controller, we use the method
def index @items = Store.all end
Lastly, we would modify our View to use the instance variable @items to get all the items in our database.
<ul class="list-of-items"> <% @items.each do |item| %> <li><%= item.name %> - <%= item.price %></li> <% end %> <ul>
This will grab all the items from our database and loop through them, printing the value in the name column and the price column in the unordered list format.
So, what is this code doing?
First, the Controller is using the Store.all method to tell the Model (called Store) that it wants all the records from the database. The .all method is built into Rails and has the logic in it to essentially submit a “SELECT * FROM stores” query behind the scenes. The results are then stored in an instance variable, @items.
Instance variables are so named because they are created with each instance of a class. When I open the page (accessed through the Controller), I get an instance of the class, which comes with an instance of the instance variables. This creates the application state. When you open the page, you get a different instance or state. With that, the @items variable is openly available in both the Model and the View that comes along with our instance of the application. This allows us to take the instance variable in the View and loop through its elements using the .each do method. Then we can simply use the dot syntax to get the individual properties of each element and format them directly in the HTML using the <%= %> ERB syntax.
That’s it! Super simple!
But wait, there’s more!
Say you want to do a custom query in the Model and hardcode in SQL. What then?
My approach was to create a custom method in the Model that allowed me to pass variables from the Controller. So, something like this:
class Store < ApplicationRecord def self.filter_by_price (price_limit) return find_by_sql('SELECT * FROM stores WHERE price < ' + price_limit ) end end
Using self makes this method available to call directly on the class object. Now, you can create another instance variable for limited prices, like this:
def index @items = Store.all @items_below_price = Store.filter_by_price(params[:price]) end
Now, the price symbol can be passed from a form input in the View and picked up by the Controller (you can also add it to your params private object at the bottom of the generated Controller and use it that way to keep it private). We now have an instance variable that is visible to the View that we can use to create a list filtered by price.
One thing you might say is, “But I can just filter in the .each do loop in the View instead of creating a query in the Model and passing it around.” Any you’d be correct. That would probably be easier for a site with a small database size. But, if you only needed 10 records out of 100,000, would it be better to grab all 100k records and then filter them, or only grab the 10 from the database that you need?
That all depends on where you want your processing to happen. If you’re OK with it happening in the View, then by all means, filter in your each do loop. But, if you want it handled on the back end before it ever gets to your View, then do it in the Model/Controller.
So, if nothing else, now you know how information is passed around in the Rails MVC setup. For me, that was the main point of this post. I know that there are tons of things I can do to improve the code and make it more secure, which I’ll probably end up getting into later.
One big improvement that would be immediately evident is to use the .find ActiveRecord method. (All the methods for querying with ActiveRecord can be found here) With .find, you could simply do:
@items_below_price = Store.find("price < ?", params[:price])
with :price still being passed from the view.
My need for a custom SQL query in a method came up when I was using Geocoder and trying to filter based on the user’s current location and preferred distance.
I’ll get to that next!