Up until now we have looked at the create
and update
controller action in Rails in the context of using a basic <html>
form tag, <form></form>
.
When you wanted to POST a new movie to your Metube app:
First you made a new
controller-action in your videos
controller. This action was mapped to the GET
route that loaded the new video form from the views/videos/new.html.erb
file with a route that looks like get "/videos/new", to: "videos#new"
in the routes.rb
file.
Looking at the new
action in the videos
controller, it's time to come clean. There was really no reason in the previous Rails CRUD lesson with the new
action that we needed to hand down this instance variable:
def new
@video = Video.new
end
It is a Rails convention in the controller for use with the form_for()
helper method. What we were doing with @video = Video.new()
is creating a blank video object hash in the @video
instance variable to pass down to the view: views/videos/new.html.erb
.
form_for(@video)
can then use blank @video
object, which looks like,
@video = {id: "", title: "", description: "", youtube_id: ""}
.. , to create a series of form tags in much the same way we hard code it previously.
Lets back up for one second before we fully look at the form_for()
helper method. Think of the form_tag(), as the Rails way of writing slightly less code to build forms.
So we could replace the old basic html form with:
<%= form_tag("/videos", method: "post", authenticity_token: form_authenticity_token) do %>
<%= label_tag('video[title]', "Title:") %>
<%= text_field_tag('video[title]') %><br>
<%= label_tag('video[description]', "Description:") %>
<%= text_field_tag('video[description]') %><br>
<%= label_tag('video[youtube_id]', "Youtube ID:") %>
<%= text_field_tag('video[youtube_id]') %><br>
<%= submit_tag("submit") %>
<% end %>
which makes to the same form we were using yesterday in the browser:
<form action="/videos" method="post">
<label for="video_title">Title:</label>
<input type="text" name="video[title]" id="video_title"><br>
<label for="video_description">Description:</label>
<input type="text" name="video[description]" id="video_description"><br>
<label for="video_youtube_id">Youtube ID:</label>
<input type="text" name="video[youtube_id]" id="video_youtube_id"><br>
<input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden">
<input type="submit" value="submit">
</form>
form_for
is another helper method that cleans up your form even more, so you can write less code. Go ahead and type the following code in your browser to replace the form_tag code.
<%= form_for(@video) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %><br />
<%= f.label :description %>
<%= f.text_field :description %><br />
<%= f.label :youtube_id %>
<%= f.text_field :youtube_id %><br />
<% end %>
You should also make the connection at this point that besides all the things we have said, the @video
passed in to the form_for method as a parameter is the part of the code that creates the forms action=""
attribute. In this case it creates action="/videos"
.
In the case of a nested object like a user's videos, you would pass in both of the objects that you needed. For example if the route was /users/2/videos/new
, and I wanted to also carry along the user who created the video in the post. My form_for()
tag might look like:
<%= form_for(@user, @video) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %><br />
<%= f.label :description %>
<%= f.text_field :description %><br />
<%= f.label :youtube_id %>
<%= f.text_field :youtube_id %><br />
<%= submit_tag("submit") %>
<% end %>
It would build the action to be the route it needs to send the request to. It would look like: <form action="/users/2/videos/new">
, since we passed in the parameters that rails would need to build the action attribute in the proper order.
There is additional abstraction we can do with forms. Go ahead and add gem 'simple_form'
to your Gemfile. Then run bundle install
.
Check this out:
You can now replace all that form code for creating a new video with:
<%= simple_form_for(@video) do |f| %>
<%= f.input :title %>
<%= f.input :description %>
<%= f.input :youtube_id %>
<%= f.button :submit %>
<% end %>
What happened to make all this produce the same thing??? Think of the simple form as everything cool that form_for()
was doing, but it is database model aware. This is one of the neat things about abstracting your database into code in your rails app. It can actually use your schema.rb
file to figure what data types each database column is, and automatically put in the labels. If you inspect element on this, you will notice too that it puts in maxlength="255"
. It is even using your database schemas to match up some form validation according to what a database could hold for a text field. Neat!
With each step of form abstraction in rails, know what it is doing. Know how you are abstracting your form tag's action attribute into the objects you are passing into methods like form_for()
and simple_form_for()
.