【问题标题】:How to only use tag_cloud for current_user?如何仅将 tag_cloud 用于 current_user?
【发布时间】:2015-06-18 20:47:09
【问题描述】:

在我的 application_controller 我有这个:

  def tag_cloud
    @tags = Tag.top_20.sort{ |x,y| x.id <=> y.id } if current_user
  end

这将使标签计数适用于所有用户的所有标签,但我希望tag_cloud 仅显示current_user 的标签。我试过这个:

  def tag_cloud
    @tags = current_user.tags.top_20.sort{ |x,y| x.id <=> y.id } if current_user
  end

但这给了我一个错误:

NoMethodError (undefined method `top_20' for #<ActiveRecord::Associations::CollectionProxy []>):
  app/controllers/application_controller.rb:13:in `tag_cloud'

即使top_20 是在 tag.rb 中定义的:

class Tag < ActiveRecord::Base
  has_many :taggings

    scope :top_20, -> {
      where("taggings_count != 0").order("taggings_count DESC").limit(15)
    }
end

我正在使用 acts-as-taggable-on gem。谢谢!

更新

user.rb

class User < ActiveRecord::Base
  acts_as_tagger
  acts_as_taggable
  has_many :notifications
  has_many :activities
  has_many :activity_likes
  has_many :liked_activities, through: :activity_likes, class_name: 'Activity', source: :liked_activity
  has_many :liked_comments, through: :comment_likes, class_name: 'Comment', source: :liked_comment
  has_many :valuation_likes
  has_many :habit_likes
  has_many :goal_likes
  has_many :quantified_likes
  has_many :comment_likes
  has_many :authentications
  has_many :habits, dependent: :destroy
  has_many :levels
  has_many :combine_tags
  has_many :valuations, dependent: :destroy
  has_many :comments
  has_many :goals, dependent: :destroy
  has_many :quantifieds, dependent: :destroy
  has_many :results, through: :quantifieds
  has_many :notes
  accepts_nested_attributes_for :habits, :reject_if => :all_blank, :allow_destroy => true
  accepts_nested_attributes_for :notes, :reject_if => :all_blank, :allow_destroy => true
  accepts_nested_attributes_for :quantifieds, :reject_if => :all_blank, :allow_destroy => true
  accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  has_many :followers, through: :passive_relationships, source: :follower
  attr_accessor :remember_token, :activation_token, :reset_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }, unless: -> { from_omniauth? }
  has_secure_password
  validates :password, length: { minimum: 6 }
  scope :publish, ->{ where(:conceal => false) }
  User.tag_counts_on(:tags)

  def count_mastered
    @res = habits.reduce(0) do |count, habit|
    habit.current_level == 6 ? count + 1 : count
    end
  end

  def count_challenged
    @challenged_count = habits.count - @res
  end

    def self.from_omniauth(auth)
      where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
        user.provider = auth.provider
        user.image = auth.info.image
        user.uid = auth.uid
        user.name = auth.info.name
        user.oauth_token = auth.credentials.token
        user.oauth_expires_at = Time.at(auth.credentials.expires_at)
        user.password = (0...8).map { (65 + rand(26)).chr }.join
        user.email = (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
        user.save!
      end
    end

  def self.koala(auth)
    access_token = auth['token']
    facebook = Koala::Facebook::API.new(access_token)
    facebook.get_object("me?fields=name,picture")
  end


  # Returns the hash digest of the given string.
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # Remembers a user in the database for use in persistent sessions.
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  # Forgets a user. NOT SURE IF I REMOVE
  def forget
    update_attribute(:remember_digest, nil)
  end

  # Returns true if the given token matches the digest.
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

  # Activates an account.
  def activate
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # Sends activation email.
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end

  def create_reset_digest
    self.reset_token = User.new_token
    update_attribute(:reset_digest,  User.digest(reset_token))
    update_attribute(:reset_sent_at, Time.zone.now)
  end

  # Sends password reset email.
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end

   # Returns true if a password reset has expired.
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

  def good_results_count
    results.good_count
  end

  # Follows a user.
  def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
  end

  # Unfollows a user.
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # Returns true if the current user is following the other user.
  def following?(other_user)
    following.include?(other_user)
  end

private 

    def from_omniauth? 
      provider && uid 
    end

      # Converts email to all lower-case.
    def downcase_email 
      self.email = email.downcase unless from_omniauth? 
    end

    # Creates and assigns the activation token and digest.
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

tags_controller

class TagsController < ApplicationController
  def index
    @tags = Tag.all
  end

  def show
    @tag = Tag.find(params[:id])
  end
end

routes.rb

get 'tags/:tag', to: 'pages#home', as: :tag

schema.rb

  create_table "taggings", force: true do |t|
    t.integer  "tag_id"
    t.integer  "taggable_id"
    t.string   "taggable_type"
    t.integer  "tagger_id"
    t.string   "tagger_type"
    t.string   "context",       limit: 128
    t.datetime "created_at"
  end

  add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true
  add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"

  create_table "tags", force: true do |t|
    t.string  "name"
    t.integer "taggings_count", default: 0
  end

  add_index "tags", ["name"], name: "index_tags_on_name", unique: true

  create_table "users", force: true do |t|
    t.string   "name"
    t.boolean  "conceal",           default: false
    t.string   "email"
    t.text     "missed_days"
    t.text     "missed_levels"
    t.string   "provider"
    t.string   "uid"
    t.string   "oauth_token"
    t.datetime "oauth_expires_at"
    t.datetime "created_at",                        null: false
    t.datetime "updated_at",                        null: false
    t.string   "password_digest"
    t.string   "remember_digest"
    t.boolean  "admin",             default: false
    t.string   "activation_digest"
    t.boolean  "activated",         default: false
    t.datetime "activated_at"
    t.string   "reset_digest"
    t.datetime "reset_sent_at"
    t.string   "image"
  end

goal.rb

class Goal < ActiveRecord::Base
    scope :publish, ->{ where(:conceal => false) }
    belongs_to :user
    has_many :comments
    has_many :notifications
    has_many :notes
    acts_as_taggable
    scope :accomplished, -> { where(accomplished: true) }
    scope :unaccomplished, -> { where(accomplished: false) }
    scope :private_submit, -> { where(private_submit: true) }
    scope :public_submit, -> { where(private_submit: false) }
    validates :name, presence: true
    has_many :goal_likes
  has_many :likers, through: :goal_likes, class_name: 'User', source: :liker

    scope :top_3, -> do
      order("deadline").
      limit(3)
    end
end

【问题讨论】:

  • 我仍然对你的意思有点困惑:“因为我的 tag_cloud 使用多个模型”。您是否尝试将来自多个模型实例的标签组合到一个标签云中?无论如何,我认为您的自定义标签、标记器/类和迁移正在搞乱 ActsAsTaggable,您可能会将标签添加到创建它们时所期望的错误表中。
  • 是的,我正在将来自多个模型实例的标签组合成一个标签云,我将在侧边栏中显示它,这就是 def tag_cloudapplication_controller 中的原因。我不认为它搞砸了,只是因为其他一切都适用。 tag_cloud 看起来很漂亮。它只是让它只显示current_user。我希望这只是我们缺少的一个简单解决方案@maxcal
  • 由于acts-as-taggable-on 使用Rails 作用域和集合,使用.merge 或仅组合集合可能很简单——无需自定义类。如果您查看TaggingTag classes,您会发现认为您可以用一个小存根毫无问题地更换它们是非常幼稚的。
  • 那么有没有办法解决这个@maxcal ?只要它能完成工作,我就愿意让它变得丑陋。我宁愿避免重写我的所有代码,因为这是一条漫长而艰难的道路。
  • 查看我的编辑。删除这两个类可能比您想象的要少得多。

标签: ruby-on-rails ruby tags tag-cloud


【解决方案1】:

您的问题是 acts-as-taggable-on 实际上并没有使用您的 Tag 类。或者更具体地说 - 当您执行 Tags.top_20 时它可以工作,因为您在标签类上调用它。但是User#tags 关系实际上使用了ActsAsTaggableOn::Tag,这解释了NoMethodError

ActsAsTaggableOn 似乎已经 have this functionality built in 了:

current_user.tags.most_used(20)

补充:

我正在将来自多个模型实例的标签组合成一个标签 云,我在侧边栏中显示。

此要求中没有任何内容表明您需要创建 TagTagging 类。事实上这样做很可能会给你和其他开发者带来一堆悲痛。您主页上的标签云有效这一事实并没有改变这样一个事实,即您很可能通过用微小的存根替换两个相对复杂的组件(TaggingTag classes)来创建一堆未来的问题。

如果您查看tag_cloud implementation,很容易看出它需要一个 ActiveRecord::Relation 或一个集合,或者只是任何旧的可枚举对象,例如数组。

module ActsAsTaggableOn
  module TagsHelper
    # See the wiki for an example using tag_cloud.
    def tag_cloud(tags, classes)
      return [] if tags.empty?

      max_count = tags.sort_by(&:taggings_count).last.taggings_count.to_f

      tags.each do |tag|
        index = ((tag.taggings_count / max_count) * (classes.size - 1))
        yield tag, classes[index.nan? ? 0 : index.round]
      end
    end
  end
end

由于集合只是数组的花哨版本,因此将它们合并在一起很简单:

@tags = foo.tags + bar.tags

但是,将关系连接在一起有点复杂,因为 Rails 目前不支持 SQL OR 子句。 (它在 Rails 5 中出现)。如果多个 SQL 查询是性能问题,您将不得不加载和合并上述集合或create your own where clause with AREL

【讨论】:

  • 我有点懒得设置 ActsAsTaggableOn 来验证,但您可以通过 current_user.tags.first.class.name 轻松测试这一点
  • current_user.tags.most_used(10) 在 tag_cloud 中没有显示任何内容,current_user.tags.first.class.name 给出错误:ActionView::Template::Error (undefined method 'sort_by' for "NilClass":String): for the tag_cloud
  • 也许我需要在模型@maxcal 中添加一些东西以使其与我的代码一起使用?非常感谢您的帮助!
  • 我想我应该补充一点,你应该用byebugabort 测试current_user.tags.first.class.name。但结果仍然有用。你得到“NilClass”而不是“ActsAsTaggableOn::Tag”的事实告诉我 current_user 似乎没有任何标签。可以通过current_user.tags.size调试。
  • 这给了ActionView::Template::Error (undefined method empty? for 0:Fixnum):` Maxcal,这很奇怪,因为用户确实有标签。我在这样的显示页面中显示这些标签&lt;%= raw @model_name.tag_list.map { |t| link_to t.titleize, tag_path(t), class: 'label label-primary' } %&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
  • 1970-01-01
  • 2015-08-26
  • 1970-01-01
  • 2016-10-10
相关资源
最近更新 更多