在开发 Rails 3 应用程序时,我遇到了嵌套表单的问题。
一个集合是一组预定义的对象(使用 db:seed 创建)。另一个集合应该显示一个允许选择一些元素的表单。
一个例子比一个长描述更好,所以在这里。
假设您有 2 个模型:用户和组。
假设有几个组:Member、Admins、Guests、...
您希望您的用户有多个组,因此您需要一个中间表:成员资格。
模型代码很明显:
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
控制器不需要更改。然而,这种观点更为复杂。
我想显示一个复选框列表以选择几组预定义的复选框。
我在这里使用_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。(这在创建和更新之前在控制器中调用)
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
fields_for
这看起来很不对,而且它在控制器中添加了很多逻辑,只是为了控制视图助手的不良行为。
另一种解决方案是自己创建所有表单 html,尝试模仿 Rails 约定,并避免 id 问题,但这在代码中确实很吵,我相信有更好的方法。
- 这是一种让
fields_for
工作变得更好的方法吗? - 有没有更合适的帮手?
- 我在这个问题的某个地方推理错误吗?
- 你将如何做到这一点?
谢谢