Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails 如何为“has_one”属于“relationship”的关系建模?_Ruby On Rails_Ruby_Validation_Ruby On Rails 4_Associations - Fatal编程技术网

Ruby on rails 如何为“has_one”属于“relationship”的关系建模?

Ruby on rails 如何为“has_one”属于“relationship”的关系建模?,ruby-on-rails,ruby,validation,ruby-on-rails-4,associations,Ruby On Rails,Ruby,Validation,Ruby On Rails 4,Associations,用户和组织通过关系建立了多对多关联。关系模型包含几个关于关系的布尔变量,例如主持人(真/假)和成员(真/假)。此外,我还添加了一个名为default的布尔值,用于设置默认组织 我需要验证如果(且仅当)用户是一个或多个组织的成员(member==true),这些组织中的一个(正好是1个)必须具有default==true 所以基本上这意味着如果一个用户是多个组织的成员,那么其中一个组织需要是默认组织,如果用户是多个组织的成员,那么默认组织必须存在 如何编写此验证?我的当前验证在设定种子时生成以下错

用户
组织
通过
关系
建立了
多对多
关联。
关系
模型包含几个关于关系的布尔变量,例如
主持人
(真/假)和
成员
(真/假)。此外,我还添加了一个名为
default
的布尔值,用于设置默认组织

我需要验证如果(且仅当)用户是一个或多个组织的成员(
member==true
),这些组织中的一个(正好是1个)必须具有
default==true

所以基本上这意味着如果一个用户是多个组织的成员,那么其中一个组织需要是默认组织,如果用户是多个组织的成员,那么默认组织必须存在

如何编写此验证?我的当前验证在设定种子时生成以下错误:

PG::SyntaxError: ERROR:  syntax error at or near "default"
LINE 1: ...ERE (user_id = 1) AND (member = 't' and default = ...
                                                   ^
: SELECT COUNT(*) FROM "relationships" WHERE (user_id = 1) AND (member = 't' and default = 't')
我在
关系
模型中的实现:

validate :default
private
def default
  @relationships = Relationship.where('user_id = ?', self.user_id)
  @members = @relationships.where('member = ?', true)
  @defaults = @members.where('default = ?', true)
  # If more than 1 organization has been set as default for user
  if @defaults.count > 1
    @defaults.drop(0).each do |invalid|
      invalid.update_columns(default: false)
    end
  end
  # If user is member but has no default organization yet
  if !@defaults.any? && @members.any?
    @members.first.update_columns(default: true)
  end
end

更新从外观上看,我明白我不应该这样建模,而是应该使用@DavidAldridge在回答中建议的
has_one
属于
关系。但我不明白如何建立这种关系的模型(见我在答案下面的评论)。非常感谢您的任何建议。

@Brad Werth认为您的
validate
方法作为回调更有效,这是正确的

我建议您在您的关系中使用类似的模式:

before_save :set_default

private

  def set_default
    self.default = true unless self.user.relationships.where(member: true, default: true).any?
  end

如果用户的其他关系均未设置为默认关系,则应强制将该用户的关系设置为默认关系。

@Brad Werth认为您的
validate
方法可以更好地用作回调

我建议您在您的关系中使用类似的模式:

before_save :set_default

private

  def set_default
    self.default = true unless self.user.relationships.where(member: true, default: true).any?
  end

如果用户的其他关系均未设置为默认,则应强制将用户的关系设置为默认。

default
更改为
is\u default
(正如其他用户在评论中指出的,
default
是postgres关键字)。为此创建单独的迁移。(或者,如果你想保持原样,也可以在任何地方引用。)

那么有两点

首先,为什么每次都需要检查单个
是否为默认组织?您只需要迁移当前数据集,然后保持其一致性

要迁移当前数据集,请创建迁移,并在此处编写如下内容:

def self.up
  invalid_defaults = Relationship.
    where(member: true, is_default: true).
    group(:user_id).
    having("COUNT(*) > 1")

  invalid_defaults.each do |relationship|
    this_user_relationships = relationship.user.relationships.where(member: true, is_default: true)
    this_user_relationships.where.not(id: this_user_relationships.first.id).update_all(is_default: false)
  end
end
只需确保在非高峰时间运行此迁移,因为完成迁移可能需要相当长的时间。或者,您可以从服务器控制台本身运行该代码段(当然,只需事先在开发环境中进行测试)

然后,在更新记录时,使用callback(正如另一个评论者正确建议的那样)设置默认组织

before_save :set_default

private

def set_default
  relationships = Relationship.where(user_id: self.user_id)
  members = relationships.where(member: true)
  defaults = members.where(is_default: true)

  # No need to migrate records in-place

  # Change #any? to #exists?, to check existance via SQL, without actually fetching all the records
  if !defaults.exists? && members.exists?
    # Choosing the earliest record
    members.first.update_columns(is_default: true)
  end
end
要考虑到正在编辑组织的情况,还应添加对组织的回调:

class Organization
  before_save :unset_default
  after_commit :set_default

  private

  # Just quque is_default for update...
  def remember_and_unset_default
    if self.is_default_changed? && self.is_default
      @default_was_set = true
      self.is_default = false
    end
  end

  # And now update it in a multi-thread safe way: let the database handle multiple queries being sent at once,
  # and let only one of them to actually complete, keeping base in always consistent state
  def set_default
    if @default_was_set
      self.class.
        # update this record...
        where(id: self.id).
        # but only if there is still ZERO default organizations for this user
        # (thread-safety will be handled by database)
        where(
          "id IN (SELECT id FROM organizations WHERE member = ?, is_default = ?, user_id = ? GROUP BY user_id HAVING COUNT(*)=0)",
          true, true, self.user_id
        )
    end
  end

默认值
更改为
是默认值
(正如其他用户在评论中指出的,
默认值
是postgres关键字)。为此创建单独的迁移。(或者,如果你想保持原样,也可以在任何地方引用。)

那么有两点

首先,为什么每次都需要检查单个
是否为默认组织?您只需要迁移当前数据集,然后保持其一致性

要迁移当前数据集,请创建迁移,并在此处编写如下内容:

def self.up
  invalid_defaults = Relationship.
    where(member: true, is_default: true).
    group(:user_id).
    having("COUNT(*) > 1")

  invalid_defaults.each do |relationship|
    this_user_relationships = relationship.user.relationships.where(member: true, is_default: true)
    this_user_relationships.where.not(id: this_user_relationships.first.id).update_all(is_default: false)
  end
end
只需确保在非高峰时间运行此迁移,因为完成迁移可能需要相当长的时间。或者,您可以从服务器控制台本身运行该代码段(当然,只需事先在开发环境中进行测试)

然后,在更新记录时,使用callback(正如另一个评论者正确建议的那样)设置默认组织

before_save :set_default

private

def set_default
  relationships = Relationship.where(user_id: self.user_id)
  members = relationships.where(member: true)
  defaults = members.where(is_default: true)

  # No need to migrate records in-place

  # Change #any? to #exists?, to check existance via SQL, without actually fetching all the records
  if !defaults.exists? && members.exists?
    # Choosing the earliest record
    members.first.update_columns(is_default: true)
  end
end
要考虑到正在编辑组织的情况,还应添加回拨到组织:

class Organization
  before_save :unset_default
  after_commit :set_default

  private

  # Just quque is_default for update...
  def remember_and_unset_default
    if self.is_default_changed? && self.is_default
      @default_was_set = true
      self.is_default = false
    end
  end

  # And now update it in a multi-thread safe way: let the database handle multiple queries being sent at once,
  # and let only one of them to actually complete, keeping base in always consistent state
  def set_default
    if @default_was_set
      self.class.
        # update this record...
        where(id: self.id).
        # but only if there is still ZERO default organizations for this user
        # (thread-safety will be handled by database)
        where(
          "id IN (SELECT id FROM organizations WHERE member = ?, is_default = ?, user_id = ? GROUP BY user_id HAVING COUNT(*)=0)",
          true, true, self.user_id
        )
    end
  end

之所以很难做到这一点,是因为您的数据模型不正确。用户默认组织的身份是用户的属性,而不是关系的属性,因为每个用户只能有一个默认组织。如果你有一个一级、二级、三级组织,那么这将是关系的一个属性

不要在关系上放置“关系是用户的默认值”属性,而是在用户上放置“默认关系\u id”属性,以便它

belongs_to :default_relationship
。。。而且

has_one :default_organisation, :through => :default_relationship
这保证:

  • 只有一个组织可以作为用户的默认组织
  • 用户与其默认组织之间必须存在关系
  • 您还可以使用:dependent=>:nullify对:default_relationship的反向关联进行设置,并根据以下情况轻松测试单个关系是否为默认关系:

    self == user.default_relationship.
    
    比如:

    class User << ActiveRecord::Base
        has_many   :relationships, :inverse_of => :user, :dependent => :destroy
        has_many   :organisations, :through    => :relationships, :dependent => :destroy
        belongs_to :default_relationship, :class_name => "Relationship", :foreign_key => :default_relationship_id, :inverse_of => :default_for_user
        has_one    :default_organisation, :through => :default_relationship, :source => :organisation
    
    
    class Relationship  << ActiveRecord::Base
        belongs_to :user        , :inverse_of => :relationships
        belongs_to :organisation, :inverse_of => :relationships
        has_one    :default_for_user, :class_name => "User", :foreign_key => :default_relationship_id, :inverse_of => :default_relationship, :dependent => :nullify
    
    class Organisation << ActiveRecord::Base
        has_many   :relationships, :inverse_of => :organisation, :dependent => :destroy
        has_many   :users        , :through    => :relationships
        has_many   :default_for_users, :through => :relationships, :source => :default_for_user
    

    默认组织也很容易加载(不是说它不能加载,而是不需要作用域来加载)。

    这样做很困难的原因是您的数据模型不正确。用户默认组织的身份是用户的属性,而不是关系的属性,因为每个用户只能有一个默认组织。如果你有一个一级、二级、三级组织,那么这将是关系的一个属性

    不要在关系上放置“关系是用户的默认值”属性,而是在用户上放置“默认关系\u id”属性,以便它

    belongs_to :default_relationship
    
    。。。而且

    has_one :default_organisation, :through => :default_relationship
    
    这保证:

  • 只有一个组织可以作为用户的默认组织
  • 用户与其默认组织之间必须存在关系
  • 您还可以在上使用:dependent=>:nullify