Rails如何知道form是New/Edit,submit是Create/Update

疑惑

这个问题起源于董明娟提出一个JDStore中的疑惑:
在app/views/carts/index.html.erb中

<%= form_for cart_item, url: cart_item_path(cart_item.product_id) do |f| %>
    <%= f.select :quantity, 1..cart_item.product.quantity %>
    <%= f.submit "更新", data: { disable_with: "Submiting..." } %>
<% end %>

这个cart_item的form,rails是怎么知道request要提交到CartItemsController的update方法中去的?

错误认识

我最初一直以为只要设置好这个url: car_item_path(cart_item.product_id)form自然就知道要过去了,但是,转念一想,发现,这里只提供了部分信息,只提供了URI地址,在没有写单独注明controller的情况下,form顶多它要请求的网址是 /cart_item/:id,然后需要去到CartItemsController而已,当并不知道对应的action是什么?

探索

我们再看一下rake routes的结果

会发现create和update的URI并不一样
这时,你会觉得我之前说的是错的,rails就是靠设置这个url来判断是create还是update
那么,我在这里先告诉你,经过测试这个并不能决定最终会提交到哪个方法中。
为什么?(还是先说正确答案吧)

正确答案:Rails是通过判断与这个form绑定的对象是否在依然在数据库中有效存在,来决定这个form是edit_from还是new_form,从而也就决定了其将来时请求到update还是create

不是很明白?举个例子吧。
在一个只有Post的Model的Rails项目中
app/view/posts/_form.html.erb

<%= form_for post do |f| %>

        <div class="field">
            <%= f.label :title %>
            <%= f.text_field :title %>
        </div>

        <div class="actions">
            <%= f.submit %>
        </div>

<% end %>

Rails会通过post.persisted?来判断
post.persited? == true就说明数据库中已经有这个post的存在,于是就判定其为edit_form
post.persited? == false就说明数据库中还没有这个post的存在,于是就判定其为new_form

接着,就会很好奇,这个edit_form与new_form到底有什么不同,因为从页面上看起来都一样啊?
于是,我们继续做实验:打开一个edit页面与一个new页面,然后用inspect检查器看看html到底有些什么不同?

虽然form里的method的都是post
但是edit_form中,还有一个隐藏的input

<input tyep="hidden" name="_method" value="pathch">

我们在PostsController的Update方法开头打一个断点,然后查看params,得到如下内容

这个时候我们可以再回看这张route图

update的http请求method就是PATCH

那么也就是说,rails是根据object.persisted?来判断edit/new
如果是edit就在form里增加一个隐藏的input,在这个input上定义一个_method: "patch"的属性,
最终通过它来告诉Rails我点击submit按钮的时候,是要create还是update

又一问

那么,相信很多人,马上就会问,为什么不直接把form的method设定为patch?
答案就是:大多数浏览器只默认支持GET与POST,而不支持patch,所以就采用了这种隐藏input的巧妙方法
更详细的叙述,请看这两个链接:
PATCH vs. POST
Are the PUT, DELETE, HEAD, etc methods available in most web browsers?

读form_helper源码

接下来我们再来放个大招,读一下Rails负责生成form的form_helper源码
rails/form_helper.rb at 4-0-stable · rails/rails · GitHub

注意看这一句,
当object存在与数据库中的时候,就把action设置为:edit,method设置为:patch
按object不在数据库中存在的时候,就把action设置为:new,method设置为:post

趣味实验

大家可以去把controller里的new方法做如下改动

然后去new里面提交一下代码,看看,最终submit到create方法还是update方法中去了

到此,相信对rails如何生成edit/new form和如何做submit判断create/update已经比较清楚了