Rails has_many чекбоксы с дополнительными атрибутами - PullRequest
2 голосов
/ 19 марта 2012

У нас есть следующий код, работающий для сложной формы рельсов с флажками.Я не очень доволен решением, которое у нас есть, и мне было интересно, если кто-нибудь знает о более правильном способе сделать это в рельсах.Весь код ниже работает, я просто хочу знать, есть ли более чистый подход.

В моем контроллере Admins я хочу убрать необходимость вызывать следующий код при каждом обновлении.

@user.admin.school_admin_roles.destroy_all
params[:roles].each do |school_role|
    ids = school_role.split('_')
    @user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
end if !params[:roles].nil?

Так что я в основном хочу иметь возможность вызывать @ user.update_attributes (params [: user]) и поручить рельсам создавать необходимые мне отношения.У меня есть что работает с AccountRole в форме ниже.Я хочу знать, есть ли способ сделать то же самое с SchoolRole, если у меня есть дополнительная переменная school_id в таблице соединений.

У нас есть следующая форма для редактирования пользователя и назначения ролей

Снимок экрана формы -> http://i.stack.imgur.com/PJwbf.png

У меня есть следующая форма, где администратор может редактировать других пользователей и назначать роли на основе учетной записи и роли на основе школы с помощью флажков.Роли на основе учетных записей было легко реализовать.Правила, основанные на школе, немного сложны, так как объединяющая таблица school_admin_roles имеет поля school_id, user_id, role_id.Мы должны были реализовать школьные роли частью формы довольно хакерским способом.У нас есть форма, реализованная следующим образом: обратите внимание, как мы взломали school.id.to_s + '_' + role.id.to_s в один и тот же флажок для школьных ролей.

В функции обновления контроллера Admins мы вручную уничтожаемвсе роли school_admin в каждом обновлении затем циклически перебирают параметры школьных ролей по идентификаторам на «-», а затем вручную воссоздают каждую школьную роль.Я действительно ненавижу способ, которым мы должны были пойти по этому поводу.Может ли кто-нибудь пролить свет на более чистый, более рельсовый подход к решению этого сценария?

Форма -

<%= form_for @user, :url => {:controller => 'admins', :action => 'update'} do |f| %>
    <%= f.label :username %>
    <%= f.text_field :username %>

    <%= f.fields_for :admin do |uf| %>
    <div class="field">
        <%= uf.label :first_name %>
        <%= uf.text_field :first_name %>
    </div>

<label>Admin Permissions</label>  
    #account level permissions works fine
    <%= hidden_field_tag "#{uf.object_name}[account_role_ids][]"   %>
    <% AccountRole.find(:all).each do |role| %>
    <div class="account_role">
        <%= check_box_tag "#{uf.object_name}[account_role_ids][]", role.id,  @user.admin.account_roles.include?(role)%>
    <%= role.name %>
    </div>
    <% end %>

   #school level permissions a bit of a hack    
    <%= hidden_field_tag "#{uf.object_name}[school_role_ids][]"   %>
        <% SchoolRole.find(:all).each_with_index do |role, index| %>
        <div class="school_role">
        <%= check_box_tag "#{uf.object_name}[school_role_ids][]",role.id, @user.admin.school_roles.include?(role) %>
        <%= role.name %>
        <span class="advanced_box admin_permissions" <% if @user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
        <div class="content" id="perm_<%= index %>">
        <h4><%= role.name %></h4>
    <% uf.object.account.schools.each do |school|%>
    <div>
    <%= check_box_tag "roles[]", school.id.to_s+'_'+role.id.to_s, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
    <%= school.name %>
</div>
<% end %>
<%= link_to 'Done', '#', :class => "done" %>
</div>
<a href="#" class="open"> Advanced</a>
</span>
</div>
<% end %>
</div>
<% end %>

Контроллер

class AdminsController < ApplicationController
def update
    @user = User.find(params[:id])
    if  @user.update_attributes(params[:user])

      # TODO find a way to refactor this 
      @user.admin.school_admin_roles.destroy_all
      params[:roles].each do |school_role|
        ids = school_role.split('_')
        @user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
      end if !params[:roles].nil?
      #

      flash[:notice] = "Successfully updated Admin."
      redirect_to admins_path
    else
      render "edit"
    end
  end
end

Учитывая следующие модели

class User < ActiveRecord::Base
  has_one :parent
  has_one :admin
  has_many :scool_admin_roles
  has_many :account_admin_roles
end

class AccountAdminRole < ActiveRecord::Base
  before_save :set_account_id

  belongs_to :admin
  belongs_to :account_role
end

class SchoolAdminRole < ActiveRecord::Base
  belongs_to :admin
  belongs_to :school_role
  belongs_to :school
end

class SchoolRole < ActiveRecord::Base
  has_many :school_admin_roles
end


class AccountRole < ActiveRecord::Base
  has_many :account_admin_role
end

Ответы [ 2 ]

1 голос
/ 19 марта 2012

Когда я сталкиваюсь с кодом, который, как я знаю, плохо пахнет, обычно это приводит меня к дизайну.

В этом случае проблема заключается в дизайне таблицы базы данных.

Вы взламываете значение, переданное из флажка с разделителем, потому что таблица «соединения» делает больше, чем просто соединение.Я считаю, что отношения со школой принадлежат SchoolLole, а не SchoolAdminRole.Изменение этого параметра приведет к созданию шаблона, очень похожего на вашу AccountRole.

Исправление дизайна модели может быть немного болезненным в настоящее время, но оно намного чище и его можно будет обслуживать в будущем.Вы будете благодарить себя позже.

0 голосов
/ 21 марта 2012

Мы изменили приведенный выше код следующим образом

В модели мы добавили acceptpts_nested_attributes_for: school_admin_roles,: reject_if => proc {| attr | атр [ 'school_role_id']. пустой? }

и добавили school_admin_roles_attributes к attr_accessible

class Admin < ActiveRecord::Base
  belongs_to :account
  belongs_to :user

  has_many :school_admin_roles
  has_many :school_roles, :through => :school_admin_roles
  has_many :account_admin_roles
  has_many :account_roles, :through => :account_admin_roles

  accepts_nested_attributes_for :account

  accepts_nested_attributes_for :school_admin_roles, :reject_if => proc { |attr|  attr['school_role_id'].blank? }

  attr_accessible :account_role_ids, :email, :first_name, :last_name, :account_id, :user_id, :account_attributes, :school_admin_roles_attributes
  default_scope where(:deleted => false)
end

Затем мы построили форму следующим образом

<% index2 = 0 %>
<% SchoolRole.find(:all).each_with_index do |role, index| %>
<div class="school_role">
<%= check_box_tag "school_roles[]",role.id, @user.admin.school_roles.include?(role) %>
<%= role.name %>
<span class="advanced_box admin_permissions" <% if @user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
div class="content" id="perm_<%= index %>">
<h4><%= role.name %></h4>
                                <%  uf.object.account.schools.each do |school|%>
<div>
<%=    check_box_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_role_id]", role.id, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
                                            <%= school.name %>
                                        <%= hidden_field_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_id]", school.id %>
                                    </div>
                                    <% index2 += 1 %>
                            <% end %>
                            <%= link_to 'Done', '#', :class => "done" %>
                    </div>
                    <a href="#" class="open"> Advanced</a>
                </span>
            </div>
            <% end %>
        </div>
    <% end %>

Что затем позволило нам провести рефакторинг контроллера без разделения идентификаторов, но нам все равно придется вызывать команду destroy каждый раз, когда я могу жить.

def update
    @user = User.find(params[:id])
    @user.admin.school_admin_roles.destroy_all
    if  @user.update_attributes(params[:user])
      flash[:notice] = "Successfully updated Admin."
      redirect_to admins_path
    else
      render "edit"
    end
  end
...