roda-tags 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +24 -0
- data/.github/workflows/ruby.yml +45 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +33 -0
- data/.rubocop_todo.yml +7 -0
- data/CODE_OF_CONDUCT.md +22 -16
- data/Gemfile +86 -0
- data/Guardfile +25 -0
- data/README.md +234 -236
- data/Rakefile +14 -11
- data/lib/core_ext/blank.rb +37 -36
- data/lib/core_ext/hash.rb +39 -16
- data/lib/core_ext/object.rb +2 -2
- data/lib/core_ext/string.rb +290 -116
- data/lib/roda/plugins/tag_helpers.rb +795 -575
- data/lib/roda/plugins/tags.rb +532 -276
- data/lib/roda/tags/version.rb +2 -3
- data/lib/roda/tags.rb +2 -0
- data/roda-tags.gemspec +30 -32
- metadata +44 -128
- data/.travis.yml +0 -4
    
        data/lib/core_ext/string.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # frozen_string_literal: false
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # The inflector extension adds inflection instance methods to String, which allows the easy
         | 
| 2 4 | 
             
            # transformation of words from singular to plural, class names to table names, modularized class
         | 
| 3 | 
            -
            # names to ones without, and class names to foreign keys.  It exists for | 
| 5 | 
            +
            # names to ones without, and class names to foreign keys.  It exists for
         | 
| 4 6 | 
             
            # backwards compatibility to legacy Sequel code.
         | 
| 5 7 | 
             
            #
         | 
| 6 8 | 
             
            # To load the extension:
         | 
| @@ -17,16 +19,21 @@ class String | |
| 17 19 | 
             
              #     inflect.uncountable "equipment"
         | 
| 18 20 | 
             
              #   end
         | 
| 19 21 | 
             
              #
         | 
| 20 | 
            -
              # New rules are added at the top. So in the example above, the irregular rule for octopus will | 
| 21 | 
            -
              # now be the first of the pluralization and singularization rules that is runs. This guarantees | 
| 22 | 
            +
              # New rules are added at the top. So in the example above, the irregular rule for octopus will
         | 
| 23 | 
            +
              # now be the first of the pluralization and singularization rules that is runs. This guarantees
         | 
| 22 24 | 
             
              # that your rules run before any of the rules that may already have been loaded.
         | 
| 25 | 
            +
              #
         | 
| 23 26 | 
             
              module Inflections
         | 
| 27 | 
            +
                # Array to store plural inflection rules
         | 
| 24 28 | 
             
                @plurals   = []
         | 
| 29 | 
            +
                # Array to store singular inflection rules
         | 
| 25 30 | 
             
                @singulars = []
         | 
| 31 | 
            +
                # Array to store words that are the same in singular and plural forms
         | 
| 26 32 | 
             
                @uncountables = []
         | 
| 27 | 
            -
             | 
| 33 | 
            +
             | 
| 28 34 | 
             
                # Proc that is instance evaled to create the default inflections for both the
         | 
| 29 35 | 
             
                # model inflector and the inflector extension.
         | 
| 36 | 
            +
                # rubocop:disable Metrics/BlockLength
         | 
| 30 37 | 
             
                DEFAULT_INFLECTIONS_PROC = proc do
         | 
| 31 38 | 
             
                  plural(/$/, 's')
         | 
| 32 39 | 
             
                  plural(/s$/i, 's')
         | 
| @@ -66,29 +73,45 @@ class String | |
| 66 73 | 
             
                  irregular('quiz', 'quizzes')
         | 
| 67 74 | 
             
                  irregular('testis', 'testes')
         | 
| 68 75 |  | 
| 69 | 
            -
                  uncountable(%w | 
| 76 | 
            +
                  uncountable(%w[equipment information rice money species series fish sheep news])
         | 
| 70 77 | 
             
                end
         | 
| 71 | 
            -
                
         | 
| 72 | 
            -
             | 
| 78 | 
            +
                # rubocop:enable Metrics/BlockLength
         | 
| 79 | 
            +
             | 
| 73 80 | 
             
                class << self
         | 
| 74 | 
            -
                  # Array  | 
| 75 | 
            -
                  #  | 
| 81 | 
            +
                  # An Array that stores the pluralization rules.
         | 
| 82 | 
            +
                  # Each rule is a 2-element array containing:
         | 
| 83 | 
            +
                  # - A regular expression pattern for matching words to pluralize
         | 
| 84 | 
            +
                  # - A substitution pattern (e.g. '\1es') for transforming the match into plural form
         | 
| 85 | 
            +
                  # Rules are processed in reverse order, so newer rules take precedence.
         | 
| 76 86 | 
             
                  attr_reader :plurals
         | 
| 77 87 |  | 
| 78 | 
            -
                  # Array  | 
| 79 | 
            -
                  #  | 
| 88 | 
            +
                  # An Array that stores the singularization rules.
         | 
| 89 | 
            +
                  # Each rule is a 2-element array containing:
         | 
| 90 | 
            +
                  # - A regular expression pattern for matching plural words
         | 
| 91 | 
            +
                  # - A substitution pattern (e.g. '\1y') for transforming the match into singular form
         | 
| 92 | 
            +
                  # Rules are processed in reverse order, so newer rules take precedence.
         | 
| 80 93 | 
             
                  attr_reader :singulars
         | 
| 81 94 |  | 
| 82 | 
            -
                  # Array of  | 
| 95 | 
            +
                  # An Array of uncountable word strings that should not be inflected.
         | 
| 96 | 
            +
                  # These words have the same form in both singular and plural.
         | 
| 97 | 
            +
                  # Examples: 'fish', 'money', 'species'
         | 
| 83 98 | 
             
                  attr_reader :uncountables
         | 
| 84 99 | 
             
                end
         | 
| 85 100 |  | 
| 86 | 
            -
                #  | 
| 87 | 
            -
                #  | 
| 101 | 
            +
                # Clear inflection rules in a given scope.
         | 
| 102 | 
            +
                # If scope is not specified, all inflection rules will be cleared.
         | 
| 103 | 
            +
                # Passing :plurals, :singulars, or :uncountables will clear only that specific type of rule.
         | 
| 104 | 
            +
                #
         | 
| 105 | 
            +
                # @param scope [Symbol] The scope of rules to clear. Can be :all (default),
         | 
| 106 | 
            +
                #   :plurals, :singulars, or :uncountables
         | 
| 107 | 
            +
                # @return [Array] An empty array
         | 
| 108 | 
            +
                #
         | 
| 109 | 
            +
                # @example Clear all inflection rules
         | 
| 110 | 
            +
                #   String.inflections.clear
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # @example Clear only plural rules
         | 
| 113 | 
            +
                #   String.inflections.clear(:plurals)
         | 
| 88 114 | 
             
                #
         | 
| 89 | 
            -
                # Examples:
         | 
| 90 | 
            -
                #   clear :all
         | 
| 91 | 
            -
                #   clear :plurals
         | 
| 92 115 | 
             
                def self.clear(scope = :all)
         | 
| 93 116 | 
             
                  case scope
         | 
| 94 117 | 
             
                  when :all
         | 
| @@ -96,219 +119,370 @@ class String | |
| 96 119 | 
             
                    @singulars    = []
         | 
| 97 120 | 
             
                    @uncountables = []
         | 
| 98 121 | 
             
                  else
         | 
| 99 | 
            -
                    instance_variable_set("@#{scope}", [])
         | 
| 122 | 
            +
                    instance_variable_set(:"@#{scope}", [])
         | 
| 100 123 | 
             
                  end
         | 
| 101 124 | 
             
                end
         | 
| 102 125 |  | 
| 103 | 
            -
                # Specifies a new irregular that  | 
| 104 | 
            -
                #  | 
| 105 | 
            -
                #  | 
| 126 | 
            +
                # Specifies a new irregular inflection rule that transforms between singular and plural forms.
         | 
| 127 | 
            +
                # This method creates rules for both pluralization and singularization simultaneously.
         | 
| 128 | 
            +
                # Unlike regular inflection rules, this only works with literal strings, not regexp.
         | 
| 129 | 
            +
                #
         | 
| 130 | 
            +
                # @param singular [String] The singular form of the word
         | 
| 131 | 
            +
                # @param plural [String] The plural form of the word
         | 
| 132 | 
            +
                #
         | 
| 133 | 
            +
                # @example
         | 
| 134 | 
            +
                #   irregular('person', 'people')  # Creates rules to transform person <-> people
         | 
| 135 | 
            +
                #   irregular('child', 'children') # Creates rules to transform child <-> children
         | 
| 106 136 | 
             
                #
         | 
| 107 | 
            -
                # Examples:
         | 
| 108 | 
            -
                #   irregular 'octopus', 'octopi'
         | 
| 109 | 
            -
                #   irregular 'person', 'people'
         | 
| 110 137 | 
             
                def self.irregular(singular, plural)
         | 
| 111 | 
            -
                  plural(Regexp.new("(#{singular[0, 1]})#{singular[1 | 
| 112 | 
            -
                  singular(Regexp.new("(#{plural[0, 1]})#{plural[1 | 
| 138 | 
            +
                  plural(Regexp.new("(#{singular[0, 1]})#{singular[1..]}$", 'i'), "\\1#{plural[1..]}")
         | 
| 139 | 
            +
                  singular(Regexp.new("(#{plural[0, 1]})#{plural[1..]}$", 'i'), "\\1#{singular[1..]}")
         | 
| 113 140 | 
             
                end
         | 
| 114 141 |  | 
| 115 | 
            -
                # Specifies a new pluralization rule  | 
| 116 | 
            -
                #  | 
| 117 | 
            -
                # | 
| 142 | 
            +
                # Specifies a new pluralization rule to transform singular words into plural forms.
         | 
| 143 | 
            +
                # Adds the rule to the beginning of the rules array so it takes precedence over existing rules.
         | 
| 144 | 
            +
                #
         | 
| 145 | 
            +
                # @param rule [Regexp, String] Pattern to match words that should be pluralized
         | 
| 146 | 
            +
                # @param replacement [String] Template for constructing the plural form, can reference
         | 
| 147 | 
            +
                #   captured groups from the rule pattern using \1, \2 etc.
         | 
| 148 | 
            +
                # @return [Array] The updated array of plural rules
         | 
| 149 | 
            +
                #
         | 
| 150 | 
            +
                # @example Add rule to pluralize words ending in 'y'
         | 
| 151 | 
            +
                #   plural(/([^aeiou])y$/i, '\1ies') # changes 'fly' to 'flies'
         | 
| 118 152 | 
             
                #
         | 
| 119 | 
            -
                # Example:
         | 
| 120 | 
            -
                #   plural(/(x|ch|ss|sh)$/i, '\1es')
         | 
| 121 153 | 
             
                def self.plural(rule, replacement)
         | 
| 122 154 | 
             
                  @plurals.insert(0, [rule, replacement])
         | 
| 123 155 | 
             
                end
         | 
| 124 156 |  | 
| 125 | 
            -
                # Specifies a new singularization rule  | 
| 126 | 
            -
                #  | 
| 127 | 
            -
                # | 
| 157 | 
            +
                # Specifies a new singularization rule to transform plural words into singular forms.
         | 
| 158 | 
            +
                # Adds the rule to the beginning of the rules array so it takes precedence over existing rules.
         | 
| 159 | 
            +
                #
         | 
| 160 | 
            +
                # @param rule [Regexp, String] Pattern to match words that should be singularized
         | 
| 161 | 
            +
                # @param replacement [String] Template for constructing the singular form, can reference
         | 
| 162 | 
            +
                #   captured groups from the rule pattern using \1, \2 etc.
         | 
| 163 | 
            +
                #
         | 
| 164 | 
            +
                # @return [Array] The updated array of singular rules
         | 
| 165 | 
            +
                #
         | 
| 166 | 
            +
                # @example Add rule to singularize words ending in 'ies'
         | 
| 167 | 
            +
                #   singular(/([^aeiou])ies$/i, '\1y') # changes 'flies' to 'fly'
         | 
| 128 168 | 
             
                #
         | 
| 129 | 
            -
                # Example:
         | 
| 130 | 
            -
                #   singular(/([^aeiouy]|qu)ies$/i, '\1y') 
         | 
| 131 169 | 
             
                def self.singular(rule, replacement)
         | 
| 132 170 | 
             
                  @singulars.insert(0, [rule, replacement])
         | 
| 133 171 | 
             
                end
         | 
| 134 172 |  | 
| 135 | 
            -
                #  | 
| 173 | 
            +
                # Adds words that have the same singular and plural form to the uncountables list.
         | 
| 174 | 
            +
                # These words will be skipped by the inflector and returned unchanged.
         | 
| 175 | 
            +
                #
         | 
| 176 | 
            +
                # @param words [Array<String>] One or more words to mark as uncountable
         | 
| 177 | 
            +
                # @return [Array] The flattened array of all uncountable words
         | 
| 178 | 
            +
                #
         | 
| 179 | 
            +
                # @example Add a single uncountable word
         | 
| 180 | 
            +
                #   uncountable "fish"
         | 
| 181 | 
            +
                #
         | 
| 182 | 
            +
                # @example Add multiple uncountable words
         | 
| 183 | 
            +
                #   uncountable "rice", "equipment"
         | 
| 184 | 
            +
                #
         | 
| 185 | 
            +
                # @example Add an array of uncountable words
         | 
| 186 | 
            +
                #   uncountable %w(sheep species)
         | 
| 136 187 | 
             
                #
         | 
| 137 | 
            -
                # Examples:
         | 
| 138 | 
            -
                #   uncountable "money"
         | 
| 139 | 
            -
                #   uncountable "money", "information"
         | 
| 140 | 
            -
                #   uncountable %w( money information rice )
         | 
| 141 188 | 
             
                def self.uncountable(*words)
         | 
| 142 189 | 
             
                  (@uncountables << words).flatten!
         | 
| 143 190 | 
             
                end
         | 
| 144 191 |  | 
| 145 | 
            -
                #  | 
| 146 | 
            -
                #  | 
| 147 | 
            -
                #  | 
| 148 | 
            -
                 | 
| 192 | 
            +
                # Execute the default inflection rules defined in DEFAULT_INFLECTIONS_PROC
         | 
| 193 | 
            +
                # This sets up the basic plural/singular transformations and irregular/uncountable words
         | 
| 194 | 
            +
                # that the inflector will use by default
         | 
| 195 | 
            +
                instance_exec(&DEFAULT_INFLECTIONS_PROC)
         | 
| 149 196 | 
             
              end
         | 
| 150 197 |  | 
| 151 | 
            -
              #  | 
| 152 | 
            -
              # the Inflections module.
         | 
| 198 | 
            +
              # Provides access to the Inflections module for defining custom inflection rules.
         | 
| 199 | 
            +
              # If a block is given, yields the Inflections module to the block.
         | 
| 200 | 
            +
              # Always returns the Inflections module.
         | 
| 201 | 
            +
              #
         | 
| 202 | 
            +
              # @yield [Inflections] The Inflections module if a block is given
         | 
| 203 | 
            +
              #
         | 
| 204 | 
            +
              # @return [Inflections] The Inflections module
         | 
| 205 | 
            +
              #
         | 
| 206 | 
            +
              # @example Define custom inflection rules
         | 
| 207 | 
            +
              #   String.inflections do |inflect|
         | 
| 208 | 
            +
              #     inflect.plural /^(ox)$/i, '\1\2en'
         | 
| 209 | 
            +
              #     inflect.singular /^(ox)en/i, '\1'
         | 
| 210 | 
            +
              #   end
         | 
| 211 | 
            +
              #
         | 
| 153 212 | 
             
              def self.inflections
         | 
| 154 | 
            -
                yield Inflections if  | 
| 213 | 
            +
                yield Inflections if defined?(yield)
         | 
| 155 214 | 
             
                Inflections
         | 
| 156 215 | 
             
              end
         | 
| 157 216 |  | 
| 158 | 
            -
              #  | 
| 159 | 
            -
              #  | 
| 217 | 
            +
              # Converts the string to CamelCase format.
         | 
| 218 | 
            +
              # - Replaces forward slashes with double colons (e.g. 'foo/bar' -> 'Foo::Bar')
         | 
| 219 | 
            +
              # - Converts underscores to camelized format (e.g. 'foo_bar' -> 'FooBar')
         | 
| 160 220 | 
             
              #
         | 
| 161 | 
            -
              #  | 
| 221 | 
            +
              # @param first_letter_in_uppercase [Symbol] Whether first letter should be
         | 
| 222 | 
            +
              #    uppercase (:upper) or lowercase (:lower)
         | 
| 162 223 | 
             
              #
         | 
| 163 | 
            -
              #  | 
| 224 | 
            +
              # @return [String] The camelized string
         | 
| 225 | 
            +
              #
         | 
| 226 | 
            +
              # @example Convert to UpperCamelCase
         | 
| 164 227 | 
             
              #   "active_record".camelize #=> "ActiveRecord"
         | 
| 228 | 
            +
              #
         | 
| 229 | 
            +
              # @example Convert to lowerCamelCase
         | 
| 165 230 | 
             
              #   "active_record".camelize(:lower) #=> "activeRecord"
         | 
| 231 | 
            +
              #
         | 
| 232 | 
            +
              # @example Convert path to namespace
         | 
| 166 233 | 
             
              #   "active_record/errors".camelize #=> "ActiveRecord::Errors"
         | 
| 167 | 
            -
              # | 
| 234 | 
            +
              #
         | 
| 168 235 | 
             
              def camelize(first_letter_in_uppercase = :upper)
         | 
| 169 | 
            -
                s = gsub(%r{/(.?)}) | 
| 170 | 
            -
                    .gsub(/(^|_)(.)/) { |x| x[-1 | 
| 236 | 
            +
                s = gsub(%r{/(.?)}) { |x| "::#{x[-1..].upcase unless x == "/"}" }
         | 
| 237 | 
            +
                    .gsub(/(^|_)(.)/) { |x| x[-1..].upcase }
         | 
| 171 238 | 
             
                s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
         | 
| 172 239 | 
             
                s
         | 
| 173 240 | 
             
              end
         | 
| 174 | 
            -
               | 
| 241 | 
            +
              alias camelcase camelize
         | 
| 175 242 |  | 
| 176 | 
            -
              #  | 
| 177 | 
            -
              #  | 
| 243 | 
            +
              # Converts a string into a class name by removing any non-final period and subsequent characters,
         | 
| 244 | 
            +
              # converting to singular form, and camelizing.
         | 
| 245 | 
            +
              # Commonly used to obtain class name from table or file names.
         | 
| 246 | 
            +
              #
         | 
| 247 | 
            +
              # @return [String] A camelized singular form suitable for a class name
         | 
| 178 248 | 
             
              #
         | 
| 179 | 
            -
              #  | 
| 249 | 
            +
              # @example Convert database table name to class name
         | 
| 180 250 | 
             
              #   "egg_and_hams".classify #=> "EggAndHam"
         | 
| 181 | 
            -
              # | 
| 251 | 
            +
              #
         | 
| 252 | 
            +
              # @example Remove schema prefix
         | 
| 182 253 | 
             
              #   "schema.post".classify #=> "Post"
         | 
| 254 | 
            +
              #
         | 
| 255 | 
            +
              # @example Basic conversion
         | 
| 256 | 
            +
              #   "post".classify #=> "Post"
         | 
| 257 | 
            +
              #
         | 
| 183 258 | 
             
              def classify
         | 
| 184 259 | 
             
                sub(/.*\./, '').singularize.camelize
         | 
| 185 260 | 
             
              end
         | 
| 186 261 |  | 
| 187 | 
            -
              #  | 
| 188 | 
            -
              #  | 
| 189 | 
            -
              #  | 
| 262 | 
            +
              # Finds and returns a Ruby constant from a string name.
         | 
| 263 | 
            +
              # The string must be a valid constant name in CamelCase format.
         | 
| 264 | 
            +
              # Can handle namespaced constants using double colons (::).
         | 
| 265 | 
            +
              # Raises NameError if the constant name is invalid or not defined.
         | 
| 190 266 | 
             
              #
         | 
| 191 | 
            -
              #  | 
| 267 | 
            +
              # @return [Object] The Ruby constant corresponding to the string name
         | 
| 268 | 
            +
              #
         | 
| 269 | 
            +
              # @raise [NameError] If string is not a valid constant name or constant is not defined
         | 
| 270 | 
            +
              #
         | 
| 271 | 
            +
              # @example Get Module class
         | 
| 192 272 | 
             
              #   "Module".constantize #=> Module
         | 
| 193 | 
            -
              # | 
| 273 | 
            +
              #
         | 
| 274 | 
            +
              # @example Get namespaced constant
         | 
| 275 | 
            +
              #   "ActiveRecord::Base".constantize #=> ActiveRecord::Base
         | 
| 276 | 
            +
              #
         | 
| 277 | 
            +
              # @example Invalid constant name
         | 
| 278 | 
            +
              #   "invalid_name".constantize #=> NameError: invalid_name is not a valid constant name!
         | 
| 279 | 
            +
              #
         | 
| 194 280 | 
             
              def constantize
         | 
| 195 | 
            -
                unless m = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.match(self)
         | 
| 196 | 
            -
                  raise(NameError, "#{inspect} is not a valid constant name!") | 
| 281 | 
            +
                unless (m = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.match(self))
         | 
| 282 | 
            +
                  raise(NameError, "#{inspect} is not a valid constant name!")
         | 
| 197 283 | 
             
                end
         | 
| 198 | 
            -
             | 
| 284 | 
            +
             | 
| 285 | 
            +
                # rubcop:disable Style/DocumentDynamicEvalDefinition
         | 
| 286 | 
            +
                Object.module_eval("::#{m[1]}", __FILE__, __LINE__) # ::Post
         | 
| 287 | 
            +
                # rubcop:enable Style/DocumentDynamicEvalDefinition
         | 
| 199 288 | 
             
              end
         | 
| 200 289 |  | 
| 201 | 
            -
              # Replaces underscores  | 
| 290 | 
            +
              # Replaces underscores (_) in a string with dashes (-).
         | 
| 291 | 
            +
              # A helper method commonly used for URL slugs and CSS class names.
         | 
| 292 | 
            +
              #
         | 
| 293 | 
            +
              # @return [String] The string with underscores replaced by dashes
         | 
| 294 | 
            +
              #
         | 
| 295 | 
            +
              # @example
         | 
| 296 | 
            +
              #   "hello_world".dasherize #=> "hello-world"
         | 
| 297 | 
            +
              #   "foo_bar_baz".dasherize #=> "foo-bar-baz"
         | 
| 202 298 | 
             
              #
         | 
| 203 | 
            -
              # Example
         | 
| 204 | 
            -
              #   "puni_puni".dasherize #=> "puni-puni"
         | 
| 205 299 | 
             
              def dasherize
         | 
| 206 300 | 
             
                tr('_', '-')
         | 
| 207 301 | 
             
              end
         | 
| 208 302 |  | 
| 209 | 
            -
              # Removes the module part from  | 
| 303 | 
            +
              # Removes the module part from a fully-qualified Ruby constant name,
         | 
| 304 | 
            +
              # returning just the rightmost portion after the last double colon (::).
         | 
| 305 | 
            +
              #
         | 
| 306 | 
            +
              # @return [String] The final constant name without any module namespacing
         | 
| 307 | 
            +
              #
         | 
| 308 | 
            +
              # @example Remove module namespace from fully-qualified name
         | 
| 309 | 
            +
              #   "ActiveRecord::Base::Table".demodulize #=> "Table"
         | 
| 310 | 
            +
              #
         | 
| 311 | 
            +
              # @example No change when no modules present
         | 
| 312 | 
            +
              #   "String".demodulize #=> "String"
         | 
| 210 313 | 
             
              #
         | 
| 211 | 
            -
              # Examples
         | 
| 212 | 
            -
              #   "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
         | 
| 213 | 
            -
              #   "Inflections".demodulize #=> "Inflections"
         | 
| 214 314 | 
             
              def demodulize
         | 
| 215 315 | 
             
                gsub(/^.*::/, '')
         | 
| 216 316 | 
             
              end
         | 
| 217 317 |  | 
| 218 | 
            -
              # Creates a foreign key name from a class name | 
| 219 | 
            -
              #  | 
| 318 | 
            +
              # Creates a foreign key name from a class name by removing any module namespacing,
         | 
| 319 | 
            +
              # underscoring the remaining name, and appending 'id'. The underscore before 'id'
         | 
| 320 | 
            +
              # is optional.
         | 
| 321 | 
            +
              #
         | 
| 322 | 
            +
              # @param use_underscore [Boolean] Whether to include an underscore before 'id'
         | 
| 323 | 
            +
              #
         | 
| 324 | 
            +
              # @return [String] The foreign key name
         | 
| 220 325 | 
             
              #
         | 
| 221 | 
            -
              #  | 
| 326 | 
            +
              # @example Basic usage
         | 
| 222 327 | 
             
              #   "Message".foreign_key #=> "message_id"
         | 
| 223 | 
            -
              # | 
| 328 | 
            +
              #
         | 
| 329 | 
            +
              # @example Without underscore
         | 
| 330 | 
            +
              #   "Message".foreign_key(use_underscore: false) #=> "messageid"
         | 
| 331 | 
            +
              #
         | 
| 332 | 
            +
              # @example With namespaced class
         | 
| 224 333 | 
             
              #   "Admin::Post".foreign_key #=> "post_id"
         | 
| 225 | 
            -
               | 
| 226 | 
            -
             | 
| 334 | 
            +
              #
         | 
| 335 | 
            +
              def foreign_key(use_underscore: true)
         | 
| 336 | 
            +
                "#{demodulize.underscore}#{"_" if use_underscore}id"
         | 
| 227 337 | 
             
              end
         | 
| 228 338 |  | 
| 229 | 
            -
              #  | 
| 230 | 
            -
              #  | 
| 339 | 
            +
              # Converts a string into a more human-readable format by:
         | 
| 340 | 
            +
              # - Removing any trailing '_id'
         | 
| 341 | 
            +
              # - Converting underscores to spaces
         | 
| 342 | 
            +
              # - Capitalizing the first letter
         | 
| 343 | 
            +
              #
         | 
| 344 | 
            +
              # @return [String] A human-friendly version of the string
         | 
| 345 | 
            +
              #
         | 
| 346 | 
            +
              # @example Convert a database column name
         | 
| 347 | 
            +
              #   "employee_salary".humanize #=> "Employee salary"
         | 
| 348 | 
            +
              #
         | 
| 349 | 
            +
              # @example Remove ID suffix
         | 
| 350 | 
            +
              #   "user_id".humanize #=> "User"
         | 
| 351 | 
            +
              #
         | 
| 352 | 
            +
              # @example Basic conversion
         | 
| 353 | 
            +
              #   "hello_world".humanize #=> "Hello world"
         | 
| 231 354 | 
             
              #
         | 
| 232 | 
            -
              # Examples
         | 
| 233 | 
            -
              #   "employee_salary" #=> "Employee salary"
         | 
| 234 | 
            -
              #   "author_id" #=> "Author"
         | 
| 235 355 | 
             
              def humanize
         | 
| 236 356 | 
             
                gsub(/_id$/, '').tr('_', ' ').capitalize
         | 
| 237 357 | 
             
              end
         | 
| 238 358 |  | 
| 239 | 
            -
              #  | 
| 359 | 
            +
              # Transforms a word into its plural form according to standard English language rules
         | 
| 360 | 
            +
              # and any custom rules defined through String.inflections.
         | 
| 240 361 | 
             
              #
         | 
| 241 | 
            -
              #  | 
| 362 | 
            +
              # If the word is in the uncountable list (e.g. "sheep", "fish"), returns it unchanged.
         | 
| 363 | 
            +
              # Otherwise applies plural transformation rules in order until one matches.
         | 
| 364 | 
            +
              #
         | 
| 365 | 
            +
              # @return [String] The plural form of the word
         | 
| 366 | 
            +
              #
         | 
| 367 | 
            +
              # @example Basic pluralization
         | 
| 242 368 | 
             
              #   "post".pluralize #=> "posts"
         | 
| 243 369 | 
             
              #   "octopus".pluralize #=> "octopi"
         | 
| 244 | 
            -
              # | 
| 245 | 
            -
              # | 
| 370 | 
            +
              #
         | 
| 371 | 
            +
              # @example Uncountable words
         | 
| 372 | 
            +
              #   "fish".pluralize #=> "fish"
         | 
| 373 | 
            +
              #
         | 
| 374 | 
            +
              # @example Complex phrases
         | 
| 246 375 | 
             
              #   "the blue mailman".pluralize #=> "the blue mailmen"
         | 
| 247 376 | 
             
              #   "CamelOctopus".pluralize #=> "CamelOctopi"
         | 
| 377 | 
            +
              #
         | 
| 248 378 | 
             
              def pluralize
         | 
| 249 379 | 
             
                result = dup
         | 
| 250 380 | 
             
                unless Inflections.uncountables.include?(downcase)
         | 
| 251 | 
            -
                  Inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } | 
| 381 | 
            +
                  Inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
         | 
| 252 382 | 
             
                end
         | 
| 253 383 | 
             
                result
         | 
| 254 384 | 
             
              end
         | 
| 255 385 |  | 
| 256 | 
            -
              #  | 
| 386 | 
            +
              # Transforms a word into its singular form according to standard English language rules
         | 
| 387 | 
            +
              # and any custom rules defined through String.inflections.
         | 
| 257 388 | 
             
              #
         | 
| 258 | 
            -
              #  | 
| 389 | 
            +
              # If the word is in the uncountable list (e.g. "sheep", "fish"), returns it unchanged.
         | 
| 390 | 
            +
              # Otherwise applies singular transformation rules in order until one matches.
         | 
| 391 | 
            +
              #
         | 
| 392 | 
            +
              # @return [String] The singular form of the word
         | 
| 393 | 
            +
              #
         | 
| 394 | 
            +
              # @example Basic singularization
         | 
| 259 395 | 
             
              #   "posts".singularize #=> "post"
         | 
| 260 | 
            -
              #   " | 
| 261 | 
            -
              # | 
| 262 | 
            -
              # | 
| 396 | 
            +
              #   "matrices".singularize #=> "matrix"
         | 
| 397 | 
            +
              #
         | 
| 398 | 
            +
              # @example Uncountable words
         | 
| 399 | 
            +
              #   "fish".singularize #=> "fish"
         | 
| 400 | 
            +
              #
         | 
| 401 | 
            +
              # @example Complex phrases
         | 
| 263 402 | 
             
              #   "the blue mailmen".singularize #=> "the blue mailman"
         | 
| 264 403 | 
             
              #   "CamelOctopi".singularize #=> "CamelOctopus"
         | 
| 404 | 
            +
              #
         | 
| 265 405 | 
             
              def singularize
         | 
| 266 406 | 
             
                result = dup
         | 
| 267 407 | 
             
                unless Inflections.uncountables.include?(downcase)
         | 
| 268 | 
            -
                  Inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } | 
| 408 | 
            +
                  Inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
         | 
| 269 409 | 
             
                end
         | 
| 270 410 | 
             
                result
         | 
| 271 411 | 
             
              end
         | 
| 272 412 |  | 
| 273 | 
            -
              #  | 
| 413 | 
            +
              # Converts a class name or CamelCase word to a suitable database table name
         | 
| 414 | 
            +
              # by underscoring and pluralizing it. Namespaces are converted to paths.
         | 
| 415 | 
            +
              # Used to derive table names from model class names.
         | 
| 274 416 | 
             
              #
         | 
| 275 | 
            -
              #  | 
| 417 | 
            +
              # @return [String] The table name (underscored, pluralized form)
         | 
| 418 | 
            +
              #
         | 
| 419 | 
            +
              # @example Convert class name to table name
         | 
| 276 420 | 
             
              #   "RawScaledScorer".tableize #=> "raw_scaled_scorers"
         | 
| 277 | 
            -
              # | 
| 421 | 
            +
              #
         | 
| 422 | 
            +
              # @example Handle namespaces
         | 
| 423 | 
            +
              #   "Admin::Post".tableize #=> "admin/posts"
         | 
| 424 | 
            +
              #
         | 
| 425 | 
            +
              # @example Basic conversion
         | 
| 278 426 | 
             
              #   "fancyCategory".tableize #=> "fancy_categories"
         | 
| 427 | 
            +
              #
         | 
| 279 428 | 
             
              def tableize
         | 
| 280 429 | 
             
                underscore.pluralize
         | 
| 281 430 | 
             
              end
         | 
| 282 431 |  | 
| 283 | 
            -
              #  | 
| 284 | 
            -
              #  | 
| 432 | 
            +
              # Converts a string into a more human-readable title format by:
         | 
| 433 | 
            +
              # - Converting underscores and dashes to spaces
         | 
| 434 | 
            +
              # - Capitalizing each word
         | 
| 435 | 
            +
              # - Applying human-friendly formatting
         | 
| 285 436 | 
             
              #
         | 
| 286 437 | 
             
              # titleize is also aliased as as titlecase
         | 
| 287 438 | 
             
              #
         | 
| 288 | 
            -
              #  | 
| 289 | 
            -
              # | 
| 439 | 
            +
              # @return [String] A titleized version of the string
         | 
| 440 | 
            +
              #
         | 
| 441 | 
            +
              # @example Convert basic string to title
         | 
| 442 | 
            +
              #   "hello_world".titleize #=> "Hello World"
         | 
| 443 | 
            +
              #
         | 
| 444 | 
            +
              # @example Convert with special characters
         | 
| 290 445 | 
             
              #   "x-men: the last stand".titleize #=> "X Men: The Last Stand"
         | 
| 446 | 
            +
              #
         | 
| 447 | 
            +
              # @example Convert camelCase to title
         | 
| 448 | 
            +
              #   "camelCase".titleize #=> "Camel Case"
         | 
| 449 | 
            +
              #
         | 
| 291 450 | 
             
              def titleize
         | 
| 292 | 
            -
                underscore.humanize.gsub(/\b([a-z])/) { |x| x[-1 | 
| 451 | 
            +
                underscore.humanize.gsub(/\b([a-z])/) { |x| x[-1..].upcase }
         | 
| 293 452 | 
             
              end
         | 
| 294 | 
            -
               | 
| 453 | 
            +
              alias titlecase titleize
         | 
| 295 454 |  | 
| 296 | 
            -
              #  | 
| 297 | 
            -
              #  | 
| 455 | 
            +
              # Converts a CamelCase or camelCase string into an underscored format.
         | 
| 456 | 
            +
              # - Replaces '::' with '/' for namespace/path conversion
         | 
| 457 | 
            +
              # - Adds underscores between words including:
         | 
| 458 | 
            +
              #   - Between runs of capital letters: 'ABC' -> 'a_b_c'
         | 
| 459 | 
            +
              #   - Before first lowercase letter after capitals: 'HTMLParser' -> 'html_parser'
         | 
| 460 | 
            +
              #   - Before capitals after lowercase/numbers: 'fooBar' -> 'foo_bar'
         | 
| 461 | 
            +
              # - Converts all dashes to underscores
         | 
| 462 | 
            +
              # - Converts everything to lowercase
         | 
| 298 463 | 
             
              #
         | 
| 299 | 
            -
              #  | 
| 464 | 
            +
              # @return [String] The underscored version of the string
         | 
| 465 | 
            +
              #
         | 
| 466 | 
            +
              # @example Convert camelCase
         | 
| 467 | 
            +
              #   "camelCase".underscore #=> "camel_case"
         | 
| 300 468 | 
             
              #   "ActiveRecord".underscore #=> "active_record"
         | 
| 301 | 
            -
              # | 
| 469 | 
            +
              #
         | 
| 470 | 
            +
              # @example Convert namespace
         | 
| 471 | 
            +
              #   "ActiveRecord::Errors".underscore #=> 'active_record/errors'
         | 
| 472 | 
            +
              #
         | 
| 473 | 
            +
              # @example Convert complex CamelCase
         | 
| 474 | 
            +
              #   "HTMLParser".underscore #=> "html_parser"
         | 
| 475 | 
            +
              #
         | 
| 302 476 | 
             
              def underscore
         | 
| 303 | 
            -
                gsub( | 
| 304 | 
            -
             | 
| 477 | 
            +
                gsub('::', '/').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
         | 
| 478 | 
            +
                               .gsub(/([a-z\d])([A-Z])/, '\1_\2').tr('-', '_').downcase
         | 
| 305 479 | 
             
              end
         | 
| 306 480 | 
             
            end
         | 
| 307 481 |  | 
| 308 | 
            -
             | 
| 309 482 | 
             
            # Ripped from the Sequel gem by Jeremy Evans
         | 
| 310 | 
            -
            # 
         | 
| 311 | 
            -
            # | 
| 483 | 
            +
            # https://github.com/jeremyevans/sequel/blob/master/lib/sequel/extensions/inflector.rb
         | 
| 484 | 
            +
            #
         | 
| 485 | 
            +
            #
         | 
| 312 486 | 
             
            # Copyright (c) 2007-2008 Sharon Rosner
         | 
| 313 487 | 
             
            # Copyright (c) 2008-2015 Jeremy Evans
         | 
| 314 488 | 
             
            #
         |