Rails 从模型访问关联属性

Rails association attributes access from model

我有一个 rails 5 API 只有 rails 应用程序具有如下多对多关系:

class Business < ApplicationRecord
  has_many :managements, inverse_of: :business, autosave: true
  has_many :managers, through: :managements, source: :user, autosave: true
end

class Management < ApplicationRecord
  belongs_to :user
  belongs_to :business
  validates :user, uniqueness: { scope: :business }
end

class User < ApplicationRecord
  has_many :managements
  has_many :businesses, through: :managements
end

在业务中 class 我有以下方法,我想用它来设置管理连接的所有者 table。

def owner=(user)
    userAsManager = managements.find_by(user_id: user.id)
    unless userAsManager
      managers << user
      self.save
      userAsManager = managements.find_by(user_id: user.id)
    end
    if userAsManager && !userAsManager.owner
      if owner
        self.managements_attributes = [
          { id: managements.find_by(user_id: owner.id).id, owner: false }
        ]
        save
      end
      self.managements_attributes = [ {id: userAsManager.id, owner: true}]
      save
    end
  end

在 setter 中使用此逻辑的原因是为了管理所有没有经理、现有所有者等的情况。我发现在 Rspec 测试中例如:

it 'added when the owner is already a manager' do
  business.managers << owner
  business.save
  business.owner = owner
  business.save
  expect(Business.first.owner).to eq owner
  expect(Business.first.managers[0]).to eq owner
end

我必须继续保存模型,否则当我在 owner=(user) 方法中访问 self 时,关联的模型不可用。例如在测试中将 owner 设置为 business.managers 如果 business 未保存,则在输入 [= 时将所有者设置为业务 business.owner = owner 的所有者14=] 方法 self.managers 是一个空对象。

为什么会这样,有没有一种方法可以在模型中进行更新,然后在测试中完成后保存它?

TBH 我对很多这样的代码不太满意,我真的不明白为什么我需要在 self.managements_attributes 上使用 self 以及为什么我不能只使用 managements.find_by(user_id: user.id).owner = true 来设置老板,所以如果你能给我指出一些可以在更深层次上解释关联使用的好资源,那真的很有帮助。

我认为代码可以是这样的。你写了一些关于你的代码不能正常工作的东西(所以你使用了属性)。这段代码有同样的问题吗?由于您在测试中提到的问题,我添加了很多重新加载。也许不需要。

def owner=(user)
  #Added reload. Could this solve the problem about empty managements in tests?
  userAsManager = managements.reload.find_by(user_id: user.id)
  unless userAsManager
    managers << user
    userAsManager = managements.reload.find_by(user_id: user.id)
  end
  if userAsManager
    #Just for simplicity, remove all other owners and add this one.
    managements.update_all(owner: false)
    userAsManager.update_attributes(owner: true)
    managements.reload
  end
end

要访问尚未保存的内存对象,需要进行一些更改。首先,在两侧使用 inverse_of 来 link 关联。 示例:

class Business < ApplicationRecord
  has_many :managements, inverse_of: :business
end

class Management < ApplicationRecord
  belongs_to :business, inverse_of: :managements
end

要查找已添加到关联但尚未保存到数据库的记录,您不能使用 find_by,因为该方法会调用数据库。相反,您可以使用 Array#find。像这样:

def owner=(user)
  management = managements.find { |m| m.user ==  user } || self.managments.new(user: user)
  management.owner ||= management.owner.blank?
end

有几件事...

首先,关于business.save的测试:

来自 Rails 指南,在 "Controlling Caching":

All of the association methods are built around caching, which keeps the result of the most recent query available for further operations.

所以你必须选择:

a) 在测试中而不是 business.save,你可以使用 business.reload

b) 在你的 owner= 方法中,在使用 find_by 之前重新加载你的关联:managements.reload

这里有一些关于读入测试的信息:In-Memory vs. Database Layer -- .reload

二、关于self.managements_attributes

why I can't just use managements.find_by(user_id: user.id).owner = true to set the owner

因为您没有使用该调用更新数据库中的所有者。你的来电是这样的:

to_update = managements.find_by(user_id: user.id)
to_update.owner = true

数据库中没有保存更改。要更新数据库,您可以这样做:

user_as_manager = managements.find_by(user_id: user.id)
user_as_manager.owner = true
user_as_manager.save # this will persist the owner to the database

相同
user_as_manager = managements.find_by(user_id: user.id)
user_as_manager.update(owner: true)

相同
managements.find_by(user_id: user.id).update(owner: true)