4

在开发 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工作变得更好的方法吗?
  • 有没有更合适的帮手?
  • 我在这个问题的某个地方推理错误吗?
  • 你将如何做到这一点?

谢谢

4

1 回答 1

1

我希望我理解正确吗?

这些组是预定义的,您希望能够将用户添加到组中。在编辑用户屏幕上,您显示所有或部分预定义组。您想通过勾选复选框并保存记录来添加用户。如果您取消选中该复选框,则该用户在未选中组中的成员资格应为零。

以下是我对公司和项目的处理方式:

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

这有帮助吗?

于 2011-02-03T10:02:43.450 回答