Since March this year, I've been in the process of learning Rails, I've been on Treehouse learning Ruby in my spare time for around 2 years so decided it was time to learn how Rails works and begin to understand it in all it's "magic", This is hopefully the first of a few blog posts to document my learning curve and give me (and others) a resource I can come back to later when I forget how things work.
A fundamental part of Rails is the MVC layout, this stands for Model, View and Controller which are the three main elements of Rails applications.
To begin, let's just create a simple app, I prefer using Postgres as I've had troubles with Gemfile dependencies for the default Sqlite3 (something else for me to learn to deal with later) so I'll specify that in my command into Terminal.
rails new demo --database=postgresql
This will create our app named "demo" in the current directory, the next thing to do is to shift into that application folder using Terminal.
cd demo The general rule for order of creating a MVC layout is to follow the order of the letters, beginning with your model, moving onto your view(s) and finishing with your controller, but this isn't a hard and fast rule, just a common practice, for ease of this post, I'm going to break this rule and go MCV, but there are plenty of stackoverflow posts to show why it's more commonly done MVC. So let's begin by creating our Model
A Model defines what something is, and what abilities it has.
I'm going to create a simple food inventory, maybe for knowing how much food we have left in our cupboard.
rails generate model Food name:string size:integer
This will create the following migration file.
class CreateFoods < ActiveRecord::Migration[5.2] def change create_table :foods do |t| t.string :name t.string :size t.timestamps end end end
This seems good to go for our simple application so run
rake db:migrate in order to run the migration.
Now that we've created our Model, let's go ahead and generate our controller.
A Controller handles all the complex logic, rendering partials, saving things to the database and giving feedback to the user.
rails generate controller foods
Notice that the controller is labeled as a plural of the model, and there is no uppercase letters. If it is done the wrong way they may have trouble connecting to each other.
This will generate the following.
class FoodsController < ApplicationController end
As you can see, it's not really doing anything, so we need to add some methods which we can use to modify and add to our application, like so...
class FoodsController < ApplicationController def index end def show end def new end def create end def edit end def update end def destroy end private def allowed_params params.require(:food).permit(:name, :size) end end
These are all the methods we would need for a typical application, and while they're empty now, we will come back and add to them in a little while. For the meantime, we need to link our Model to our Controller we do this by going into our
/config/routes.rb file. and add the Rails helper method
resources :foods to our list. This will create the routes necessary for our FoodsController. This could also be written long form, with each route specified.
get "/foods", to: "foods#index" get "/foods/:id", to: "foods#show" get "/foods/new", to: "foods#new" post "/foods", to: "foods#create" # usually a submitted form get "/foods/:id/edit", to: "foods#edit" put "/foods/:id", to: "foods#update" # usually a submitted form delete "/foods/:id", to: "foods#destroy"
This is a good time to explain Routing. Although generally you will only have
resources :id there is a way to see your list of Routes. Go back to your terminal and enter
rake routes. and you'll be able to see the above list.. I found it took a while for me to get used to reading the routes in that format, but it's really useful once you put some time in!
A URL in a browser sends a request to our router (config/routes.rb) which will analyze the URL and find and send to matching action in matching controller, which will try to match with the view.
Now that we've seen how the model and controller work, and now that the controller is searching for a view, let's create all the necessary views.
A View is a template, something shown to the user.. most often a HTML document.
In your editor find your "app/views/foods" folder and create the file "index.html.erb", follow suite with a "show", "edit" and "new".
To begin with, let's create our "new" view. First, we need to edit our FoodsController, adding to our new method.
def new @food = Food.new end
This creates a new instance of our food Model and assigns it to the
@food instance variable. Now we head over to "new.html.erb" and create our page. We just want a simple form to add a food name and size to our database..
<%= form_for @food do |f| %> <%= f.label :name %> <br> <%= f.text_field :name %> <br> <%= f.label :size %> <br> <%= f.text_field :size %> <br> <%= f.submit 'Save Food Item' %> <% end %>
Now it's time to start your server, go to http://localhost:3000/foods/new and have a look. Your form will be there, and you'll be able to add to your database, but without other routes defined yet. You can't see, edit or delete that data and trying to save the information will throw a "Template is missing" error for "foods/create" so let's add that action.
def create @food = Food.new(allowed_params) if @food.save redirect_to foods_path else render 'new' end end
So this method take the instance of the food model we entered into our form and attempt to save it, if it is successful we'll be redirected to our index method (via
foods_path) or if not, it will direct us back to our New form.
Because you'll be sent to our Index page, it'll be helpful if we created one now. To begin, add to your index method.
def index @foods = Food.all end
This take all the foods from the database and lists them on our Index views page, which we need to edit to all in to do that. Add the following to your view.
<% @foods.each do |food| %> <p> Name: <%= food.name %><br> Size: <%= food.size %> | <%= link_to 'Edit', edit_food_path(food) %> </p> <% end %>
This loops through each of your database items and places then in a separate paragraph. It's a good idea to be trying these new functions as you go in your browser to see if you run into any problems. As you can see, I've also begun my edit function on the previous code sample for the sake of saving space. It's time to give functionality to that edit button in the Controller, incase we need to update our inventory as we use items.
def edit @food = Food.find(params[:id]) end
This gives the Edit function the ability to search through the database for the correct item to edit, identified by the
For your view, just copy and paste from your "new.html.erb" into your "edit.html.erb" This will provide the form to Edit (with profiled fields) but if you try it out, you'll find it won't save the updated values, showing us our error is that theres no Template for our FoodsController's Update function, so it's now time to add that.
def update @food = Food.find(params[:id]) if @food.update_attributes(allowed_params) redirect_to foods_path else render 'new' end end
This works in a similar way to our Edit and Create Functions combined. Finding the item, updating it's attributes, and depending on whether or not it can be saved, redirects us to where we need to be.
Two routes that are useful to have on our index page are Adding an item, and deleting an item. Editing your index.html.erb file like so will adding these links.
<%= link_to 'Add Food', new_food_path %> <% @foods.each do |food| %> <p> Name: <%= food.name %><br> Size: <%= food.size %> | <%= link_to 'Edit', edit_food_path(food) %> | <%= link_to 'Delete Item', food, method: :delete %> </p> <% end %>
The Add Food link will work automatically, but your Destroy method in your FoodsController will need to be edited. It should read..
def destroy @food = Food.find(params[:id]) @food.destroy redirect_to foods_path end
After adding this, go ahead and delete one of your records, and see that it deletes it and immediately redirects us back to our
foods_path which is our index page.
The final method we need to add is our Show method. We'll us this to allow us to look at each item individually. Where our name attribute is shown in index.html.erb, edit it the following way.
Name: <%= link_to food.name, food_path(food) %><br>
This will lead up to our show method (as you can see if you use your
rake routes command in terminal again) Now we need to give it a view to display in "show.html.erb"
<h1><%= @food.name %></h1>
This will display the name of a variable as a header, which has yet to be defined. All we need to do is edit our Controller's Show function to search for our item and assign it to the variable our view is wanting to display.
def show @food = Food.find(params[:id]) end
Once this is done. we have all of our Routes accounted for and now we have this basic setup, it's just a case of duplicating it's ideas for more complex tasks.
I hope this was as helpful for you as it was for me writing it! I would appreciate any feedback, corrections and questions as I'm excited to keep learning and learning to teach these concepts.