category.rb 3.47 KB
class Category < ActiveRecord::Base

  validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('%{fn} cannot be like that.')
  validates_presence_of :name, :environment_id
  validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('%{fn} is already being used by another category.')
  belongs_to :environment

  validates_inclusion_of :display_color, :in => [ 1, 2, 3, 4, nil ]
  validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('%{fn} was already assigned to another category.')

  def validate
    if self.parent && (self.class != self.parent.class)
      self.errors.add(:type, _("%{fn} must be the same as the parents'"))
    end
  end

  acts_as_tree :order => 'name'

  # calculates the full name of a category by accessing the name of all its
  # ancestors.
  #
  # If you have this category hierarchy:
  #   Category "A"
  #     Category "B"
  #       Category "C"
  #
  # Then Category "C" will have "A/B/C" as its full name.
  def full_name(sep = '/')
    my_name = self.name ? self.name : '?'
    self.parent ? (self.parent.full_name(sep) + sep + my_name) : (my_name)
  end

  # calculates the level of the category in the category hierarchy. Top-level
  # categories have level 0; the children of the top-level categories have
  # level 1; the children of categories with level 1 have level 2, and so on.
  #
  #      A    level 0
  #     / \
  #    B   C  level 1
  #   / \ / \
  #   E F G H level 2
  #     ...
  def level
    self.parent ? (self.parent.level + 1) : 0
  end

  # Is this category a top-level category?
  def top_level?
    self.parent.nil?
  end

  # Is this category a leaf in the hierarchy tree of categories?
  #
  # Being a leaf means that this category has no subcategories.
  def leaf?
    self.children.empty?
  end

  # Finds all top level categories for a given environment. 
  def self.top_level_for(environment)
    self.find(:all, :conditions => ['parent_id is null and environment_id = ?', environment.id ])
  end

  # used to know when to trigger batch renaming
  attr_accessor :recalculate_path

  # sets the name of the category. Also sets #slug accordingly.
  def name=(value)
    if self.name != value
      self.recalculate_path = true
    end

    self[:name] = value
    unless self.name.blank?
      self.slug = self.name.transliterate.downcase.gsub( /[^-a-z0-9~\s\.:;+=_]/, '').gsub(/[\s\.:;=_+]+/, '-').gsub(/[\-]{2,}/, '-').to_s
    end
  end

  # sets the slug of the category. Also sets the path with the new slug value.
  def slug=(value)
    self[:slug] = value
    unless self.slug.blank?
      self.path = self.calculate_path
    end
  end

  # calculates the full path to this category using parent's path.
  def calculate_path
    if self.top_level?
      self.slug
    else
      self.parent.calculate_path + "/" + self.slug
    end
  end

  # calculate the right path
  before_create do |cat|
    if cat.path == cat.slug && (! cat.top_level?)
      cat.path = cat.calculate_path
    end
  end

  # when renaming a category, all children categories must have their paths
  # recalculated
  after_update do |cat|
    if cat.recalculate_path
      cat.children.each do |item|
        item.path = item.calculate_path
        item.recalculate_path = true
        item.save!
      end
    end
    cat.recalculate_path = false
  end

  def top_ancestor
    self.top_level? ? self : self.parent.top_ancestor
  end

  def explode_path
    path.split(/\//)
  end

end