<commit_before>module Post::TagMethods
  attr_accessor :tags, :new_tags, :old_tags, :old_cached_tags

  module ClassMethods
    def find_by_tags(tags, options = {})
      find_by_sql(Post.generate_sql(tags, options))
    end

    def recalculate_cached_tags(id = nil)
      conds = []
      cond_params = []

      sql = %{
        UPDATE posts p SET cached_tags = (
          SELECT array_to_string(coalesce(array(
            SELECT t.name
            FROM tags t, posts_tags pt
            WHERE t.id = pt.tag_id AND pt.post_id = p.id
            ORDER BY t.name
          ), '{}'::text[]), ' ')
        )
      }

      if id
        conds << "WHERE p.id = ?"
        cond_params << id
      end

      sql = [sql, conds].join(" ")
      execute_sql sql, *cond_params
    end

    # new, previous and latest are History objects for cached_tags.  Split
    # the tag changes apart.
    def tag_changes(new, previous, latest)
      new_tags = new.value.scan(/\S+/)
      old_tags = (previous.value rescue "").scan(/\S+/)
      latest_tags = latest.value.scan(/\S+/)

      {
        :added_tags => new_tags - old_tags,
        :removed_tags => old_tags - new_tags,
        :unchanged_tags => new_tags & old_tags,
        :obsolete_added_tags => (new_tags - old_tags) - latest_tags,
        :obsolete_removed_tags => (old_tags - new_tags) & latest_tags
      }
    end
  end

  def self.included(m)
    m.extend ClassMethods
    m.before_save :commit_metatags
    m.after_save :commit_tags
    m.after_save :save_post_history
    m.has_many :tag_history, lambda { order "id DESC" }, :class_name => "PostTagHistory", :table_name => "post_tag_histories"
    m.versioned :source, :default => ""
    m.versioned :cached_tags
  end

  def cached_tags_undo(change, redo_changes = false)
    current_tags = cached_tags.scan(/\S+/)
    prev = change.previous

    change, prev = prev, change if redo_changes
    changes = Post.tag_changes(change, prev, change.latest)
    new_tags = (current_tags - changes[:added_tags]) | changes[:removed_tags]
    self.attributes = { :tags => new_tags.join(" ") }
  end

  def cached_tags_redo(change)
    cached_tags_undo(change, true)
  end

  # === Parameters
  # * :tag<String>:: the tag to search for
  def has_tag?(tag)
    cached_tags =~ /(^|\s)#{tag}($|\s)/
  end

  # Returns the tags in a URL suitable string
  def tag_title
    title_tags.gsub(/\W+/, "-")[0, 50]
  end

  # Return the tags we display in URLs, page titles, etc.
  def title_tags
    ret = ""
    ret << cached_tags
    ret
  end

  def tags
    cached_tags
  end

  # Sets the tags for the post. Does not actually save anything to the database when called.
  #
  # === Parameters
  # * :tags<String>:: a whitespace delimited list of tags
  def tags=(tags)
    self.new_tags = Tag.scan_tags(tags)

    current_tags = cached_tags.scan(/\S+/)
    self.touch_change_seq! if new_tags != current_tags
  end

  # Returns all versioned tags and metatags.
  def cached_tags_versioned
    ["rating:" + rating, cached_tags].join(" ")
  end
<commit_msg>Better title tag sorting.<commit_after>module Post::TagMethods
  attr_accessor :tags, :new_tags, :old_tags, :old_cached_tags

  module ClassMethods
    def find_by_tags(tags, options = {})
      find_by_sql(Post.generate_sql(tags, options))
    end

    def recalculate_cached_tags(id = nil)
      conds = []
      cond_params = []

      sql = %{
        UPDATE posts p SET cached_tags = (
          SELECT array_to_string(coalesce(array(
            SELECT t.name
            FROM tags t, posts_tags pt
            WHERE t.id = pt.tag_id AND pt.post_id = p.id
            ORDER BY t.name
          ), '{}'::text[]), ' ')
        )
      }

      if id
        conds << "WHERE p.id = ?"
        cond_params << id
      end

      sql = [sql, conds].join(" ")
      execute_sql sql, *cond_params
    end

    # new, previous and latest are History objects for cached_tags.  Split
    # the tag changes apart.
    def tag_changes(new, previous, latest)
      new_tags = new.value.scan(/\S+/)
      old_tags = (previous.value rescue "").scan(/\S+/)
      latest_tags = latest.value.scan(/\S+/)

      {
        :added_tags => new_tags - old_tags,
        :removed_tags => old_tags - new_tags,
        :unchanged_tags => new_tags & old_tags,
        :obsolete_added_tags => (new_tags - old_tags) - latest_tags,
        :obsolete_removed_tags => (old_tags - new_tags) & latest_tags
      }
    end
  end

  def self.included(m)
    m.extend ClassMethods
    m.before_save :commit_metatags
    m.after_save :commit_tags
    m.after_save :save_post_history
    m.has_many :tag_history, lambda { order "id DESC" }, :class_name => "PostTagHistory", :table_name => "post_tag_histories"
    m.versioned :source, :default => ""
    m.versioned :cached_tags
  end

  def cached_tags_undo(change, redo_changes = false)
    current_tags = cached_tags.scan(/\S+/)
    prev = change.previous

    change, prev = prev, change if redo_changes
    changes = Post.tag_changes(change, prev, change.latest)
    new_tags = (current_tags - changes[:added_tags]) | changes[:removed_tags]
    self.attributes = { :tags => new_tags.join(" ") }
  end

  def cached_tags_redo(change)
    cached_tags_undo(change, true)
  end

  # === Parameters
  # * :tag<String>:: the tag to search for
  def has_tag?(tag)
    cached_tags =~ /(^|\s)#{tag}($|\s)/
  end

  # Returns the tags in a URL suitable string
  def tag_title
    title_tags.gsub(/\W+/, "-")[0, 50]
  end

  # Return the tags we display in URLs, page titles, etc.
  def title_tags
    Tag
      .sort_by_type(cached_tags)
      .map { |t| t[1] }
      .join(" ")
  end

  def tags
    cached_tags
  end

  # Sets the tags for the post. Does not actually save anything to the database when called.
  #
  # === Parameters
  # * :tags<String>:: a whitespace delimited list of tags
  def tags=(tags)
    self.new_tags = Tag.scan_tags(tags)

    current_tags = cached_tags.scan(/\S+/)
    self.touch_change_seq! if new_tags != current_tags
  end

  # Returns all versioned tags and metatags.
  def cached_tags_versioned
    ["rating:" + rating, cached_tags].join(" ")
  end

{'fraction_non_alphanumeric': 0.11702473958333333, 'fraction_numerical': 0.0011393229166666667, 'mean_word_length': 2.6104582843713278, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '751787', 'n_tokens_mistral': 2102, 'n_tokens_neox': 2045, 'n_words': 517}