0

我花了一个小时调试一个非常奇怪的 rails 行为。鉴于:

应用程序/模型/user.rb

class User < ApplicationRecord
  ...
  has_many :images
  has_many :videos
  ...
  has_many :tags
  ...
end


应用程序/模型/image.rb

class Image < ApplicationRecord
  ...
  belongs_to :user
  ...
  has_and_belongs_to_many :tags
  ...
  include TagsFunctions
  ...
end


应用程序/模型/video.rb

class Video < ApplicationRecord
  ...
  include TagsFunctions
  ...
  belongs_to :user
  ...
  has_and_belongs_to_many :tags
  ...
end

应用程序/模型/tag.rb

class Tag < ApplicationRecord
  belongs_to :user

  validates :text, uniqueness: {scope: :user}, presence: true

  before_create :set_code

  def set_code
    return if self[:code].present?

    loop do
      self[:code] = [*'A'..'Z'].sample(8).join
      break if Tag.find_by(code: self[:code]).nil?
    end
  end
end


应用程序/模型/关注/tags_functions.rb

module TagsFunctions
  extend ActiveSupport::Concern

  # hack for new models
  included do
    attr_accessor :tags_after_creation

    after_create -> { self.tags_string = tags_after_creation if tags_after_creation.present? }
  end

  def tags_string
    tags.pluck(:text).join(',')
  end

  def tags_string=(value)
    unless user 
      @tags_after_creation = value
      return
    end

    @tags_after_creation = ''
    self.tags = []
    value.to_s.split(',').map(&:strip).each do |tag_text|  
      tag = user.tags.find_or_create_by(text: tag_text) 
      self.tags << tag                               
    end
  end
end


如果我执行这样的代码:

user = User.first
tags_string = 'test'
image = user.images.create(tags_string: tags_string)
video = user.videos.create(tags_string: tags_string)

它将提供 1 个项目image.tags,但 2 个重复项目video.tags

但是如果我们这样修改代码:

user = User.first
tags_string = 'test'
image = Image.create(user: user,  tags_string: tags_string)
video = Video.create(user: user, tags_string: tags_string)

一切正常,图像 1 个标签,视频 1 个标签

甚至更多...如果我们移到include TagsFunctions 下面 has_and_belongs_to_many :tagsvideo.rb文件中,两个代码示例都可以正常工作。

我以为我很了解 Rails,但这种行为对我来说真的不清楚。
导轨版本:5.1.1

4

2 回答 2

0

看来您可能想检查这两行 tag = user.tags.find_or_create_by(text: tag_text) self.tags << tag

这里似乎发生的是正在为用户创建的标签,以及实际的视频记录。但是如果不查看标签模型中是否有任何东西,很难知道。最好避免在标签函数中将标签关联与用户相关联。

于 2017-05-19T20:03:16.010 回答
0

我认为你有一个 X 和 Y 问题,因为首先可以更好地建模域:

# rails g model tag name:string:uniq
class Tag < ApplicationRecord
  has_many :taggings
  has_many :tagged_items, through: :taggings, source: :resource
  has_many :videos, through: :taggings, source: :resource, source_type: 'Video'
  has_many :images, through: :taggings, source: :resource, source_type: 'Image'
end

 # rails g model tagging tag:belongs_to tagger:belongs_to resource:belongs_to:polymorphic
class Tagging < ApplicationRecord
  belongs_to :tag
  belongs_to :tagger, class_name: 'User'
  belongs_to :resource, polymorpic: true
end

class User < ApplicationRecord
  has_many :taggings, foreign_key: 'tagger_id'
  has_many :tagged_items, through: :taggings, source: :resource
  has_many :tagged_videos, through: :taggings, source: :resource, source_type: 'Video'
  has_many :tagged_images, through: :taggings, source: :resource, source_type: 'Image'
end

module Taggable
  extend ActiveSupport::Concern

  included do
    has_many :taggings, as: :resource
    has_many :tags, through: :taggings
  end

  # example
  # @video.tag!('#amazeballs', '#cooking', tagger: current_user) 
  def tag!(*names, tagger:)
     names.each do |name|
       tag = Tag.find_or_create_by(name: name)
       taggnings.create(tag: tag, tagger: tagger)
     end
  end
end

这将创建一个规范化的标签表,我们可以使用它来查找而不是比较字符串值。has_and_belongs_to_many仅在您只是连接两个表并且永远不需要直接查询连接表的最简单情况下才真正有用(换句话说,使用 HABTM 在 90% 的情况下都是错误的)。

使用回调“侵入”HABTM 只会让情况变得更糟。

在设置不同的可标记类时,您可以使用一些元编程来减少重复。

class Video < ApplicationRecord
  include Taggable
end

class Image< ApplicationRecord
  include Taggable
end
于 2017-05-20T13:24:34.287 回答