cassandra_mapper 0.0.1

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.
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