【问题标题】:Instantiating multiple instances of polymorphic child manually手动实例化多态子的多个实例
【发布时间】:2014-07-30 01:50:53
【问题描述】:

我的模型中有多个可用的社交网络:

class Social < ActiveRecord::Base
  enum kind: [ :twitter, :google_plus, :facebook, :linked_in, :skype, :yahoo ]
  belongs_to :sociable, polymorphic: true
  validates_presence_of :kind
  validates_presence_of :username
end

我想手动声明使用的种类。也许我需要替代 fields_for?

<%= f.fields_for :socials do |a| %>
  <%= a.hidden_field :kind, {value: :facebook} %> Facebook ID: <%= a.text_field :username, placeholder: "kind" %>
  <%= a.hidden_field :kind, {value: :twitter} %> Twitter ID: <%= a.text_field :username, placeholder: "kind" %>
  <%= a.hidden_field :kind, {value: :google_plus} %> Google ID: <%= a.text_field :username, placeholder: "kind" %>
  <%= a.hidden_field :kind, {value: :linked_in} %> Linked In ID: <%= a.text_field :username, placeholder: "kind" %>
<% end %>

但我只为所有四个 ID 保存并显示了一个值。

在对每个单独的项目执行 fields_for 时,我得到 repeated kinds and repeated values

注意:与此个人资料表单相关联的每种类型都应该只有一个。


我相信我需要使用find_or_create_by 之类的东西来确保只制作每种类型的一个并将其加载到编辑器中,因为 fields_for 只是按照保存顺序加载所有内容。也许展示了这个Rails find_or_create by more than one attribute? 可以与kind 一起使用。

我需要确保产品只会保存每种产品中的一种以及在您编辑时;它将按种类正确加载,而不仅仅是任何belonging to

由于在我的示例中,所有四个都将显示保存在编辑页面的第一个字段中的内容,很明显它目前不能确保 kind


我想在我的application_controller.rb 中使用类似的东西

def one_by_kind(obj, kind)
  obj.where(:kind => kind).first_or_create
end

如何用这个替换 fields_for 方法?

【问题讨论】:

    标签: ruby-on-rails rails-activerecord polymorphic-associations


    【解决方案1】:

    这里有一些问题: 1-当您只能为它选择一个枚举状态时,您将如何定义具有多种类型或种类的 Social ? 2 - 绝对不能使用 :username 作为模型中所有字段的名称。 Rails 只会将最后一个理解为有效的。所有其他的都将被覆盖。

    但是你可以通过简化你的策略来解决这个问题:

    忘记将表单中的 kind 设置为隐藏字段,这真的不会按您想要的方式工作。

    1 - 产品 has_one social 代替了具有许多社交的产品,它保留了与该模型的社交网络相关的所有数据。

    class Product < ActiveRecord::Base
    
        has_one :social
        accepts_nested_attributes_for :social 
    
    #...
    end
    

    2 - 你的表格会更简单,你可以决定出现的顺序。您也可以在编辑视图中将其作为部分重用:

    #your form header with whatever you need here...
    <%= f.text_field(:name) %>
    
    <%= f.fields_for :social do |a| %>
        Facebook ID: <%= a.text_field :facebook_username %>
        Yahoo ID: <%= a.text_field :yahoo_username %>
        Linkedin ID: <%= a.text_field :linkedin_username %>
            Twitter ID: <%= a.text_field :twitter_username %>
    <% end %>
    

    3 - 在您的新方法中,您需要初始化 has_one 关系:

        def new
            @product = Product.new
            @product.build_social
        end
    

    4 - 如果您使用的是 Rails 4,请不要忘记将允许的属性列入白名单:

    def product_params
            params.require(:product).permit([:name, socials_attributes: [:twitter_username, 
                :facebook_username, :linkedin_username, :google_username, :skype_username, :yahoo_username] ])
    end  
    

    5 - 然后在您的控制器中,您可以根据填写的字段为您的模型分配多种类型。为此在模型中使用 before_save 回调。检查您的字段的内容,例如

        def assign_social_kinds
            if !self.twitter_username.blank? #or some more refined validation of it
                self.twitter = true
            end 
            if !self.skype_username.blank?
                self.skype = true
            end
        end
    

    【讨论】:

    • 这是否确保产品只有 1 个 twitter 类型?当你去编辑它时,它会在正确的表单字段中加载那种类型吗?
    • 提交后,它将为每个fields_for生成一个带有Social hash的params hash。因此,在每个参数哈希中,您将拥有元组(种类,用户名)。在您的控制器中,您当然需要使用用户名过滤社交对象。如果社交没有用户名,您可以忽略并且不使用它保存父模型(在我的情况下为产品)。另外,是的。它将在编辑时加载正确的表单。
    • 不幸的是,最后输入的字段成为所有字段的值。因此,在您的代码示例中,Linked In 字段是保存的字段。
    • 这是您的代码输出在编辑页面上显示的内容。 dl.dropboxusercontent.com/u/27731634/polymorphic-child.png你可以忽略kind这个词...这只是一个placeholder我有
    • 如果您在控制台中查看,对象产品正在正确保存。不仅是最后一个字段。虽然,编辑很奇怪。我去看看。
    【解决方案2】:

    在 Rails 中手动创建多态

    好的,我已经找到了解决方案。这就是我所拥有的。

    models/profile.rb

    class Profile < ActiveRecord::Base
      has_many :socials, as: :sociable, dependent: :destroy
      accepts_nested_attributes_for :socials, allow_destroy: true
    end
    

    models/social.rb

    class Social < ActiveRecord::Base
      enum kind: [ :twitter, :google_plus, :facebook, :linked_in, :skype, :yahoo ]
      belongs_to :sociable, polymorphic: true
      validates_presence_of :kind
      validates_presence_of :username
    end
    

    controllers/profiles_controller.rb

    class ProfilesController < ApplicationController
      before_action :set_profile, only: [:show, :edit, :update, :destroy]
      before_action :set_social_list, only: [:new, :edit]
    
      def new
        @profile = Profile.new
      end
    
      def edit
      end
    
      private
    
      def set_profile
        @profile = Profile.find(params[:id])
      end
    
      def set_social_list
        @social_list = [
          ["linkedin.com/pub/", :linked_in],
          ["facebook.com/", :facebook],
          ["twitter.com/", :twitter],
          ["google.com/", :google_plus]
        ]
      end
    
      def profile_params
        params.require(:profile).permit(
         :socials_attributes => [:id,:kind,:username,:_destroy]
        )
      end
    end
    

    我已经缩短了与此处相关的实际文件。您将需要您的用例允许的任何其他参数。其余的可以保持不变。

    controllers/application_controller.rb

    class ApplicationController < ActionController::Base
    
      def one_by_kind(obj, kind)
        obj.where(:kind => kind).first || obj.where(:kind => kind).build
      end
      helper_method :one_by_kind
    
    end
    

    这就是魔法发生的地方。它是在 .where(...).first_or_create 之后设计的,但使用 build 代替,因此我们不必声明 build用于 profile_controller 中的社交对象。

    最后是最重要的观点:

    (多态最无证的方面。)

    views/profiles/_form.html

    <% @social_list.each do |label, entry| %>
        <%= f.fields_for :socials, one_by_kind(@profile.socials, @profile.socials.kinds[entry]) do |a| %>
            <%= a.hidden_field :kind, {value: entry} %><%= label %>: <%= a.text_field :username %>
        <% end %>
    <% end %>
    

    @social_listprofile_controller 中定义,是标签和种类对的数组。因此,当每一个都通过时,我们在 application_controller 中定义的 one_by_kind 方法会寻找第一个具有正确 kind 的多态子节点'已命名条目。如果未找到数据库记录,则构建它。 one_by_kind 然后将对象交还给我们编写/更新。

    这为创建和更新多态子项维护了一个视图。因此它允许在您的个人资料和社会关系中使用每种视图。

    【讨论】:

      猜你喜欢
      • 2016-01-05
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      • 2021-01-29
      • 1970-01-01
      • 2011-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多