根据关联属性处理连接表条目

时间:2018-02-16 13:50:43

标签: ruby-on-rails nested-forms jointable

TL; DR

基于具有关联属性的表单(如条形码或板号)创建连接表条目的最佳方法是什么?

详细说明

在此系统中,记录存储位置之间的项目移动,has_many_and_belongs_to_manystorage_movements之间存在storage_items关系,因为项目可以多次移动,多个项目可以移动到一次。

这些物品以前是创建过的,并由物品附在物品上的板号识别,并记录在物品的创建上。

问题在于我需要创建一个storage_movements形式,其中用户只输入正在移动的storage_item的板号,但我无法想出一个方法来轻松实现这一点

我已经在这个墙上撞了一段时间了,我能想到的唯一解决方案是在storage_movements的新storage_items表单上创建嵌套字段,并使用特定代码通过显式查询这些板号并操纵这些操作的连接表条目来创建,更新和删除这些storage_movements的模型。

这是处理问题的正确方法吗?这个解决方案的主要问题是我似乎无法在错误的特定板号上显示验证错误(我使用的是simple_forms),因为我没有storage_item个对象来添加错误。

下面是我正在使用的表单的代码片段。欢迎任何帮助:D

# views/storage_movements/_form.html.erb
<%= simple_form_for @storage_movement do |movement_form| %>
  #Other form inputs
  <%= movement_form.simple_fields_for :storage_items do |item_form| %>
    <%= item_form.input :plate, label: "Plate number" %>
  <% end %>
<% end %>

# models/storage_movement.rb
class StorageMovement < ActiveRecord::Base
  has_many_and_belongs_to_many :storage_items, inverse_of: :storage_movements, validate: true
  accepts_nested_attributes_for :storage_items, allow_destroy: true

  ... several callbacks and validations ...
end

# models/storage_item.rb
class StorageItem < ActiveRecord::Base
  has_many_and_belongs_to_many :storage_movements, inverse_of: :storage_items

  ... more callbacks and validations ...
end

控制器是默认生成的控制器。

1 个答案:

答案 0 :(得分:0)

这是我的解决方案,它确实“感觉”错了,验证也没有像我想要的那样显示......但这是我能想到的......希望它有助于某人。

我在模型上创建了create_from_platesupdate_from_plates方法来处理创建和更新,并更新了控制器的操作以使用它们。

注意:由于回调必需品,必须切换到has_many through关联。

# models/storage_movement.rb
class StorageMovement < ActiveRecord::Base
  has_many :movements_items, dependent: :destroy, inverse_of: :storage_movement
  has_many :storage_items, through: :movements_items, inverse_of: :allocations, validate: true
  accepts_nested_attributes_for :storage_items, allow_destroy: true

  validate :check_plates

  def StorageMovement::create_from_plates mov_attributes
    attributes = mov_attributes.to_h
    items_attributes = attributes.delete "items_attributes"

    unless items_attributes.nil?
      item_plates = items_attributes.collect {|k, h| h["plate"]}
      items = StorageItem.where, plate: item_plates
    end

    if not items_attributes.nil? and item_plates.length == items.count
      new_allocation = Allocation.new attributes
      movements_items.each {|i| new_allocation.items << i}
      return new_allocation
    else
      Allocation.new mov_attributes
    end
  end

  def update_from_plates mov_attributes
    attributes = mov_attributes.to_h
    items_attributes = attributes.delete "items_attributes"

    if items_attributes.nil?
      self.update mov_attributes
    else
      transaction do
        unless items_attributes.nil?
          items_attributes.each do |k, item_attributes|
            item = StorageItem.find_by_plate(item_attributes["plate"])
            if item.nil?
              self.errors.add :base, "The plate #{item_attributes["plate"]} was not found"
              raise ActiveRecord::Rollback
            elsif item_attributes["_destroy"] == "1" or item_attributes["_destroy"] == "true"
              self.movements_items.destroy item
            elsif not self.items.include? item
              self.movements_items << item
            end
          end
        end
        self.update attributes
      end
    end
  end


  def check_plates
    movements_items.each do |i|
      i.errors.add :plate, "Plate not found" if StorageItem.find_by_plate(i.plate).nil?
    end
  end

  ... other validations and callbacks ...
end

有了这个,创建就可以按照我想要的方式工作,因为如果出现错误,验证会将错误添加到特定的item属性中。但更新并不是因为它必须将错误添加到运动的基础,因为没有项目。