RoR - 检查视图中 has_many 多态引用的存在

RoR - check presence in has_many polimorphic reference in the view

我正在制作一个包含用户、提交和评论的应用程序。我需要提交和评论都有一个 Like 按钮,所以经过一些研究我终于创建了一个 Like 模型,使用对用户的引用和对提交和评论的多态引用。由于我是 RoR 的新手,所以我分享了我的代码,这样如果有什么不对的地方你可以纠正我:

guser.rb:

class Guser < ActiveRecord::Base
  has_many :likes, dependent: :destroy
end

like.rb:

class Like < ActiveRecord::Base
  belongs_to :guser
  belongs_to :likeable, polymorphic: true
end

submission.rb:

class Submission < ActiveRecord::Base
  belongs_to :guser
  has_many :comments
  ... 
  has_many :likes, as: :likeable
  has_many :likers, through: :likes, source: :user
end

comment.rb:

class Comment < ActiveRecord::Base
  ...  
  has_many :likes, as: :likeable
  has_many :likers, through: :likes, source: :user
end

迁移create_likes:

class CreateLikes < ActiveRecord::Migration
  def change
    create_table :likes do |t|
      t.integer :likeable_id
      t.string  :likeable_type
      t.references :guser, index: true, foreign_key: true
      t.timestamps null: false
    end
  end
end

现在我的问题是:如果 submission/comment 还没有被用户点赞,我只需要显示点赞按钮。所以现在我需要做这样的事情:

<%if current_guser and not current_guser.likes.map(&:submission_id).include? submission.id %>
      <%= link_to(image_tag("https://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Green_equilateral_triangle_point_up.svg/1182px-Green_equilateral_triangle_point_up.svg.png", :width => 8, :height => 8), { :controller => "likes", :submission_id => submission.id,:action => "create"}, method: :post) %>

但现在我需要检查 likeable_type 是否为提交以及 likeable_id 是否与提交 ID 匹配。而且,在评论视图中,我必须对评论做同样的事情。我该如何做这项检查?具有多个属性的地图是否有包含函数?

谢谢!

这个怎么样?

<% if current_guser and not current_guser.likes.
        map{|x| [x.likeable_id, x.likeable_type]).
        include? [submission.id, 'Submission'] %>

或者,您可以搜索完整对象:

<% if current_guser and not current_guser.likes.map(&:likeable).
      include?(submission) %>

请注意,对于后一个选项,您可能希望在控制器操作中使用 includes 以确保避免 N+1 查询:

@user_likes = Like.includes(:likeable).where(guser_id: current_guser.id)

您可能要考虑的另一个选项是进行散列查找(查找键的 O(1))而不是 运行 多个 include? 语句(每次 O(N)你正在寻找一把钥匙)。 例如:

# app/models/guser.rb
class Guser < ActiveRecord::Base
  # @return [Hash] of the structure { ['Submission', 5] => <Like object> }
  def self.lookup_for_user(guser_id)
    # With `pluck` no active record objects are created, saving some overhead
    arr = where(guser_id: guser_id).pluck(:likeable_type, :likeable_id).map{|x| [x, true]}
    Hash[arr]
  end
end

# app/controllers/your_controller.rb
def your_action
  @user_likes_lookup = Like.lookup_for_user(current_guser.id)
end

然后您需要在视图中做的就是:

<% if current_guser and not @user_likes_lookup[['Submission', submission.id]] %>

最后,请注意,随着系统的发展和每个用户的点赞数变大,这些方法的性能不是很好,因为您要加载一个人所做的所有点赞。然后您可能想考虑限制数据库查询以仅提取您在页面上呈现的提交和评论 ID。

例如,您可以将 Guser::lookup_for_user 更改为:

# app/models/guser.rb
class Guser < ActiveRecord::Base
  # @return [Hash] of the structure { ['Submission', 5] => <Like object> }
  def self.lookup_for_user(guser_id, likeable_objects_to_find=[])
    arr = where(guser_id: guser_id, likeable: likeable_objects_to_find).pluck(:likeable_type, :likeable_id).map{|x| [x, true]}
    Hash[arr]
  end
end

然后在你的控制器中:

# app/controllers/your_controller.rb
def your_action
  @submissions = # ... || []
  @comments = # ... || []
  @user_likes_lookup = Like.lookup_for_user(current_guser.id, @submissions + @comments)
end

我通过将以下方法添加到一个可爱的模型(在我的例子中是 Post 或评论)解决了同样的问题:

  def is_liked_by user
    if defined? user
      self.likes.find_by(user: user) # likes referes to the relation declaration: has_many :likes, :as => :likeable, :dependent => :destroy
    else 
      false
    end
  end

您当然应该为自己的用户模型自定义它。

检查是否有人喜欢 post 在您的视图中使用此选项:

unless likeable.is_liked_by current_user
  # do your magic
else
  # do some other stuff (like unlike :))
end