This will guide you through building a simple Pinterest app from scratch. The app will have two models (User
and Item
), where User has_many
Items. A User is just identified by a name, and and Item has a name and a price. The User will be a Devise model that supports authentication, and the Item will have Paperclip attachments. Today, we'll just build out the functionality to view all the items, and create a new one.
These notes follow this video.
A good place to start is with your models—this is why it's important to sketch out your app on paper before you start. I've done my sketching above, so I know exactly what to write (in the shell):
rails g model User name:string
rails g model Item name:string price:float # user_id:integer
In the video, I forgot to add the user_id
field the first time—a result of poor planning! So I had to create another migration to add the user_id
column. I took advantage of some nice Rails g syntax:
rails g migration add_user_id_to_items user_id:integer
Then migrate
rake db:migrate
Next step is to generate routes. I decided during the video that the main page will show all the items (effectively the index
action), and have a form to create a new one (effectively the new
action).
# routes.rb
root to: 'items#index'
post '/items', 'items#create'
Now let's make the controller. Type in the shell:
rails g controller items
Edit the generated file:
# app/controllers/items_controller.rb
def index
@items = Item.all
end
def create
# TODO
end
Last step: create the view! Aside from rendering all the items, it's good UX (user experience) to give a sensible message when the list is empty. I've done that with an if statement that checks if the @items
array is empty with the .any?
method.
<!-- app/views/items/index.html.erb -->
<h3>Viewing all Items</h3>
<% if @items.any? %>
<ul>
<% @items.each do |item| %>
<%= render partial: 'item', locals: {item: item} %>
<% end %>
</ul>
<% else %>
<p>There are no items!</p>
<p>Create one above!</p>
<% end %>
I factored out rendering the item in to a partial. That's somewhat optional—I think it help keep code organized, but you don't have to use it if it's confusing to you. Create a new file app/views/items/_item.html.erb
:
<!-- app/views/items/_item.html.erb -->
<p><%= item.name %> | <%= item.price %></p>
Now if I go to http://localhost:3000
in my browser, it should show "There are no items!". Hooray!
Now that we have a basic framework for the application, we'd like to make functionality to create a new item. As with anything this starts in the routes.rb
file. We've been smart however—foreseeing that we'd like to add items in the future, we've already added post 'tweets' => 'tweets#create'
. Sweet, we can go straight to the controller. We need to create a new item that will eventually get saved.
# app/controllers/items_controller.rb
def index
@items = Item.all
@new_item = Item.new # The new item
end
In the view tweets/index.html.erb
, we add a line to render a form for that new item:
<!-- app/views/items/index.html.erb -->
<h3>Viewing all Items</h3>
<%= render partial: 'form', locals: {new_item: @new_item} %>
<!-- More code below... -->
Again, I'm factoring code out into a partial to keep it organized, doing so is optional. Now we create a new file called app/views/items/_form.html.erb
:
<!-- app/views/items/_form.html.erb -->
<%= form_for new_item do |f| %>
<%= f.text_field :name, placeholder: "Item name" %>
$<%= f.number_field :price, placeholder: "Item price" %>
<%= f.file_field :picture %>
<%= f.submit %>
<% end %>
Now when you try to refresh to refresh the page, you'll get an error—something about an undefined items_path
. This happens when you try to use form_for
—Rails tries to guess the URL, but in this case, cannot. To fix it, we just specify the URL and method manually (notice the added url: 'items', method: 'POST'
):
<!-- app/views/items/index.html.erb -->
<%= form_for new_item, url: '/items', method: 'POST' do |f| %>
<%= f.text_field :name, placeholder: "Item name" %>
$<%= f.number_field :price, placeholder: "Item price" %>
<%= f.file_field :picture %>
<%= f.submit %>
<% end %>
Great we have a form! Submitting it does nothing; we haven't put anything in the create
action yet:
# app/controllers/items_controller.rb
# ...index action above
def create
item_params = params[:item].permit(:name, :price) # Only allow the name and price
@item = Item.new(item_params)
if @item.save
redirect_to root_path
else
redirect_to root_path
end
end
Why do we do the silly if @item.save
with identical outcomes at the end? We're again foreseeing that if the item doesn't save, we'd like to do something different than if the item does save. However, we don't want to worry about that now. More exciting is that we can actually create items! Make sure it works by visiting localhost:3000, filling out the form, and submitting.
We'd like to add some validations to price so that we only allow users to create sensible items. I'd like every item to have a name, and a positive number as a