File: active_support/inflector.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: ActiveSupport#6
  module: Inflector#15
extends
  Inflector ( ActiveSupport )
has properties
method: inflections #123
method: pluralize / 1 #139
method: singularize / 1 #158
method: camelize / 2 #179
method: titleize / 1 #196
method: underscore / 1 #207
method: dasherize / 1 #219
method: humanize / 1 #229
method: demodulize / 1 #241
method: parameterize / 2 #260
method: transliterate (1/3) / 1 #279
method: transliterate (2/3) / 1 #285
method: transliterate (3/E) / 1 #294
method: tableize / 1 #307
method: classify / 1 #321
method: foreign_key / 2 #334
method: constantize (1/2) / 1 #358
method: constantize (2/E) / 1 #369
method: ordinalize / 1 #389
  class: Inflections#33
includes
  Singleton ( Builtin-Module )
inherits from
  Object ( Builtin-Module )
has properties
attribute: plurals [R] #36
attribute: singulars [R] #36
attribute: uncountables [R] #36
attribute: humans [R] #36
method: initialize #38
method: plural / 2 #44
method: singular / 2 #52
method: irregular / 2 #64
method: uncountable / 1 #84
method: human / 2 #95
method: clear / 1 #106

Class Hierarchy

Code

   1  # encoding: utf-8
   2  require 'singleton'
   3  require 'iconv'
   4  require 'kconv'
   5 
   6  module ActiveSupport
   7    # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
   8    # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
   9    # in inflections.rb.
  10    #
  11    # The Rails core team has stated patches for the inflections library will not be accepted
  12    # in order to avoid breaking legacy applications which may be relying on errant inflections.
  13    # If you discover an incorrect inflection and require it for your application, you'll need
  14    # to correct it yourself (explained below).
  15    module Inflector
  16      extend self
  17 
  18      # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
  19      # inflection rules. Examples:
  20      #
  21      #   ActiveSupport::Inflector.inflections do |inflect|
  22      #     inflect.plural /^(ox)$/i, '\1\2en'
  23      #     inflect.singular /^(ox)en/i, '\1'
  24      #
  25      #     inflect.irregular 'octopus', 'octopi'
  26      #
  27      #     inflect.uncountable "equipment"
  28      #   end
  29      #
  30      # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
  31      # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
  32      # already have been loaded.
  33      class Inflections
  34        include Singleton
  35 
  36        attr_reader :plurals, :singulars, :uncountables, :humans
  37 
  38        def initialize
  39          @plurals, @singulars, @uncountables, @humans = [], [], [], []
  40        end
  41 
  42        # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
  43        # The replacement should always be a string that may include references to the matched data from the rule.
  44        def plural(rule, replacement)
  45          @uncountables.delete(rule) if rule.is_a?(String)
  46          @uncountables.delete(replacement)
  47          @plurals.insert(0, [rule, replacement])
  48        end
  49 
  50        # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
  51        # The replacement should always be a string that may include references to the matched data from the rule.
  52        def singular(rule, replacement)
  53          @uncountables.delete(rule) if rule.is_a?(String)
  54          @uncountables.delete(replacement)
  55          @singulars.insert(0, [rule, replacement])
  56        end
  57 
  58        # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
  59        # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
  60        #
  61        # Examples:
  62        #   irregular 'octopus', 'octopi'
  63        #   irregular 'person', 'people'
  64        def irregular(singular, plural)
  65          @uncountables.delete(singular)
  66          @uncountables.delete(plural)
  67          if singular[0,1].upcase == plural[0,1].upcase
  68            plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
  69            singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
  70          else
  71            plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
  72            plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
  73            singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
  74            singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
  75          end
  76        end
  77 
  78        # Add uncountable words that shouldn't be attempted inflected.
  79        #
  80        # Examples:
  81        #   uncountable "money"
  82        #   uncountable "money", "information"
  83        #   uncountable %w( money information rice )
  84        def uncountable(*words)
  85          (@uncountables << words).flatten!
  86        end
  87 
  88        # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
  89        # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
  90        # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
  91        #
  92        # Examples:
  93        #   human /_cnt$/i, '\1_count'
  94        #   human "legacy_col_person_name", "Name"
  95        def human(rule, replacement)
  96          @humans.insert(0, [rule, replacement])
  97        end
  98 
  99        # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
 100        # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
 101        # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
 102        #
 103        # Examples:
 104        #   clear :all
 105        #   clear :plurals
 106        def clear(scope = :all)
 107          case scope
 108            when :all
 109              @plurals, @singulars, @uncountables = [], [], []
 110            else
 111              instance_variable_set "@#{scope}", []
 112          end
 113        end
 114      end
 115 
 116      # Yields a singleton instance of Inflector::Inflections so you can specify additional
 117      # inflector rules.
 118      #
 119      # Example:
 120      #   ActiveSupport::Inflector.inflections do |inflect|
 121      #     inflect.uncountable "rails"
 122      #   end
 123      def inflections
 124        if block_given?
 125          yield Inflections.instance
 126        else
 127          Inflections.instance
 128        end
 129      end
 130 
 131      # Returns the plural form of the word in the string.
 132      #
 133      # Examples:
 134      #   "post".pluralize             # => "posts"
 135      #   "octopus".pluralize          # => "octopi"
 136      #   "sheep".pluralize            # => "sheep"
 137      #   "words".pluralize            # => "words"
 138      #   "CamelOctopus".pluralize     # => "CamelOctopi"
 139      def pluralize(word)
 140        result = word.to_s.dup
 141 
 142        if word.empty? || inflections.uncountables.include?(result.downcase)
 143          result
 144        else
 145          inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
 146          result
 147        end
 148      end
 149 
 150      # The reverse of +pluralize+, returns the singular form of a word in a string.
 151      #
 152      # Examples:
 153      #   "posts".singularize            # => "post"
 154      #   "octopi".singularize           # => "octopus"
 155      #   "sheep".singluarize            # => "sheep"
 156      #   "word".singularize             # => "word"
 157      #   "CamelOctopi".singularize      # => "CamelOctopus"
 158      def singularize(word)
 159        result = word.to_s.dup
 160 
 161        if inflections.uncountables.any? { |inflection| result =~ /#{inflection}\Z/i }
 162          result
 163        else
 164          inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
 165          result
 166        end
 167      end
 168 
 169      # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
 170      # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
 171      #
 172      # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
 173      #
 174      # Examples:
 175      #   "active_record".camelize                # => "ActiveRecord"
 176      #   "active_record".camelize(:lower)        # => "activeRecord"
 177      #   "active_record/errors".camelize         # => "ActiveRecord::Errors"
 178      #   "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
 179      def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
 180        if first_letter_in_uppercase
 181          lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
 182        else
 183          lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
 184        end
 185      end
 186 
 187      # Capitalizes all the words and replaces some characters in the string to create
 188      # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
 189      # used in the Rails internals.
 190      #
 191      # +titleize+ is also aliased as as +titlecase+.
 192      #
 193      # Examples:
 194      #   "man from the boondocks".titleize # => "Man From The Boondocks"
 195      #   "x-men: the last stand".titleize  # => "X Men: The Last Stand"
 196      def titleize(word)
 197        humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
 198      end
 199 
 200      # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
 201      #
 202      # Changes '::' to '/' to convert namespaces to paths.
 203      #
 204      # Examples:
 205      #   "ActiveRecord".underscore         # => "active_record"
 206      #   "ActiveRecord::Errors".underscore # => active_record/errors
 207      def underscore(camel_cased_word)
 208        camel_cased_word.to_s.gsub(/::/, '/').
 209          gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
 210          gsub(/([a-z\d])([A-Z])/,'\1_\2').
 211          tr("-", "_").
 212          downcase
 213      end
 214 
 215      # Replaces underscores with dashes in the string.
 216      #
 217      # Example:
 218      #   "puni_puni" # => "puni-puni"
 219      def dasherize(underscored_word)
 220        underscored_word.gsub(/_/, '-')
 221      end
 222 
 223      # Capitalizes the first word and turns underscores into spaces and strips a
 224      # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
 225      #
 226      # Examples:
 227      #   "employee_salary" # => "Employee salary"
 228      #   "author_id"       # => "Author"
 229      def humanize(lower_case_and_underscored_word)
 230        result = lower_case_and_underscored_word.to_s.dup
 231 
 232        inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
 233        result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
 234      end
 235 
 236      # Removes the module part from the expression in the string.
 237      #
 238      # Examples:
 239      #   "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
 240      #   "Inflections".demodulize                                       # => "Inflections"
 241      def demodulize(class_name_in_module)
 242        class_name_in_module.to_s.gsub(/^.*::/, '')
 243      end
 244 
 245      # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
 246      #
 247      # ==== Examples
 248      #
 249      #   class Person
 250      #     def to_param
 251      #       "#{id}-#{name.parameterize}"
 252      #     end
 253      #   end
 254      #
 255      #   @person = Person.find(1)
 256      #   # => #<Person id: 1, name: "Donald E. Knuth">
 257      #
 258      #   <%= link_to(@person.name, person_path(@person)) %>
 259      #   # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
 260      def parameterize(string, sep = '-')
 261        # remove malformed utf8 characters
 262        string = string.toutf8 unless string.is_utf8?
 263        # replace accented chars with ther ascii equivalents
 264        parameterized_string = transliterate(string)
 265        # Turn unwanted chars into the seperator
 266        parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
 267        unless sep.blank?
 268          re_sep = Regexp.escape(sep)
 269          # No more than one of the separator in a row.
 270          parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
 271          # Remove leading/trailing separator.
 272          parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
 273        end
 274        parameterized_string.downcase
 275      end
 276 
 277 
 278      # Replaces accented characters with their ascii equivalents.
 279      def transliterate(string)
 280        Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s
 281      end
 282 
 283      if RUBY_VERSION >= '1.9'
 284        undef_method :transliterate
 285        def transliterate(string)
 286          warn "Ruby 1.9 doesn't support Unicode normalization yet"
 287          string.dup
 288        end
 289 
 290      # The iconv transliteration code doesn't function correctly
 291      # on some platforms, but it's very fast where it does function.
 292      elsif "foo" != (Inflector.transliterate("föö") rescue nil)
 293        undef_method :transliterate
 294        def transliterate(string)
 295          string.mb_chars.normalize(:kd). # Decompose accented characters
 296            gsub(/[^\x00-\x7F]+/, '')     # Remove anything non-ASCII entirely (e.g. diacritics).
 297        end
 298      end
 299 
 300      # Create the name of a table like Rails does for models to table names. This method
 301      # uses the +pluralize+ method on the last word in the string.
 302      #
 303      # Examples
 304      #   "RawScaledScorer".tableize # => "raw_scaled_scorers"
 305      #   "egg_and_ham".tableize     # => "egg_and_hams"
 306      #   "fancyCategory".tableize   # => "fancy_categories"
 307      def tableize(class_name)
 308        pluralize(underscore(class_name))
 309      end
 310 
 311      # Create a class name from a plural table name like Rails does for table names to models.
 312      # Note that this returns a string and not a Class. (To convert to an actual class
 313      # follow +classify+ with +constantize+.)
 314      #
 315      # Examples:
 316      #   "egg_and_hams".classify # => "EggAndHam"
 317      #   "posts".classify        # => "Post"
 318      #
 319      # Singular names are not handled correctly:
 320      #   "business".classify     # => "Busines"
 321      def classify(table_name)
 322        # strip out any leading schema name
 323        camelize(singularize(table_name.to_s.sub(/.*\./, '')))
 324      end
 325 
 326      # Creates a foreign key name from a class name.
 327      # +separate_class_name_and_id_with_underscore+ sets whether
 328      # the method should put '_' between the name and 'id'.
 329      #
 330      # Examples:
 331      #   "Message".foreign_key        # => "message_id"
 332      #   "Message".foreign_key(false) # => "messageid"
 333      #   "Admin::Post".foreign_key    # => "post_id"
 334      def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
 335        underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
 336      end
 337 
 338      # Ruby 1.9 introduces an inherit argument for Module#const_get and
 339      # #const_defined? and changes their default behavior.
 340      if Module.method(:const_get).arity == 1
 341        # Tries to find a constant with the name specified in the argument string:
 342        #
 343        #   "Module".constantize     # => Module
 344        #   "Test::Unit".constantize # => Test::Unit
 345        #
 346        # The name is assumed to be the one of a top-level constant, no matter whether
 347        # it starts with "::" or not. No lexical context is taken into account:
 348        #
 349        #   C = 'outside'
 350        #   module M
 351        #     C = 'inside'
 352        #     C               # => 'inside'
 353        #     "C".constantize # => 'outside', same as ::C
 354        #   end
 355        #
 356        # NameError is raised when the name is not in CamelCase or the constant is
 357        # unknown.
 358        def constantize(camel_cased_word)
 359          names = camel_cased_word.split('::')
 360          names.shift if names.empty? || names.first.empty?
 361 
 362          constant = Object
 363          names.each do |name|
 364            constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
 365          end
 366          constant
 367        end
 368      else
 369        def constantize(camel_cased_word) #:nodoc:
 370          names = camel_cased_word.split('::')
 371          names.shift if names.empty? || names.first.empty?
 372 
 373          constant = Object
 374          names.each do |name|
 375            constant = constant.const_get(name, false) || constant.const_missing(name)
 376          end
 377          constant
 378        end
 379      end
 380 
 381      # Turns a number into an ordinal string used to denote the position in an
 382      # ordered sequence such as 1st, 2nd, 3rd, 4th.
 383      #
 384      # Examples:
 385      #   ordinalize(1)     # => "1st"
 386      #   ordinalize(2)     # => "2nd"
 387      #   ordinalize(1002)  # => "1002nd"
 388      #   ordinalize(1003)  # => "1003rd"
 389      def ordinalize(number)
 390        if (11..13).include?(number.to_i % 100)
 391          "#{number}th"
 392        else
 393          case number.to_i % 10
 394            when 1; "#{number}st"
 395            when 2; "#{number}nd"
 396            when 3; "#{number}rd"
 397            else    "#{number}th"
 398          end
 399        end
 400      end
 401    end
 402  end
 403 
 404  # in case active_support/inflector is required without the rest of active_support
 405  require 'active_support/inflections'
 406  require 'active_support/core_ext/string/inflections'
 407  unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
 408    String.send :include, ActiveSupport::CoreExtensions::String::Inflections
 409  end