【问题标题】:How to control integrity in Rails has_many :through relationship?如何控制 Rails has_many 中的完整性:通过关系?
【发布时间】:2012-06-24 17:11:04
【问题描述】:

如果关联记录有自己的关联记录,我正在尝试阻止它被删除。 Rails 似乎正在颠覆 :dependent => :restrict 选项并执行 DELETE SQL。我错过了什么?

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions
end

class Region < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
end

class UserRegion < ActiveRecord::Base
  belongs_to :user
  belongs_to :region

  has_many :events, :dependent => :restrict
end

class Event < ActiveRecord::Base
  belongs_to :user_region, :readonly => true
  has_one :planner, :through => :user_region, :source => :user
  has_one :region, :through => :user_region
end

现在假设我有两个区域 [R1 和 R2],两个用户 [U1 和 U2]。 U1 已分配给 R1 和 R2 [作为 UR1 和 UR2],而 U2 已分配给 R2 [作为 UR3]。这给出了总共三个 UserRegions。

此外,我还有事件 E1、E2 和 E3,其中 E1 在 UserRegion UR1 中,E2 在 UserRegion UR2 中,E3 在 UserRegion UR3 中。这给了 U1 两个事件和 U2 一个事件。

应该发生什么: 如果我尝试删除 U1 对 R2 的分配(即销毁 UR2),我应该会收到错误消息,因为 U1 有通过 UR2 的事件。

发生了什么:

u1 = User.find(1)
urids = u1.regions.map &:id
 => [1,2]
u1.region_ids = [1]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" WHERE "regions"."id" = ? LIMIT 1  [["id", 1]]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" INNER JOIN "user_regions" ON "regions"."id" = "user_regions"."region_id" WHERE "user_regions"."user_id" = 1
 (0.1ms)  begin transaction
SQL (0.3ms)  DELETE FROM "user_regions" WHERE "user_regions"."user_id" = 1 AND "user_regions"."region_id" = 2
 (2.7ms)  commit transaction
 => [1] 

Rails 显然没有在被删除的 UserRegion 上执行 :destroy 回调,因为如果我尝试这样做:

UserRegion.find(2).destroy

我会得到一个错误并且事务会回滚。

我需要做些什么来确保将 Region id 分配给 User 不会删除 UserRegions?

这里的目标是“编辑用户”表单有一个用于分配区域的复选框列表。应该可以向用户添加区域并删除用户没有事件的区域。

【问题讨论】:

    标签: ruby-on-rails associations has-many has-many-through


    【解决方案1】:

    也许您可以先搜索关联的记录,如果它们的计数为零,则只执行删除代码行?

    def destroy
    @hgroup = Hgroup.find(params[:id])
      if 'search for associations' == 0
        @hgroup.destroy
          respond_to do |format|
           format.html { redirect_to hgroups_url }
         end
      else
         'do something else'
      end
    

    结束

    尝试类似的方法。

    【讨论】:

    • 用户表单看起来像(使用 SimpleForm): :check_boxes, :collection => Region.all %> 这意味着分配了区域以@user.region_ids = [...] 的身份发送给用户
    • 我会在控制器中以任何您用作“销毁”操作的方法来完成。
    • 我刚刚使用我正在开发的应用程序中的一些代码进行了更新。我没有测试它。
    【解决方案2】:

    来自ActiveRecord::Associations documentation,在“删除还是销毁?”部分:

    对于 has_many,destroy 总是会调用 删除记录以便运行回调。但是删除会 要么根据指定的策略进行删除 :dependent 选项,或者如果没有给出 :dependent 选项,那么它将 遵循默认策略。 默认策略是:nullify(设置 外键为零),除了 has_many :through,默认 策略是 delete_all (删除连接记录,而不运行它们 回调)。

    所以在我上面的用户模型中,我需要改变这个:

    class User < ActiveRecord::Base
      has_many :user_regions
      has_many :events, :through => :user_regions
      has_many :regions, :through => :user_regions
    end
    

    进入这个:

    class User < ActiveRecord::Base
      has_many :user_regions
      has_many :events, :through => :user_regions
      has_many :regions, :through => :user_regions, :dependent => :destroy
    end
    

    如果用户仍然有通过 UserRegion 的事件,这有效地防止了 UserRegion 被删除。如果尝试这样做,将引发 ActiveRecord::DeleteRestrictionError。

    在控制器级别处理此错误并设置我的视图以使这种情况永远不会遇到当然是另一回事了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-20
      相关资源
      最近更新 更多