#196 Nested Model Form Part 1
- Download:
- source codeProject Files in Zip (100 KB)
- mp4Full Size H.264 Video (16.1 MB)
- m4vSmaller H.264 Video (11.1 MB)
- webmFull Size VP8 Video (28 MB)
- ogvFull Size Theora Video (21.6 MB)
在2007年的一系列 episode中涵盖了创建能管理多个model的复杂的form。这一系列现在已经过时了,所以从这期episode我要给你们展示最新的处理这种多个model的form的方法。
使我们的处理这个问题的方法的产生差异的原始是accepts_nested_attributes_for
方法,这个方法是在Rails 2.3增加的。我们在整个系列都要用这个方法,所以你必须运行最新版本的Rails,才能把这项技术应用于你的项目中。
accepts_nested_attributes_for
的文档是值得一读的,文档中介绍了如何在一个update请求中更新嵌套属性,但是文档中对于如何让它在view中工作讲得不清除,所以我们主要关注这方面。
我们的调查程序
让我们先看一下我们我们要在这集建立的调查程序。该应用程序将有一个用于创建和编辑Survey(调查)的复杂的form,这个form让我们输入调查的名字,若干问题,每一个问题有多个答案。这个form有一些links,允许我们从一个调查中动态的创建和删除问题和答案。
用于创建和编辑调查的复杂form
我们在这里有一个深层嵌套的关联,一项调查中,有许多问题,问题有许多答案。这个在之前的复杂form的系列中是不可能的,不可能创建象这样的深层嵌套的form,但Rails 2.3 以后我们可以。
入门
我们将从头开始创建我们的调查申请,因此我们将首先创建一个新的Rails应用程序surveysays
为了使编写应用程序更容易使用,我们将使用两个 Ryan Bates 的 nifty generators。我们将使用nifty layout generator,创建一个应用程序的layout。
``` terminal script/generate nifty_layout ```我们的应用程序将有三个model: Survey
, Question
和Answer
。我们将开始与Survey
model,并使用nifty scaffold generator去创建sacaffold。Survey
将只有一个属性叫name
。
接下来,我们运行migrate去创建surveys表。
``` terminal rake db:migrate ```因为我们有scaffold当我们查看这个应用程序的时候,我们能列出,创建和编辑survey,并且我们有基本survery form。
我们想在form上出现那些我们可以给survey添加问题和答案的field。第一步,我们将生成Question
model。这将有一个survey_id
field 和用来放置问题内容的 content field。
完成后,我们将再次migrate数据库,创建questions
表。
接下来,我们将在他们各自的model文件上建立Survey
和Question
之间的关联。
请注意,在Survey
,我们使用:dependent => :destroy
,当我们删除了一项调查其所有问题都被删除了。
在Survey
的model,我们将使用accepts_nested_attributes_for
,它使我们能够通过Survey
管理 questions。利用这一点,当我们更新调查的属性的时候,我们可以创建,更新和删除问题。
创建form
建立Survey
和Question
的model后,我们将建立调查的 form我们想要做的,给每一个调查的问题在form上创建一些fields。我们可以使用fields_for
方法在一个form中管理关联的fields,传递给它的关联的model的名称,循环所有关联的记录,然后为他们创建form builder。这个builder会为每个问题渲染一个label和一个textarea。
<%= f.label :name %>
<%= f.label :name %>
<%= builder.label :content, "Question" %>
<%= builder.text_area :content, :rows => 3 %>
<%= f.submit "Submit" %>
<% end %> ```当我们刷新form的时候,它看起来像以前一样。这是因为一项新的调查将不会有任何问题与之关联,因此,没有问题的field被显示出来。最终我们希望在form上有一个”Add Question“的链接,但现在我们通过SurveyController
的new acton 建立一些问题。
上面的代码将会给一个调查建立三个问题,当我们刷新页面时我们就会看到。我们现在可以填写姓名和前两个问题,然后提交新的调查。
当我们的提交后,一个新的Survey
记录将被创建,但因为我们没有在页面上展示问题,所有我们不会看到它的问题。为了解决这个问题,我们可以修改的Survey show
,显示一项调查的所有问题。
Name: <%=h @survey.name %>
-
<% for question in @survey.questions %>
- <%= h question.content %> <% end %>
<%= link_to "Edit", edit_survey_path(@survey) %> | <%= link_to "Destroy", @survey, :confirm => 'Are you sure?', :method => :delete %> | <%= link_to "View All", surveys_path %>
```当我们重新刷新调查的页面,我们会看到列出的问题,这说明,当我们添加调查的时候问题也被保存了。
我们还可以编辑的一项调查,当我们提交这个form的时候,对于任何问题的改变都会被提交。
在我们的网页上面列出的有三个问题,尽管我们只输入了前两个值。最后的空白问题如果会被自动删除,那就更好更好了。该accepts_nested_attributes_for
方法有一个reject_if
选项,我们可以用这个做。该方法接受一个lambda
,hash属性传递给它,我们可以使用这个hash,当问题的content
是空白的时候,拒绝保存问题。
如果我们现在创建的survey只有在只有两个问题field被添入了,那么只有这两个question会杯保存,我们不会看到列表中的空白的问题。
如果我们想删除正在编辑的调查中已经存在问题,该怎么办呢?在最终的应用程序要使用link来删除问题,但现在我们将采取更容易实现,用一个checkbox。在调查的的form partial中,我们将添加一个checkbox和一个lanel。
``` codeFilePath /app/views/survey/_form.html.erb ``` ``` ruby <% form_for @survey do |f| %> <%= f.error_messages %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= builder.label :content, "Question" %>
<%= builder.text_area :content, :rows => 3 %>
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, "Remove Question" %>
<%= f.submit "Submit" %>
<% end %> ```这里的窍门是给name复选框属性指定_destroy
。当这个复选框的value是true
,复选框被选中,该记录将在表单提交之后被删除。
为了使它项工作,我们需要Survey
model中开启它,在 accepts_nested_attributes_for
后加入:allow_destroy => true
。
当我们刷新页面,我们对于每一个问题将有一个“Remove Question”的checkbox。
如果我们选中一个问题的“Remove Question” checkbox,提交表单,这个问题就会被删除。
添加答案
现在,我们有问题了,但没有答案。我们现在通过创建Answer
model,建立嵌套表单。首先,我们将生成的model。
然后migrate 数据库。
``` terminal rake db:migrate ```接下来,我们将建立Answer
和Question
的关联。Answer
将belong_to Question
。
对于Question
,我们将需要使用accepts_nested_attributes_for
,就像我们在Survey
model里做的那样。
在form中,我们需要给答案添加field,但如果我们再给答案增加嵌套模型,答案的form的view将会变得混乱。将来,我们将要使用JavaScript通过link添加问题,因为这两个问题,我们要吧form部分的代码移入question_fields
partial。
经过整理后的form view变成这样。
``` codeFilePath /app/views/surveys/_form.html.erb ``` ``` ruby <% form_for @survey do |f| %> <%= f.error_messages %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit "Submit" %>
<% end %> ```请注意,我们只是传递了partial的name的字符串到render,新form的简便写法是Rails 2.3 引入的。我们以f
为名字把builder
传到partial。在新的question_fields
partial,我们可以使用f
变量去render Question
的表单元素。
<%= f.label :content, "Question" %>
<%= f.text_area :content, :rows => 3 %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove Question" %>
我们可以用类似的方法处理答案部分的field,把他们放在自己的partial文件中。在_question_fields
partial中,我们将要循环所有问题的答案,render新的名为_answer_fields
的parital。
<%= f.label :content, "Question" %>
<%= f.label :content, "Question" %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove Question" %>
在我们的新_answer_fields
partial,我们将放入一些代码去render一个Answer
。
<%= f.label :content, "Answer" %> <%= f.text_field :content %> <%= f.check_box :_destroy %> <%= f.label :_destroy, "Remove" %>
```因此,我们可以在表单上看到答案的fields,我们将修改SurveyController
的new
action,三个问题中的每一个问题将会有四个答案。
现在,在你创建一各调查的时候,将会创建3个问题,每个问题有四个答案。
当我们填好表单,提交受答案没有出现,但我们可以很容易地解决这个问题。在调查的show
view中,当我们render每一个问题的时候,我们将加入一些代码render这些问题的答案。
-
<% for question in @survey.questions %>
- <%= h question.content %>
- <%= h answer.content %> <% end %>
-
<% for answer in question.answers %>
如果我们刷新调查页面,我们将会看到问题和答案。
我们还没有完全完成,因为我们希望能够通过link在form上动态添加或删除问题或答案。我们将在下一个episode涵盖。