padrino-support/lib/padrino-support/inflections.rb
##
# This module is based on Sequel 4.27.0
# sequel-4.27.0/lib/sequel/model/inflections.rb
#
require 'padrino-support/default_inflections'
module Padrino
# This module acts as a singleton returned/yielded by Sequel.inflections,
# which is used to override or specify additional inflection rules
# for Sequel. Examples:
#
# Sequel.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1\2en'
# inflect.singular /^(ox)en/i, '\1'
#
# inflect.irregular 'octopus', 'octopi'
#
# inflect.uncountable "equipment"
# end
#
# New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
# already have been loaded.
module Inflections
CAMELIZE_CONVERT_REGEXP = /(^|_)(.)/.freeze
CAMELIZE_MODULE_REGEXP = /\/(.?)/.freeze
DASH = '-'.freeze
DEMODULIZE_CONVERT_REGEXP = /^.*::/.freeze
EMPTY_STRING= ''.freeze
SLASH = '/'.freeze
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
UNDERSCORE = '_'.freeze
UNDERSCORE_CONVERT_REGEXP1 = /([A-Z]+)([A-Z][a-z])/.freeze
UNDERSCORE_CONVERT_REGEXP2 = /([a-z\d])([A-Z])/.freeze
UNDERSCORE_CONVERT_REPLACE = '\1_\2'.freeze
UNDERSCORE_MODULE_REGEXP = /::/.freeze
@plurals, @singulars, @uncountables = [], [], []
class << self
# Array of two element arrays, first containing a regex, and the second containing a substitution pattern, used for plurization.
attr_reader :plurals
# Array of two element arrays, first containing a regex, and the second containing a substitution pattern, used for singularization.
attr_reader :singulars
# Array of strings for words were the singular form is the same as the plural form
attr_reader :uncountables
end
# Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
# the options are: :plurals, :singulars, :uncountables
#
# Examples:
# clear :all
# clear :plurals
def self.clear(scope = :all)
case scope
when :all
@plurals, @singulars, @uncountables = [], [], []
else
instance_variable_set("@#{scope}", [])
end
end
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
#
# Examples:
# irregular 'octopus', 'octopi'
# irregular 'person', 'people'
def self.irregular(singular, plural)
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
end
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
# The replacement should always be a string that may include references to the matched data from the rule.
#
# Example:
# plural(/(x|ch|ss|sh)$/i, '\1es')
def self.plural(rule, replacement)
@plurals.insert(0, [rule, replacement])
end
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
# The replacement should always be a string that may include references to the matched data from the rule.
#
# Example:
# singular(/([^aeiouy]|qu)ies$/i, '\1y')
def self.singular(rule, replacement)
@singulars.insert(0, [rule, replacement])
end
# Add uncountable words that shouldn't be attempted inflected.
#
# Examples:
# uncountable "money"
# uncountable "money", "information"
# uncountable %w( money information rice )
def self.uncountable(*words)
(@uncountables << words).flatten!
end
instance_eval(&DEFAULT_INFLECTIONS_PROC)
extend self
# Convert the given string to CamelCase. Will also convert '/' to '::' which is useful for converting paths to namespaces.
def camelize(s)
s = s.to_s
return s.camelize if s.respond_to?(:camelize)
s = s.gsub(CAMELIZE_MODULE_REGEXP){|x| "::#{x[-1..-1].upcase unless x == SLASH}"}.gsub(CAMELIZE_CONVERT_REGEXP){|x| x[-1..-1].upcase}
s
end
# Tries to find a declared constant with the name specified
# in the string. It raises a NameError when the name is not in CamelCase
# or is not initialized.
def constantize(s)
s = s.to_s
return s.constantize if s.respond_to?(:constantize)
raise(NameError, "#{s.inspect} is not a valid constant name!") unless m = VALID_CONSTANT_NAME_REGEXP.match(s)
Object.module_eval("::#{m[1]}", __FILE__, __LINE__)
end
# Removes the module part from the expression in the string
def demodulize(s)
s = s.to_s
return s.demodulize if s.respond_to?(:demodulize)
s.gsub(DEMODULIZE_CONVERT_REGEXP, EMPTY_STRING)
end
# Returns the plural form of the word in the string.
def pluralize(s)
s = s.to_s
return s.pluralize if s.respond_to?(:pluralize)
result = s.dup
Inflections.plurals.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
result
end
# The reverse of pluralize, returns the singular form of a word in a string.
def singularize(s)
s = s.to_s
return s.singularize if s.respond_to?(:singularize)
result = s.dup
Inflections.singulars.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
result
end
# The reverse of camelize. Makes an underscored form from the expression in the string.
# Also changes '::' to '/' to convert namespaces to paths.
def underscore(s)
s = s.to_s
return s.underscore if s.respond_to?(:underscore)
s.gsub(UNDERSCORE_MODULE_REGEXP, SLASH).gsub(UNDERSCORE_CONVERT_REGEXP1, UNDERSCORE_CONVERT_REPLACE).
gsub(UNDERSCORE_CONVERT_REGEXP2, UNDERSCORE_CONVERT_REPLACE).tr(DASH, UNDERSCORE).downcase
end
##
# Capitalizes the first word, turns underscores into spaces, and strips a trailing '_id' if present.
#
def humanize(s)
s = s.to_s
return s.humanize if s.respond_to?(:humanize)
s.gsub(/_id$/, '').tr('_', ' ').capitalize
end
##
# Create a class name from a plural table name like Rails does for table names to models.
#
def classify(s)
s = s.to_s
return s.classify if s.respond_to?(:classify)
camelize(singularize(s.sub(/.*\./, '')))
end
end
end