How to only use tag_cloud for current_user?

168 Views Asked by At

In my application_controller I have this:

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

which will make the tag count work for all tags from all users, but instead I want the tag_cloud to show only the tags of the current_user. I tried this:

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

but that gave me an error:

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

even though top_20 is defined in tag.rb:

class Tag < ActiveRecord::Base
  has_many :taggings

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

I'm using the acts-as-taggable-on gem. Thank you!

UPDATES

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
1

There are 1 best solutions below

15
On BEST ANSWER

Your issue is that acts-as-taggable-on does not actually use your Tag class. Or to be more specific - it works when you do Tags.top_20 since you are calling it on your tag class. But the User#tags relationship actually uses ActsAsTaggableOn::Tag which explains the NoMethodError.

ActsAsTaggableOn seems to have this functionality built in already though:

current_user.tags.most_used(20)

Addition:

I am combining the tags from several model instances into a single tag cloud, which I am showing in the sidebar.

There is nothing in this requirement which says that you need to create a Tag or Tagging class. In fact doing so is likely to cause you and other developers a bunch of grief. The fact that the tag cloud on your home page works does not change the fact that you most likely are creating a bunch of future issues by replacing two relatively complex components (Tagging and Tag classes) with tiny stubs willy-nilly.

If you look at the tag_cloud implementation is pretty easy to see that it takes a ActiveRecord::Relation or a collection or just any old enumerable such as an array.

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

Since collections are just fancy pants versions of arrays merging them together is as easy as:

@tags = foo.tags + bar.tags

However joining relations together is a bit more complex since Rails currently does not support the SQL OR clause. (Its coming in Rails 5). You would either have to load and merge the collections as above or create your own where clause with AREL if multiple SQL queries is a performance issue.