【问题标题】:Why 'include' position in Rails model affets HABTM behavior?为什么 Rails 模型中的“包含”位置会影响 HABTM 行为?
【发布时间】:2017-05-19 18:46:05
【问题描述】:

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

app/models/user.rb

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


app/models/image.rb

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


app/models/video.rb

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

app/models/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


app/models/concerns/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)

它将在image.tags 中给出 1 个项目,但在 video.tags 中给出 2 个重复项目

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

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 :tags 移动到video.rb 文件中,两个代码示例都可以正常工作。

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

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-5 ruby-on-rails-5.1


    【解决方案1】:

    您似乎需要检查这两行 tag = user.tags.find_or_create_by(text: tag_text) self.tags << tag

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

    【讨论】:

    • 我编辑了问题并添加了 tag.rb 内容。对我来说奇怪的是,图像和视频的两条线是相等的。当我调试我的应用程序时,我注意到在两个不同的位置添加了两次 habtm 关联。执行self.tags &lt;&lt; tag 行时添加第一条记录,保存视频记录时添加第二条记录。
    • 对不起我的英语:) 通过说 在标签函数中避免将标签关联与用户关联可能会很好。 你的意思是我应该重命名 has_many :tags去别的地方?
    【解决方案2】:

    我认为你有一个 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
    

    【讨论】:

    • 有几个库很可能比我在 20 分钟内编写的代码好 100 倍
    • 感谢您的代码。我的 hack 的目的是能够创建一个记录,通过 create 方法传递所有标签。否则我必须在模型创建后添加标签。我真的不需要单独的表,因为我的连接表从不直接查询,而且关系非常简单。只是想知道为什么我会得到这种奇怪的行为。我不太明白你关于域的第一句话。
    • 您在将现实映射到模型方面做得很差。您可以使用accepts_nested_attributes_for 并在控制器中拆分/解析一串标签,从而以一种不那么老套的方式做到这一点。
    • "我真的不需要单独的表,因为我的连接表从不直接查询,而且关系非常简单"。没有。你有一个三向连接的情况,这意味着使用 HABTM 会导致你一堆 greif。 blog.flatironschool.com/…
    • 你的数据库布局也缺乏规范化——你应用的每个标签都是唯一的标签,而不是仅仅指向一个具有规范化定义的表。这意味着您需要使用字符串搜索来查找类似的项目,而不是使用优化的连接。
    猜你喜欢
    • 2013-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多