cassandra_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README.rdoc +98 -0
  2. data/Rakefile.rb +11 -0
  3. data/lib/cassandra_mapper.rb +5 -0
  4. data/lib/cassandra_mapper/base.rb +19 -0
  5. data/lib/cassandra_mapper/connection.rb +9 -0
  6. data/lib/cassandra_mapper/core_ext/array/extract_options.rb +29 -0
  7. data/lib/cassandra_mapper/core_ext/array/wrap.rb +22 -0
  8. data/lib/cassandra_mapper/core_ext/class/inheritable_attributes.rb +232 -0
  9. data/lib/cassandra_mapper/core_ext/kernel/reporting.rb +62 -0
  10. data/lib/cassandra_mapper/core_ext/kernel/singleton_class.rb +13 -0
  11. data/lib/cassandra_mapper/core_ext/module/aliasing.rb +70 -0
  12. data/lib/cassandra_mapper/core_ext/module/attribute_accessors.rb +66 -0
  13. data/lib/cassandra_mapper/core_ext/object/duplicable.rb +65 -0
  14. data/lib/cassandra_mapper/core_ext/string/inflections.rb +160 -0
  15. data/lib/cassandra_mapper/core_ext/string/multibyte.rb +72 -0
  16. data/lib/cassandra_mapper/exceptions.rb +10 -0
  17. data/lib/cassandra_mapper/identity.rb +29 -0
  18. data/lib/cassandra_mapper/indexing.rb +465 -0
  19. data/lib/cassandra_mapper/observable.rb +36 -0
  20. data/lib/cassandra_mapper/persistence.rb +309 -0
  21. data/lib/cassandra_mapper/support/callbacks.rb +136 -0
  22. data/lib/cassandra_mapper/support/concern.rb +31 -0
  23. data/lib/cassandra_mapper/support/dependencies.rb +60 -0
  24. data/lib/cassandra_mapper/support/descendants_tracker.rb +41 -0
  25. data/lib/cassandra_mapper/support/inflections.rb +58 -0
  26. data/lib/cassandra_mapper/support/inflector.rb +7 -0
  27. data/lib/cassandra_mapper/support/inflector/inflections.rb +213 -0
  28. data/lib/cassandra_mapper/support/inflector/methods.rb +143 -0
  29. data/lib/cassandra_mapper/support/inflector/transliterate.rb +99 -0
  30. data/lib/cassandra_mapper/support/multibyte.rb +46 -0
  31. data/lib/cassandra_mapper/support/multibyte/utils.rb +62 -0
  32. data/lib/cassandra_mapper/support/observing.rb +218 -0
  33. data/lib/cassandra_mapper/support/support_callbacks.rb +593 -0
  34. data/test/test_helper.rb +11 -0
  35. data/test/unit/callbacks_test.rb +100 -0
  36. data/test/unit/identity_test.rb +51 -0
  37. data/test/unit/indexing_test.rb +406 -0
  38. data/test/unit/observer_test.rb +56 -0
  39. data/test/unit/persistence_test.rb +561 -0
  40. metadata +192 -0
@@ -0,0 +1,31 @@
1
+ module CassandraMapper
2
+ module Support
3
+ module Concern
4
+ def self.extended(base)
5
+ base.instance_variable_set("@_dependencies", [])
6
+ end
7
+
8
+ def append_features(base)
9
+ if base.instance_variable_defined?("@_dependencies")
10
+ base.instance_variable_get("@_dependencies") << self
11
+ return false
12
+ else
13
+ return false if base < self
14
+ @_dependencies.each { |dep| base.send(:include, dep) }
15
+ super
16
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
17
+ base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
18
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
19
+ end
20
+ end
21
+
22
+ def included(base = nil, &block)
23
+ if base.nil?
24
+ @_included_block = block
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ require 'cassandra_mapper/core_ext/module/attribute_accessors'
2
+ require 'cassandra_mapper/support/inflector'
3
+
4
+ module CassandraMapper #:nodoc:
5
+ module Support
6
+ module Dependencies #:nodoc:
7
+ extend self
8
+
9
+ # An array of qualified constant names that have been loaded. Adding a name to
10
+ # this array will cause it to be unloaded the next time Dependencies are cleared.
11
+ mattr_accessor :autoloaded_constants
12
+ self.autoloaded_constants = []
13
+
14
+ # Determine if the given constant has been automatically loaded.
15
+ def autoloaded?(desc)
16
+ # No name => anonymous module.
17
+ return false if desc.is_a?(Module) && desc.anonymous?
18
+ name = to_constant_name desc
19
+ return false unless qualified_const_defined? name
20
+ return autoloaded_constants.include?(name)
21
+ end
22
+
23
+ # Convert the provided const desc to a qualified constant name (as a string).
24
+ # A module, class, symbol, or string may be provided.
25
+ def to_constant_name(desc) #:nodoc:
26
+ name = case desc
27
+ when String then desc.sub(/^::/, '')
28
+ when Symbol then desc.to_s
29
+ when Module
30
+ desc.name.presence ||
31
+ raise(ArgumentError, "Anonymous modules have no name to be referenced by")
32
+ else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
33
+ end
34
+ end
35
+
36
+ # Is the provided constant path defined?
37
+ def qualified_const_defined?(path)
38
+ names = path.sub(/^::/, '').to_s.split('::')
39
+
40
+ names.inject(Object) do |mod, name|
41
+ return false unless local_const_defined?(mod, name)
42
+ mod.const_get name
43
+ end
44
+ end
45
+
46
+ if Module.method(:const_defined?).arity == 1
47
+ # Does this module define this constant?
48
+ # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9
49
+ def local_const_defined?(mod, const)
50
+ mod.const_defined?(const)
51
+ end
52
+ else
53
+ def local_const_defined?(mod, const) #:nodoc:
54
+ mod.const_defined?(const, false)
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,41 @@
1
+ require 'cassandra_mapper/support/dependencies'
2
+
3
+ module CassandraMapper
4
+ module Support
5
+ # This module provides an internal implementation to track descendants
6
+ # which is faster than iterating through ObjectSpace.
7
+ module DescendantsTracker
8
+ @@descendants = Hash.new { |h, k| h[k] = [] }
9
+
10
+ def self.descendants
11
+ @@descendants
12
+ end
13
+
14
+ def self.clear
15
+ @@descendants.each do |klass, descendants|
16
+ if CassandraMapper::Support::Dependencies.autoloaded?(klass)
17
+ @@descendants.delete(klass)
18
+ else
19
+ descendants.reject! { |v| CassandraMapper::Support::Dependencies.autoloaded?(v) }
20
+ end
21
+ end
22
+ end
23
+
24
+ def inherited(base)
25
+ self.direct_descendants << base
26
+ super
27
+ end
28
+
29
+ def direct_descendants
30
+ @@descendants[self]
31
+ end
32
+
33
+ def descendants
34
+ @@descendants[self].inject([]) do |descendants, klass|
35
+ descendants << klass
36
+ descendants.concat klass.descendants
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,58 @@
1
+ module CassandraMapper
2
+ module Support
3
+ Inflector.inflections do |inflect|
4
+ inflect.plural(/$/, 's')
5
+ inflect.plural(/s$/i, 's')
6
+ inflect.plural(/(ax|test)is$/i, '\1es')
7
+ inflect.plural(/(octop|vir)us$/i, '\1i')
8
+ inflect.plural(/(alias|status)$/i, '\1es')
9
+ inflect.plural(/(bu)s$/i, '\1ses')
10
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
11
+ inflect.plural(/([ti])um$/i, '\1a')
12
+ inflect.plural(/sis$/i, 'ses')
13
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
14
+ inflect.plural(/(hive)$/i, '\1s')
15
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
16
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
17
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
18
+ inflect.plural(/([m|l])ouse$/i, '\1ice')
19
+ inflect.plural(/^(ox)$/i, '\1en')
20
+ inflect.plural(/(quiz)$/i, '\1zes')
21
+
22
+ inflect.singular(/s$/i, '')
23
+ inflect.singular(/(n)ews$/i, '\1ews')
24
+ inflect.singular(/([ti])a$/i, '\1um')
25
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
26
+ inflect.singular(/(^analy)ses$/i, '\1sis')
27
+ inflect.singular(/([^f])ves$/i, '\1fe')
28
+ inflect.singular(/(hive)s$/i, '\1')
29
+ inflect.singular(/(tive)s$/i, '\1')
30
+ inflect.singular(/([lr])ves$/i, '\1f')
31
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
32
+ inflect.singular(/(s)eries$/i, '\1eries')
33
+ inflect.singular(/(m)ovies$/i, '\1ovie')
34
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
35
+ inflect.singular(/([m|l])ice$/i, '\1ouse')
36
+ inflect.singular(/(bus)es$/i, '\1')
37
+ inflect.singular(/(o)es$/i, '\1')
38
+ inflect.singular(/(shoe)s$/i, '\1')
39
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
40
+ inflect.singular(/(octop|vir)i$/i, '\1us')
41
+ inflect.singular(/(alias|status)es$/i, '\1')
42
+ inflect.singular(/^(ox)en/i, '\1')
43
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
44
+ inflect.singular(/(matr)ices$/i, '\1ix')
45
+ inflect.singular(/(quiz)zes$/i, '\1')
46
+ inflect.singular(/(database)s$/i, '\1')
47
+
48
+ inflect.irregular('person', 'people')
49
+ inflect.irregular('man', 'men')
50
+ inflect.irregular('child', 'children')
51
+ inflect.irregular('sex', 'sexes')
52
+ inflect.irregular('move', 'moves')
53
+ inflect.irregular('cow', 'kine')
54
+
55
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ # in case active_support/inflector is required without the rest of active_support
2
+ require 'cassandra_mapper/support/inflector/inflections'
3
+ require 'cassandra_mapper/support/inflector/transliterate'
4
+ require 'cassandra_mapper/support/inflector/methods'
5
+
6
+ require 'cassandra_mapper/support/inflections'
7
+ require 'cassandra_mapper/core_ext/string/inflections'
@@ -0,0 +1,213 @@
1
+ module CassandraMapper
2
+ module Support
3
+ module Inflector
4
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
5
+ # inflection rules. Examples:
6
+ #
7
+ # ActiveSupport::Inflector.inflections do |inflect|
8
+ # inflect.plural /^(ox)$/i, '\1\2en'
9
+ # inflect.singular /^(ox)en/i, '\1'
10
+ #
11
+ # inflect.irregular 'octopus', 'octopi'
12
+ #
13
+ # inflect.uncountable "equipment"
14
+ # end
15
+ #
16
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
17
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
18
+ # already have been loaded.
19
+ class Inflections
20
+ def self.instance
21
+ @__instance__ ||= new
22
+ end
23
+
24
+ attr_reader :plurals, :singulars, :uncountables, :humans
25
+
26
+ def initialize
27
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
28
+ end
29
+
30
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
31
+ # The replacement should always be a string that may include references to the matched data from the rule.
32
+ def plural(rule, replacement)
33
+ @uncountables.delete(rule) if rule.is_a?(String)
34
+ @uncountables.delete(replacement)
35
+ @plurals.insert(0, [rule, replacement])
36
+ end
37
+
38
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
39
+ # The replacement should always be a string that may include references to the matched data from the rule.
40
+ def singular(rule, replacement)
41
+ @uncountables.delete(rule) if rule.is_a?(String)
42
+ @uncountables.delete(replacement)
43
+ @singulars.insert(0, [rule, replacement])
44
+ end
45
+
46
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
47
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
48
+ #
49
+ # Examples:
50
+ # irregular 'octopus', 'octopi'
51
+ # irregular 'person', 'people'
52
+ def irregular(singular, plural)
53
+ @uncountables.delete(singular)
54
+ @uncountables.delete(plural)
55
+ if singular[0,1].upcase == plural[0,1].upcase
56
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
57
+ plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
58
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
59
+ else
60
+ plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
61
+ plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
62
+ plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
63
+ plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
64
+ singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
65
+ singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
66
+ end
67
+ end
68
+
69
+ # Add uncountable words that shouldn't be attempted inflected.
70
+ #
71
+ # Examples:
72
+ # uncountable "money"
73
+ # uncountable "money", "information"
74
+ # uncountable %w( money information rice )
75
+ def uncountable(*words)
76
+ (@uncountables << words).flatten!
77
+ end
78
+
79
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
80
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
81
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
82
+ #
83
+ # Examples:
84
+ # human /_cnt$/i, '\1_count'
85
+ # human "legacy_col_person_name", "Name"
86
+ def human(rule, replacement)
87
+ @humans.insert(0, [rule, replacement])
88
+ end
89
+
90
+ # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
91
+ # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
92
+ # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
93
+ #
94
+ # Examples:
95
+ # clear :all
96
+ # clear :plurals
97
+ def clear(scope = :all)
98
+ case scope
99
+ when :all
100
+ @plurals, @singulars, @uncountables = [], [], []
101
+ else
102
+ instance_variable_set "@#{scope}", []
103
+ end
104
+ end
105
+ end
106
+
107
+ # Yields a singleton instance of Inflector::Inflections so you can specify additional
108
+ # inflector rules.
109
+ #
110
+ # Example:
111
+ # ActiveSupport::Inflector.inflections do |inflect|
112
+ # inflect.uncountable "rails"
113
+ # end
114
+ def inflections
115
+ if block_given?
116
+ yield Inflections.instance
117
+ else
118
+ Inflections.instance
119
+ end
120
+ end
121
+
122
+ # Returns the plural form of the word in the string.
123
+ #
124
+ # Examples:
125
+ # "post".pluralize # => "posts"
126
+ # "octopus".pluralize # => "octopi"
127
+ # "sheep".pluralize # => "sheep"
128
+ # "words".pluralize # => "words"
129
+ # "CamelOctopus".pluralize # => "CamelOctopi"
130
+ def pluralize(word)
131
+ result = word.to_s.dup
132
+
133
+ if word.empty? || inflections.uncountables.include?(result.downcase)
134
+ result
135
+ else
136
+ inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
137
+ result
138
+ end
139
+ end
140
+
141
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
142
+ #
143
+ # Examples:
144
+ # "posts".singularize # => "post"
145
+ # "octopi".singularize # => "octopus"
146
+ # "sheep".singularize # => "sheep"
147
+ # "word".singularize # => "word"
148
+ # "CamelOctopi".singularize # => "CamelOctopus"
149
+ def singularize(word)
150
+ result = word.to_s.dup
151
+
152
+ if inflections.uncountables.any? { |inflection| result =~ /#{inflection}\Z/i }
153
+ result
154
+ else
155
+ inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
156
+ result
157
+ end
158
+ end
159
+
160
+ # Capitalizes the first word and turns underscores into spaces and strips a
161
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
162
+ #
163
+ # Examples:
164
+ # "employee_salary" # => "Employee salary"
165
+ # "author_id" # => "Author"
166
+ def humanize(lower_case_and_underscored_word)
167
+ result = lower_case_and_underscored_word.to_s.dup
168
+
169
+ inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
170
+ result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
171
+ end
172
+
173
+ # Capitalizes all the words and replaces some characters in the string to create
174
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
175
+ # used in the Rails internals.
176
+ #
177
+ # +titleize+ is also aliased as as +titlecase+.
178
+ #
179
+ # Examples:
180
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
181
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
182
+ def titleize(word)
183
+ humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
184
+ end
185
+
186
+ # Create the name of a table like Rails does for models to table names. This method
187
+ # uses the +pluralize+ method on the last word in the string.
188
+ #
189
+ # Examples
190
+ # "RawScaledScorer".tableize # => "raw_scaled_scorers"
191
+ # "egg_and_ham".tableize # => "egg_and_hams"
192
+ # "fancyCategory".tableize # => "fancy_categories"
193
+ def tableize(class_name)
194
+ pluralize(underscore(class_name))
195
+ end
196
+
197
+ # Create a class name from a plural table name like Rails does for table names to models.
198
+ # Note that this returns a string and not a Class. (To convert to an actual class
199
+ # follow +classify+ with +constantize+.)
200
+ #
201
+ # Examples:
202
+ # "egg_and_hams".classify # => "EggAndHam"
203
+ # "posts".classify # => "Post"
204
+ #
205
+ # Singular names are not handled correctly:
206
+ # "business".classify # => "Busines"
207
+ def classify(table_name)
208
+ # strip out any leading schema name
209
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,143 @@
1
+ module CassandraMapper
2
+ module Support
3
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
4
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
5
+ # in inflections.rb.
6
+ #
7
+ # The Rails core team has stated patches for the inflections library will not be accepted
8
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
9
+ # If you discover an incorrect inflection and require it for your application, you'll need
10
+ # to correct it yourself (explained below).
11
+ module Inflector
12
+ extend self
13
+
14
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
15
+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
16
+ #
17
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
18
+ #
19
+ # Examples:
20
+ # "active_record".camelize # => "ActiveRecord"
21
+ # "active_record".camelize(:lower) # => "activeRecord"
22
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
23
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
24
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
25
+ if first_letter_in_uppercase
26
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
27
+ else
28
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
29
+ end
30
+ end
31
+
32
+ # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
33
+ #
34
+ # Changes '::' to '/' to convert namespaces to paths.
35
+ #
36
+ # Examples:
37
+ # "ActiveRecord".underscore # => "active_record"
38
+ # "ActiveRecord::Errors".underscore # => active_record/errors
39
+ def underscore(camel_cased_word)
40
+ word = camel_cased_word.to_s.dup
41
+ word.gsub!(/::/, '/')
42
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
43
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
44
+ word.tr!("-", "_")
45
+ word.downcase!
46
+ word
47
+ end
48
+
49
+ # Replaces underscores with dashes in the string.
50
+ #
51
+ # Example:
52
+ # "puni_puni" # => "puni-puni"
53
+ def dasherize(underscored_word)
54
+ underscored_word.gsub(/_/, '-')
55
+ end
56
+
57
+ # Removes the module part from the expression in the string.
58
+ #
59
+ # Examples:
60
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
61
+ # "Inflections".demodulize # => "Inflections"
62
+ def demodulize(class_name_in_module)
63
+ class_name_in_module.to_s.gsub(/^.*::/, '')
64
+ end
65
+
66
+ # Creates a foreign key name from a class name.
67
+ # +separate_class_name_and_id_with_underscore+ sets whether
68
+ # the method should put '_' between the name and 'id'.
69
+ #
70
+ # Examples:
71
+ # "Message".foreign_key # => "message_id"
72
+ # "Message".foreign_key(false) # => "messageid"
73
+ # "Admin::Post".foreign_key # => "post_id"
74
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
75
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
76
+ end
77
+
78
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
79
+ # #const_defined? and changes their default behavior.
80
+ if Module.method(:const_get).arity == 1
81
+ # Tries to find a constant with the name specified in the argument string:
82
+ #
83
+ # "Module".constantize # => Module
84
+ # "Test::Unit".constantize # => Test::Unit
85
+ #
86
+ # The name is assumed to be the one of a top-level constant, no matter whether
87
+ # it starts with "::" or not. No lexical context is taken into account:
88
+ #
89
+ # C = 'outside'
90
+ # module M
91
+ # C = 'inside'
92
+ # C # => 'inside'
93
+ # "C".constantize # => 'outside', same as ::C
94
+ # end
95
+ #
96
+ # NameError is raised when the name is not in CamelCase or the constant is
97
+ # unknown.
98
+ def constantize(camel_cased_word)
99
+ names = camel_cased_word.split('::')
100
+ names.shift if names.empty? || names.first.empty?
101
+
102
+ constant = Object
103
+ names.each do |name|
104
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
105
+ end
106
+ constant
107
+ end
108
+ else
109
+ def constantize(camel_cased_word) #:nodoc:
110
+ names = camel_cased_word.split('::')
111
+ names.shift if names.empty? || names.first.empty?
112
+
113
+ constant = Object
114
+ names.each do |name|
115
+ constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
116
+ end
117
+ constant
118
+ end
119
+ end
120
+
121
+ # Turns a number into an ordinal string used to denote the position in an
122
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
123
+ #
124
+ # Examples:
125
+ # ordinalize(1) # => "1st"
126
+ # ordinalize(2) # => "2nd"
127
+ # ordinalize(1002) # => "1002nd"
128
+ # ordinalize(1003) # => "1003rd"
129
+ def ordinalize(number)
130
+ if (11..13).include?(number.to_i % 100)
131
+ "#{number}th"
132
+ else
133
+ case number.to_i % 10
134
+ when 1; "#{number}st"
135
+ when 2; "#{number}nd"
136
+ when 3; "#{number}rd"
137
+ else "#{number}th"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end