【问题标题】:Rails Self Referencing Attribute inheritanceRails 自引用属性继承
【发布时间】:2017-05-25 13:28:29
【问题描述】:

我有一个名为 Option 的模型,它具有自引用关联。一个选项可以有多个子选项,一个子选项可以有一个父选项。我还使用 cocoon gem 的嵌套属性在 form_for 选项上创建多个子选项。在表单上创建选项时,我可以动态创建子选项。

视图/选项/_form.html.erb:

<%= form_for @option do |f| %>

  <p>
    <%= f.label :name %><br>
    <%= f.text_field :name %><br>
    <%= f.label :activity %><br>
    <%= f.select :activity_id, options_for_select(activity_array, @option.activity_id)%><br>
  </p>

  <div>
    <div id="suboptions">
      <%= f.fields_for :suboptions do |suboption| %>
        <%= render 'suboption_fields', f: suboption %>
      <% end %>

      <div class="links">
        <%= link_to_add_association 'add suboption', f, :suboptions %>
      </div>
    </div>
  </div>

  <p>
    <%= f.submit "Send" %>
  </p>
<% end %>

模型/选项.rb:

class Option < ApplicationRecord
  belongs_to :activity
  has_many :option_students
  has_many :students, through: :option_students
  has_many :suboptions, class_name: "Option", foreign_key: "option_id"
  belongs_to :parent, class_name: "Option", optional: true, foreign_key: "option_id"
  accepts_nested_attributes_for :suboptions, allow_destroy: true,
    reject_if: ->(attrs) { attrs['name'].blank? }

  validates :name, presence: true

  after_initialize :set_defaults
  before_update :set_defaults


  def set_defaults
      self.suboptions.each do |sbp|
        sbp.activity_id = self.activity_id
      end
  end

end

参数:

def option_params
    params.require(:option).permit(:name, :activity_id, :students_ids => [], suboptions_attributes: [:id, :name, activity_id, :_destroy])
  end

我希望每个子选项在创建和更新时都从父项继承 activity_id 属性。我通过在模型上使用 set_defaults 方法尝试了这种方式,它适用于具有新嵌套子选项的新选项,如果我更新父项的活动 ID,它也会更新子选项的活动 ID。但是如果我在更新时创建另一个子选项,它不会将属性从父项传递给新的子选项。

【问题讨论】:

    标签: ruby-on-rails inheritance nested-attributes model-associations self-reference


    【解决方案1】:

    您可以使用 before_validation 回调。例如,

    测试代码

    class Location < ApplicationRecord
      has_many :children, class_name: 'Location', foreign_key: 'parent_id'
      accepts_nested_attributes_for :children
      before_validation :initialize_children
    
      attr_accessor :activity
    
      def initialize_children
        children.each { |c| c.activity_id = self.activity_id }
      end
    end
    

    Rails 控制台

    irb(main):002:0> Location.create({name: "L10", activity_id: 200, :children_attributes => [{name: "L12"}]})
       (0.1ms)  begin transaction
      SQL (1.1ms)  INSERT INTO "locations" ("name", "activity_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "L10"], ["activity_id", 200], ["created_at", 2017-01-11 04:07:26 UTC], ["updated_at", 2017-01-11 04:07:26 UTC]]
      SQL (0.1ms)  INSERT INTO "locations" ("name", "parent_id", "activity_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "L12"], ["parent_id", "1"], ["activity_id", 200], ["created_at", 2017-01-11 04:07:26 UTC], ["updated_at", 2017-01-11 04:07:26 UTC]]
       (3.7ms)  commit transaction
    => #<Location id: 1, name: "L10", parent_id: nil, activity_id: 200, created_at: "2017-01-11 04:07:26", updated_at: "2017-01-11 04:07:26">
    irb(main):003:0> Location.all
      Location Load (0.2ms)  SELECT "locations".* FROM "locations"
    => #<ActiveRecord::Relation [#<Location id: 1, name: "L10", parent_id: nil, activity_id: 200, created_at: "2017-01-11 04:07:26", updated_at: "2017-01-11 04:07:26">, #<Location id: 2, name: "L12", parent_id: "1", activity_id: 200, created_at: "2017-01-11 04:07:26", updated_at: "2017-01-11 04:07:26">]>
    irb(main):004:0> Location.last
      Location Load (0.2ms)  SELECT  "locations".* FROM "locations" ORDER BY "locations"."id" DESC LIMIT ?  [["LIMIT", 1]]
    => #<Location id: 2, name: "L12", parent_id: "1", activity_id: 200, created_at: "2017-01-11 04:07:26", updated_at: "2017-01-11 04:07:26">
    

    【讨论】:

    • 但我认为如果我更新值它不起作用,不是吗?
    • 现在它适用于以前的孩子,但如果我添加一个新孩子,它就不起作用了。我正在这样做:after_initialize :set_defaults before_update :set_defaults def set_defaults self.suboptions.each do |sbp| sbp.activity_id = self.activity_id end
    • 你能试试bofore_create回电而不是after_initialize回电吗?
    • 试过了......但如果我创建一个新的孩子,它仍然不会将父activity_id传递给孩子。
    • 问题是children的属性在suboptions_attributes中。
    【解决方案2】:

    升级到 Rails 5 时我也遇到了同样的问题。Fossil 的回答有所帮助,但我还必须做一件事:

    1. 在主对象的has_many关系中添加inverse_of(在本例中为Activity模型:has_many :options, inverse_of: "activity"
    2. 在自引用对象的has_many关系中添加inverse_of(在本例中为Option模型:has_many :suboptions, inverse_of: "parent"
    3. 在自引用模型(此处为Option)中,使用before_validation 回调传递主对象:
    class Option
      belongs_to :parent, foreign_key: "option_id", class_name: "Option"
      has_many :suboptions, class_name: "Option", foreign_key: "option_id", inverse_of: "parent"
    
      before_validation :pass_activity_to_suboptions
    
      private
      define pass_activity_to_suboptions
        suboptions.each { |o| o.activity = self.activity }
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-02-03
      • 2011-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-27
      • 1970-01-01
      相关资源
      最近更新 更多