MongoDB N x N 关系,没有 join table, 又强制更新时删除,很头疼。高手有妙招么?

hardywu · July 08, 2013 · Last by hardywu replied at July 09, 2013 · 2331 hits

关系看起来简单,结果实现起来老繁琐。

我设置了两个 collections, 分别是 entries 和 entry_aliases。用 has_and_belongs_to_many 关系。entry_alias 只有一个

field :name, type: String validates :name, uniqueness: true`

entry model 里加上accepts_nested_attributes_for :entry_alias并使用 nested_form 来动态添加 entry_aliases_attributes。

在 entry model 下

  1. create 时,要求带 _destroy == '1'的 entry_alias 不被创建。数据表内已有同名的则不创建并关联已有同名 entry_alias.
  2. update 时,要求带 _destroy == '1' 或者 name 更改的 entry_alias 被取消关联。寻找并创建 name 对应的 entry_alias.

entry_alias 只能被创建,不能被删除,或者更改 name.

我最后的实现就是下面这么多代码,非常头疼。高手有妙招么?

def save
  ids = []
  self.entry_aliases.each do |item|
    alias_by_name = EntryAlias.find_or_create_by(name: item.name)
    alias_by_name.update_attributes(entry_ids:  alias_by_name.entry_ids | [self.id])
    ids << alias_by_name.id
  end

  self.entry_aliases = []
  self.entry_alias_ids = ids
  super
end

def update_attributes(params)
    params[:entry_aliases_attributes].each do |key, entry_alias|
    if entry_alias[:_destroy] == '1' && entry_alias[:id]

      alias_by_id = EntryAlias.find(entry_alias[:id])

      if alias_by_id
        alias_by_id.update_attributes(entry_ids: alias_by_id.entry_ids - [self.id])
        super(entry_alias_ids: self.entry_alias_ids - [alias_by_id.id])
      end

    elsif entry_alias[:_destroy] != '1' && entry_alias[:id]
      alias_by_id = EntryAlias.find(entry_alias[:id])

      if alias_by_id && alias_by_id.name != entry_alias[:name]
        alias_by_id.update_attributes(entry_ids: alias_by_id.entry_ids - [self.id])
        super(entry_alias_ids: self.entry_alias_ids - [alias_by_id.id])
        entry_alias.delete(:id)
      elsif !alias_by_id
        entry_alias.delete(:id)
      end          
    end
  end

  params[:entry_aliases_attributes].delete_if { |key, value| value[:_destroy] == '1' }

  super
end

好吧,花了 N 多小時,總算是折騰出辦法了。

## entry.rb 

has_and_belongs_to_many :entry_aliases, dependent: :nullify, autosave: true, before_remove: :unrelating

before_validation :check_alias

def check_alias
  self.entry_aliases.collect! do |item|
    found = EntryAlias.find_by(name: item.name)
    item = found if found
  end
end

def unrelating(entry_alias)
  entry_alias.update_attributes(entry_ids: entry_alias.entry_ids - [self.id])
end

## entry_aliases.rb
before_destroy :check_destroy

def check_destroy
  false
end
You need to Sign in before reply, if you don't have an account, please Sign up first.