form 与 表单对象
关于提到的各种 helper, 多看官方文档:
英文: http://guides.rubyonrails.org/form_helpers.html
中文: http://guides.ruby-china.org/form_helpers.html
也可以看 API 中的文档(写的也特别细) http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for
一个 form 表单有很多个参数怎么办?
对象的属性越多,传递过来的参数就越多,上面的赋值语句就越多。
我曾经见过 有 20 行语句, 都是: request.getParameter('…')
所以,解决方法: 使用表单对象 form object。来对这么多个参数进行封装.
使用 ORM 后, 做数据库操作时, 有三个层次要参与:
- form object
- ORM model
- 数据库 table
例子:
一个 article 表单,有两个属性: title, content
- 纯 html
rails 是可以自动分辨, 某个参数的值,是: 字符串,数组, 还是 hash
<form action='..' method='..'>
<input type='text' name='article[title]'/> params[:article][:title]
<input type='content' name='article[content]'/> params[:article][:content]
</form>
- form object:
在 rails 中是隐形的。你看不到它的声明。因为:它是在运行时,被 rails 中的某些方法动态创建的。动态创建方法的例子(javascript)
my_string = 'function hi(){ console.info("hi") }' "function hi(){ console.info("hi") }" eval(my_string) undefined hi() # =
( 在其他语言和框架中,这个对象,都是 显式 声明的(你的手写出来), 在 struts 中就要这样)
rails 中, 动态创建的 form object, 理论上是这样: (form 中包含什么参数, 或者说数据库中有多少列, 就有多少 attribute)
class Article
attr_accessor :title
attr_accessor :content
end
通过 form_for 来使用表单对象.
所以,rails 中, form 就要这样写:
<% form_for @aritcle do |f| %>
<%= f.text_field :title %>
<%= f.text_field :content %>
<% end %>
上面的重点,在于:do ... end
. 它说明了 rails 是如何调用 上面的隐形的表单对象的。
这一句:
<%= f.text_field :title %>
就是调用了 上面的 Article 的 attr_accessor :title
.
如果说 article 是个表单对象的话,
- 编辑 article 的时候, 要显示原来的值, 就是:
article.get_title
- 保存 article 的时候, 要保存传过来的值,就是:
article.title=
在上面的 do |f| ... end
中, 这个 f
就是表单对象.
f.text_field :title
不但会生成一个 <input type='text' />
标签, 而且还会通过调用表单对象的 .get_title
方法, 来为这个 <input/>
标签设置初始值.
- ORM model.
app/models
目录下的 article.rb
:
class Article < ActiveRecord::Base
# rails会自动生成: title, content 的 accessor
end
( 也可以认为, 在 Rails 中, ORM model 跟 form object model 是一个文件。
- DB table:
articles 表:
- 第一个列:title
- 第二个列:content
表单对象与持久层, 在 Rails 中是一个.
- 表单对象(处理表单代码时, 把参数保存到 对象中)
- 持久层(把对象中的数据保存到 DB)
在 rails 中这两个是一样的东西。
使用 Hash 为 Model 赋值.
Rails 形式: 它的宗旨就是 方便程序员, 对人友好:
- 一个属性一个属性的赋值
article = Article.new({
:title => params['article']['title'],
:content => params['article']['content']
})
article.save # 在这里执行 insert into articles values (...)
- 直接给构造函数一个 hash , 再 save
article = Article.new params['article']
article.save
- 把 new … save 的步骤, 省略成: create
Article.create params['article']
form_for
下面是一个最常见的 Rails 表单:
<%= form_for @user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
form_for 是个方法。 要三个参数:
form_for(record, options = {}, &block)
对于下面的代码:
<%= form_for @user do |f| %>
record
是 @user
, options
, 就是 {}
, 所以被省略掉了。 最后一个参数,是 block
.
为什么,第二个参数可以被省略掉?
因为, 在 ruby 当中, 规定: 如果一个函数的参数中,有 block, 那么这个 block, 必须是最后一个参数。
所以,ruby ,要解析一个函数的时候,一开始,就能知道, do ... end
之间,这是一个 block, 所以,它就是最后的参数。
而:form_for @use
r, 表示,form_for
的第一个参数,就是 @user.
所以,根据 form_for 的定义:
- 第一个参数,有了。
- 第二个参数,在定义中,就是: 可选的。 (options = {} )
- 第三个参数:(&block) 也有了。
所以这个函数就完整了,可以正常运行了。
下面的 f.label, f.text_field, f.submit
, 中的 ``f:
<%= form_for @user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
这个 f
, 就是 block 中的参数,也可以直接把它看成: form object
. (表单对象)
为什么要用 rails 的自定义 html 标签呢?
例如:
form_for,
form_tag,
text_field,
select_tag
而不是:
<form> , <input> ...
答: 为了更加简单
例如, 比较下面两种写法:
- form 的 rails 写法:
<%= form_for @post, html: { class: "edit_post", id: "edit_post_45" } do |f| %>
<%= f.text :title, '我是标题' %>
<% end %>
- form 的 HTML 写法:
<form action='/posts' method='post' class='edit_post' id='edit_post_45' >
<input type='text' name='post[title]' value= '我是标题' />
</form>
我们发现两者差别不大.
但是, 在做 编辑某个记录的时候, 我需要:
- 生成一个 form object
- 生成的 html 标签, 带有默认值.
<%= form_for @post do |f| %>
<%= f.text_field :title %>
<% end %>
例如:某个下拉框,有 100 个选项. 就要搞 100 次循环。然后,还要判断默认值。 那个时候,就会觉得代码特别的臃肿。
form_for 很智能的地方
过程: rails 发现了 form_for 的唯一参数: @post, 它就会开始按照 " 约定” 来猜测 各种参数: form_object, method, action …
所以, 对于 新建操作的 form:
<form action='/articles' method = 'post' >
</form>
和对于编辑操作的 form:
<form action='/articles/3/edit' method = 'post' >
<input type='hidden' name='_method' value='put'/>
</form>
变成 ruby 代码的话就是:
<% if @article.id.present? %>
<form action='/articles/3/edit' method = 'put' >
<% else %>
<form action='/articles' method = 'post' >
<% end %>
于是, 我们就可以用:
<%= form_for @post %>
这段代码, 来表示上面提到的两种场合 (@post 是已经存在的记录, 还是没有存在的记录).
form 中的 authentity_token
每一个 <form>
中, 都有一个 authentity_token
. 防止注入攻击 (XSS).
比如说, 我在购物时, 需要提交表单, 付费 . 这个表单当中, 很可能就是:
<form ... >
<input name='price' value='1000'/>
</form>
这个 form 是非常好伪造的. 所以, 需要用 authentity_token
来识别.
<form ... >
<input type='hidden' name='authentity_token' value='a1b2c3d4.....' />
<input name='price' value='1000'/>
</form>
authentity_token
是由服务器端 (rails) 生成的. 它针对不同的 browser/session
生成不同的 token . 于是我们就可以在服务器端做验证了.
class PostsController ...
protec_from_forgery # 这行代码的作用: 对每个form 做验证,看里面的token 是否跟服务器端匹配.
end
```ruby
<% if @post.id.present? %>
<form action='/posts/3/edit' method = 'put' >
<% else %>
<form action='/posts' method = 'post' >
<% end %>
<input type='hidden' name='token' value='<%= generate_token %>' />
...
所以使用 form tag , 就再也不用人肉写上面那行的代码了。
<input type='hidden' name='token' value='<%= generate_token %>' />
如果使用了 form tag, 就可以自动生成 csrf token 了:
<%= form_for @post do |f| %>
使用 form object 生成输入项的默认值.
text_field, select 如何保证在编辑某个属性的时候, 它能选中了某个默认值?
<select>
<option name=...> value</option>
<option name=... <%=if @post.title == 'xx' %> selected <% end %>> value</option>
<option name=... selected> value</option>
<option name=...> value</option>
</select>
在 rails 中, 可以使用 select 的辅助方法.
<%= select_tag options_for_select([1,2,3], default_value ) %>
FormHelper 和 FormTagHelper
两个例子,来对比说明。
区别:
- form helper:
<%= f.text_field :title %>
- 需要与
form object ( <%= form_for @book do |f| %> .. .<% end %> )
配合使用。 - 对于生成的
<input type='text' name="student[age]"/>
中, 它的name
是自动生成的。 例如:name="student[age]"
,student 必须是某个 model 的实例 并且, age 必须是 form object 的方法(也就是 数据库的列。)
优点: 可以简化我们对表单项的操作。(例如: 下拉单 或 文本框的 默认值)
- form tag helper:
<%= text_field_tag 'my_title' %>
- 可以独立使用。 跟表单对象无关。
- 名字可以随意取。 name='abc' 优点: 特别灵活。 可以脱离表单对象使用。 而且 便于理解。
相同点: 作用是一样的。
例子 1:
<%= f.submit "OK"%>
<%= submit_tag "OK" %>
都会生成:
<input type="submit" name="commit" value="OK">
例子 2:
<%= f.text_field :title %>
<%= text_field_tag 'article[title]' %>
都会生成:
<input type="text" name="article[title]" id="article_title">
所以说, form helper
与 form tag helper
都是一样的。 一个东西,写法不同。
甚至, 我们在 API 文档上,都可以看到 Rails 作者,告诉我们:
form helper: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
form tag helper: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html
都可以看到, xx_field 的文档中,会说: 请参考 xx_field_tag . 例如:
time_field 中:
Options: Accepts same options as time_field_tag
form_for 与 form_tag 的区别和联系
controller:
@book = Book.new
view 中, 下面两个是相同的:
<%= form_for @book do |f|%>
<%= f.text_field :title %>
<% end %>
```ruby
<%= form_tag '/books' do %>
<%= text_field_tag 'book[title]', '' %>
<% end %>
controller:
@book = Book.find(3)
view 中, 下面两个是相同的:
<%= form_for @book do |f|%>
<%= f.text_field :title %>
<% end %>
```ruby
<%= form_tag '/books/3/update' do %>
<%= text_field_tag 'book[title]', @book.title %>
<% end %>
评论区