如何只对 current_user 使用 tag_cloud?
How to only use tag_cloud for current_user?
在我的 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
您的问题是 acts-as-taggable-on
实际上没有使用您的标签 class。或者更具体地说 - 当您执行 Tags.top_20
时它会起作用,因为您是在您的标签 class 上调用它。但是 User#tags
关系实际上使用 ActsAsTaggableOn::Tag
这解释了 NoMethodError
.
ActsAsTaggableOn 似乎 have this functionality built in 已经:
current_user.tags.most_used(20)
加法:
I am combining the tags from several model instances into a single tag
cloud, which I am showing in the sidebar.
此要求中没有任何内容表明您需要创建 Tag
或 Tagging
class。事实上,这样做可能会给您和其他开发人员带来很多麻烦。主页上的标签云工作的事实并没有改变这样一个事实,即您很可能通过用微小的存根替换两个相对复杂的组件 (Tagging and Tag 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。
在我的 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
您的问题是 acts-as-taggable-on
实际上没有使用您的标签 class。或者更具体地说 - 当您执行 Tags.top_20
时它会起作用,因为您是在您的标签 class 上调用它。但是 User#tags
关系实际上使用 ActsAsTaggableOn::Tag
这解释了 NoMethodError
.
ActsAsTaggableOn 似乎 have this functionality built in 已经:
current_user.tags.most_used(20)
加法:
I am combining the tags from several model instances into a single tag cloud, which I am showing in the sidebar.
此要求中没有任何内容表明您需要创建 Tag
或 Tagging
class。事实上,这样做可能会给您和其他开发人员带来很多麻烦。主页上的标签云工作的事实并没有改变这样一个事实,即您很可能通过用微小的存根替换两个相对复杂的组件 (Tagging and Tag 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。