在开发 Rails 3 应用程序时,我遇到了嵌套表单的问题。
一个集合是一组预定义的对象(使用 db:seed 创建)。 另一个集合应该显示一个表单以允许选择一些元素。
示例比长描述更好,所以就在这里。
假设您有 2 个模型:用户和组。
假设有几个组:成员、管理员、访客……
您希望您的用户拥有多个组,因此您需要一个中间表:成员资格。
模型代码很明显:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
Controller 不需要更改。 然而,该 View 更为复杂。
我想显示一个复选框列表来选择几组预定义的复选框。
我在这里使用特殊的_destroy
字段,具有相反的值,在实际未选中时销毁(因此在选中时将用户添加到组中)
%p
= f.label :name
%br
= f.text_field :name
%ul
= f.fields_for :memberships, @groups do |g|
%li
- group = g.object
= g.hidden_field :group_id, :value => group.id
= g.check_box :_destroy, {:checked => @user.groups.include?(group)}, 0, 1
= g.label :_destroy, group.name
但是,这并不能按预期工作,因为形式 g
将始终在每个组之后创建一个具有任意 id 的输入(甚至通过在 </li>
之后包含它来破坏布局):
<input id="user_memberships_attributes_0_id" name="user[memberships_attributes][0][id]" type="hidden" value="1" />
<input id="user_memberships_attributes_1_id" name="user[memberships_attributes][1][id]" type="hidden" value="2" />
# ...
了解嵌套属性的语法如下:
{:group_id => group.id, :_destroy => 0} # Create
{:group_id => group.id, :_destroy => 0, :id => membership.id} # Update
{:group_id => group.id, :_destroy => 1, :id => membership.id} # Destroy
{:group_id => group.id, :_destroy => 1} # Do nothing
每次id都发送是行不通的,因为它会尝试更新一条不存在的记录而不是创建它,当记录不存在时尝试销毁。
我找到的当前解决方案是删除所有 id,无论如何它们都是错误的(它们应该是成员资格的 id,而不是简单的索引),并在用户已经拥有该组时添加真实的 id。 (这在创建和更新之前在 Controller 中调用)
def clean_memberships_attributes
if membership_params = params[:user][:memberships_attributes]
memberships = Membership.find_all_by_user_id params[:id]
membership_params.each_value { |membership_param|
membership_param.delete :id
if m = memberships.find { |m| m[:group_id].to_s == membership_param[:group_id] }
membership_param[:id] = m.id
end
}
end
end
这看起来太错误了,它在 Controller 中添加了很多逻辑,只是为了控制 View 的不良行为fields_for
helper 。
另一个解决方案是自己创建所有表单 html,尝试模仿 Rails 约定,并避免 id 问题,但这在代码中确实很吵,我相信有更好的方法。
- 这是一种制作
fields_for
的方法吗?工作得更好吗? - 有没有更合适的 helper ?
- 我在这个问题中的推理是否有错误?
- 您将如何实现这一目标?
谢谢
最佳答案
我希望我理解正确吗?
组是预定义的,您希望能够将用户添加到组中。 在编辑用户屏幕上,您将显示全部或部分预定义组。 您想要通过勾选复选框并保存记录来添加用户。 如果取消选中该复选框,则该用户在未选中组中的成员资格应该为零。
以下是我对公司和项目的做法:
Class Company
has_many :datasets
has_many :projects, :through => :datasets
Class Project
has_many :datasets
has_many :companies, :through => :datasets
<% for company in Company.all %>
<tr>
<td>
<%= check_box_tag 'project[company_ids][]', company.id, @project.companies.include?(company) %>
</td>
</tr>
<% end %>
我列出了所有公司并选中我想要包含在项目中的公司。
请告诉我这是否对您有帮助?我相信你在你的例子中使用 haml 。我不太习惯这种表示法。
如果您想使用子集,您可以使用范围:
scope :recent, Company.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)
然后你可以像 .all 方法一样使用这个作用域:
Company.recent
这有帮助吗?
关于ruby-on-rails - 如何正确编写具有 N-N 关系的嵌套表单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3622312/