【问题标题】:Creation form for models with polymorphism多态模型的创建形式
【发布时间】:2014-12-22 02:21:10
【问题描述】:

我有一个模型“item”、两个模型“bar”和“restaurant”以及一个模型“user”。

这些模型之间的关系是:

  • 用户 has_many :bars 和 has_many :restaurants
  • Item belongs_to :activity, polymorphic: true
  • Bar has_many :items, as: :activity
  • 餐厅 has_many :items, as: :activity

创建新项目的 _form 视图应该是什么样的?
用户可以创建一个项目并将其分配给可以是酒吧或餐厅的模型,所以我希望该用户可以选择在哪个活动中项目应该属于。
在我的表单中,我有类似 但不起作用。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4


    【解决方案1】:

    对于多态关联,您使用 fields_for 块:

    <%= form_for(@bar) do |f| %>
      Bar<br />
      <%= select :id, options_for_select(@bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
      New Item<br />
      <%= f.fields_for :items do |a| %>
        Kind: <%= a.select :kind, options_for_select(Item.kinds.keys.map.with_index {|k,i| [k, i]}) %><br /> <!-- # If you have an enum of kind for item -->
        Vendor: <%= a.select :vendor_id, options_for_select(current_user.vendors.map {|i| [i.name, i.id]}) %><br /> <!-- # If you have specific vendors per user -->
        Name: <%= a.text_field :name %><br />
      <% end %>
      <%= f.submit %>
    <% end %>
    

    这将进入您的form_for 标记。使用fields_for 块嵌套任何多态关系。

    您只能将select 用于创建 内容时存在的属性。您部分写出的示例看起来就像您只是从现有项目中进行选择。如果您正在寻找它,那将是一个不同的答案。您专门询问了有关创建项目的问题。因此,您不会使用 select 按名称创建内容,您需要 text_field 来输入新名称。

    您可以忽略解决方案的两个 select 字段。他们在那里展示select。我认为您的答案不需要select 字段。

    https://gorails.com/episodes/forum-nested-attributes-and-fields-for


    另一方面,您对 Item 的命名方案可能会造成混淆。标准命名会更像。

    class Item < ActiveRecord::Base
      belongs_to :itemable, polymorphic: true
    end
    
    class Bar < ActiveRecord::Base
      has_many :items, as: :itemable, dependent: :destroy
      accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
    end
    
    class Restaurant < ActiveRecord::Base
      has_many :items, as: :itemable, dependent: :destroy
      accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
    end
    

    在这种情况下,您将在 :items 上使用 fields_for。多态关系名称activityitemable 未在fields_for 字段中引用。相反,它是Item 的复数形式,所以是:items


    回答:

    我有一个页面来添加一个项目,用户在其中填写信息,例如 标题、描述等,然后选择哪个“栏”或 'restaurant' 他想发布它。

    <%= form_for(@item) do |f| %>
      <%= f.select :itemable_type, options_for_select([Bar.name, Restaurant.name]) %>
      <%= f.select :itemable_id, [1,2,3] %># COMPLICATION, NEED AJAX/JS TO GET AVAILABLE IDs
      <%= f.text_field :name %>
    <% end %> 
    

    这基本上就是你想要做的。但是您需要调用 Ajax/JavaScript 来将 :itemable_id 的可用选项更改为使用 [:name, :id] 映射的酒吧或餐厅列表。您可以只使用文本字段来输入酒吧/餐厅的 ID 编号,但这不是一种用户友好的体验。

    如果我精通 JavaScript,我可以给你一个方法来做到这一点。一种方法是拥有重复的 :itemable_id 字段并让 javascript 禁用/删除它不使用的任何选择字段。

    替代方案一:

    您可以为new_bar_item.html.erbnew_restaurant_item.html.erb 的每种类型创建一个页面,您只需为itemable_type 分别添加一个hidden_fieldBar.nameRestaurant.name。然后您就已经知道将哪个集合提供给您的select 字段。将选择字段的集合映射到您的 :name、:id。这消除了执行此操作的所有复杂性。

    可行的解决方案2:

    我推荐的一个不使用 JavaScript 的好方法是同时列出酒吧和餐厅。

    <%= form_tag(@item) do |f| %>
      Choose either Bar or Restaurant.<br />
      Bar: <%= select_tag 'item[bar_id]', options_for_select(@bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
      Restaurant: <%= select_tag 'item[restaurant_id]', options_for_select(@restaurants.map {|i| [i.name, i.id]}, include_blank: true) %><br />
      Item name: <%= text_field_tag 'item[name]' %>
    <% end %> 
    

    然后在您的 ItemController 中,您将需要编写一个方法来检查哪个字段不是空白并使用它设置多态类型。

    before_action :set_poly_by_params, only: [:create, :update]
    
    private
    def set_poly_by_params
      if !params["item"]["bar_id"].empty? ^ !params["item"]["restaurant_id"].empty?
        if !params["item"]["bar_id"].empty?
          params["item"]["itemable_id"] = params["item"].delete("bar_id")
          params["item"]["itemable_type"] = Bar.name
        else
          params["item"]["itemable_id"] = params["item"].delete("restaurant_id")
          params["item"]["itemable_type"] = Restaurant.name
        end
      else
        raise "some error about incorrect selection"
      end
    end
    # NOTE: The above code will fail if the form doesn't submit both a bar_id field and a restaurant_id.  It expects both, empty or not.
    

    解决方案 3(修订版 #2)

    <%= form_tag(@item) do |f| %>
      Location: <%= select_tag 'item[venue]', options_for_select(
        @bars.map {|i| [i.name, "b"+i.id.to_s]} +
        @restaurants.map {|i| i.name, "r"+i.id.to_s]}
      ) %><br />
      Item name: <%= text_field_tag 'item[name]' %>
    <% end %> 
    

    我们在酒吧 ID 前添加了b,或在餐厅 ID 前添加了r。然后我们只需要解析它的参数。

    before_action :set_poly_by_params, only: [:create, :update]
    
    private
    def set_poly_by_params
      if params["item"]["venue"]["b"]
        params["item"]["itemable_type"] = Bar.name
      else
        params["item"]["itemable_type"] = Restaurant.name
      end
      params["item"]["itemable_id"] = params["item"].delete("venue")[1..-1]
    end
    

    这符合您对一个包含酒吧和餐厅的选择字段的要求。

    【讨论】:

    • 感谢您的回答。我更改了命名方案。我可以让它工作。我试图更好地解释它:我有一个页面来添加一个项目,用户在其中填写标题、描述等信息,然后选择他想在哪个“酒吧”或“餐厅”发布它。跨度>
    • 是的。我忘了提到您需要将accepts_nested_attributes_for :items 添加到has_many 的每个模型中。这就是让fields_for 起作用的粘合剂。
    • 我明白你在说什么...给我一点时间。
    • 我个人不喜欢solution2,因为多选。我正在尝试实施可能更适合并且更适合用户体验恕我直言的解决方案 1。
    • 我正在尝试实现你的解决方案3,但是当我加载页面时,它给了我“未定义的方法 `venue'”,因为项目模型中没有场地属性。
    【解决方案2】:

    我使用了 6ft Dan 发布的部分解决方案。

    在 items_controller 我创建了一个

    before_action :set_activity, only: [:create, :update]
    


    def set_activity
      @itemable = params["item"]["itemable"]
    
      if params["item"]["itemable"]["bar"]
        @activity = Bar.find(@itemable[3..-1])
      elsif params["item"]["itemable"]["res"]
        @activity = Restaurant.find(@itemable[3..-1])
      end
    end
    

    然后在创建操作中我添加了

    @item.update_attribute(:itemable, @activity)
    

    之后

    @item = Item.new(item_params)
    

    在我的表格中我有

    <%= f.select :itemable, options_for_select(@bars.map {|i| [i.name, "bar"+i.id.to_s]} + @restaurants.map {|i| [i.name, "res"+i.id.to_s]}) %>
    

    现在项目创建并具有链接到它所属的活动的 itemable 属性!

    【讨论】:

    • 多态专门在itemable的末尾使用_id_type来允许它的多态设计。 itemable_id 是对外部表 id 的引用,item 现在是其子表。 itemable_type 就像这个孩子继承的物种。例如,如果我只有itemable_id 而没有itemable_type,那么孩子将知道它属于“酒吧”还是“餐厅”。当itemable_id 设置为3 并且itemable_type 设置为Bar 时,我们知道这个item 是Bar 的多态子代,特别是id 3。
    • 您在给定示例中可能遇到的一个问题是您可以有一个 ID 为 14 的 BarRestaurant。但是您没有'没有告诉它它在 DB 中是哪种类型。那么当你去.find(14) 时你会使用哪个表呢? 酒吧还是餐厅
    • 在 db 表模式中,我有两个 # itemable_id :integer # itemable_type :string(255) 所以我认为没关系。将测试具有相同 ID 的酒吧和餐厅,如果我会遇到错误,我会通知您。
    • 关键是您不知道用户是从表单中选择了酒吧还是餐厅。
    • 有效!查看 activeadmin 项目显示页面,在 itemable_type 中,我有模型“酒吧”或“餐厅”的类型,在 itemable 中,我有指向该项目所属的酒吧或餐厅的直接链接。
    猜你喜欢
    • 2011-08-12
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 2021-12-26
    相关资源
    最近更新 更多