conject 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +32 -0
  5. data/NOTES.txt +61 -0
  6. data/README.md +8 -0
  7. data/Rakefile +12 -0
  8. data/TODO +9 -0
  9. data/conject.gemspec +21 -0
  10. data/lib/conject.rb +40 -0
  11. data/lib/conject/borrowed_active_support_inflector.rb +525 -0
  12. data/lib/conject/class_ext_construct_with.rb +123 -0
  13. data/lib/conject/class_finder.rb +11 -0
  14. data/lib/conject/composition_error.rb +33 -0
  15. data/lib/conject/dependency_resolver.rb +16 -0
  16. data/lib/conject/extended_metaid.rb +33 -0
  17. data/lib/conject/object_context.rb +61 -0
  18. data/lib/conject/object_definition.rb +10 -0
  19. data/lib/conject/object_factory.rb +28 -0
  20. data/lib/conject/utilities.rb +8 -0
  21. data/lib/conject/version.rb +3 -0
  22. data/rake_tasks/rspec.rake +25 -0
  23. data/spec/acceptance/dev/README +7 -0
  24. data/spec/acceptance/regression/README +12 -0
  25. data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
  26. data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
  27. data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
  28. data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
  29. data/spec/conject/class_ext_construct_with_spec.rb +226 -0
  30. data/spec/conject/class_finder_spec.rb +36 -0
  31. data/spec/conject/composition_error_spec.rb +124 -0
  32. data/spec/conject/dependency_resolver_spec.rb +32 -0
  33. data/spec/conject/extended_metaid_spec.rb +90 -0
  34. data/spec/conject/object_context_spec.rb +186 -0
  35. data/spec/conject/object_definition_spec.rb +31 -0
  36. data/spec/conject/object_factory_spec.rb +89 -0
  37. data/spec/conject/utilities_spec.rb +30 -0
  38. data/spec/spec_helper.rb +24 -0
  39. data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
  40. data/spec/support/load_path_helpers.rb +27 -0
  41. data/spec/test_data/basic_composition/fence.rb +5 -0
  42. data/spec/test_data/basic_composition/front_desk.rb +2 -0
  43. data/spec/test_data/basic_composition/grass.rb +2 -0
  44. data/spec/test_data/basic_composition/guest.rb +5 -0
  45. data/spec/test_data/basic_composition/lobby.rb +5 -0
  46. data/spec/test_data/basic_composition/nails.rb +2 -0
  47. data/spec/test_data/basic_composition/tv.rb +2 -0
  48. data/spec/test_data/basic_composition/wood.rb +2 -0
  49. data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
  50. data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
  51. data/spike/depends_on_spike.rb +146 -0
  52. data/spike/donkey_fail.rb +48 -0
  53. data/spike/donkey_journey.rb +50 -0
  54. data/spike/go.rb +11 -0
  55. data/spike/metaid.rb +28 -0
  56. data/spike/object_definition.rb +125 -0
  57. data/spike/sample.rb +125 -0
  58. data/src/user_model.rb +4 -0
  59. data/src/user_presenter.rb +10 -0
  60. data/src/user_view.rb +3 -0
  61. metadata +165 -0
@@ -0,0 +1,4 @@
1
+ /coverage
2
+ tags
3
+ *.swp
4
+ /README.html
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm 1.9.3@object_context
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in conject.gemspec
4
+ gemspec
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ conject (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ multi_json (1.0.4)
11
+ rake (0.9.2.2)
12
+ rspec (2.8.0)
13
+ rspec-core (~> 2.8.0)
14
+ rspec-expectations (~> 2.8.0)
15
+ rspec-mocks (~> 2.8.0)
16
+ rspec-core (2.8.0)
17
+ rspec-expectations (2.8.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.8.0)
20
+ simplecov (0.5.4)
21
+ multi_json (~> 1.0.3)
22
+ simplecov-html (~> 0.5.3)
23
+ simplecov-html (0.5.3)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ conject!
30
+ rake
31
+ rspec
32
+ simplecov
@@ -0,0 +1,61 @@
1
+
2
+ # Jan 2012
3
+
4
+ # Requesting an object from a Context
5
+ # if object exists in current context
6
+ # return it
7
+ # else
8
+ # if a super context has the object
9
+ # use the object from the super context
10
+ # else
11
+ # create the object
12
+ # store in this Context as a singleton
13
+ # return the object
14
+ #
15
+ # Special case: an object being created within a subcontext requires a
16
+ # component that SHOULD be defined in the supercontext
17
+ # BUT has not yet been created.
18
+ #
19
+ # Support a request-time option :from_super => [ :obj1, :obj2]
20
+ #
21
+ #
22
+ # Special case: an object being created within a subcontext requires a
23
+ # component that SHOULD be defined in that SAME subcontext,
24
+ # but whose name overlaps with an object already defined in
25
+ # the SUPER context.
26
+ #
27
+ # Suppoer a request-time option :define_own => [ :obj1, :obj2]
28
+ # (:hide_in_super ? name debatable)
29
+ #
30
+ #
31
+ #
32
+
33
+ # Dec 2011
34
+
35
+ # Term: REGULAR OBJECT
36
+ # Instance of some class with 0 or more components
37
+
38
+ #
39
+ # Object definitions
40
+ # Manually add to context by name
41
+ # Indirectly added to context by meta programming in concerned class
42
+ # Generated defaults
43
+ #
44
+ # If no def exists
45
+ # if require_on_demand is true
46
+ # require guessed library name
47
+ # if def still not exist
48
+ # generate default def
49
+ #
50
+ # Use def to construct object
51
+ #
52
+ # ? when a class uses meta programming to define aspects of itself
53
+ # we are not yet in a context for certain.
54
+ # We could assume global context but that isn't always right.
55
+ #
56
+ # REAL QUESTION:
57
+ # How do we define non-global contexts conveniently?
58
+ # :w
59
+ #
60
+
61
+
@@ -0,0 +1,8 @@
1
+ # Object Context #
2
+
3
+ Retrieve and relate objects within contexts. Provides dependency injection convenience inspired by the simplicity of Google's Guice.
4
+
5
+ # VERY ALPHA #
6
+
7
+
8
+
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ HERE = File.dirname(__FILE__)
4
+
5
+ Dir[File.expand_path(HERE) + "/rake_tasks/*.rake"].each do |rake_file|
6
+ import rake_file
7
+ end
8
+
9
+ # desc 'Default: run specs and cucumber features'
10
+ # task :default => [ "spec", "cuc:features" ]
11
+
12
+ task :default => [ "spec" ]
data/TODO ADDED
@@ -0,0 +1,9 @@
1
+ Sketch out some more use cases
2
+
3
+ Move / copy test data (loadable classes) into spec/acceptance/testdata
4
+ Make helpers for accessing that folder
5
+ Make helper to set load path for a specific test
6
+
7
+ Move basic object creation spec to regression
8
+
9
+
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/conject/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Crosby"]
6
+ gem.email = ["david.crosby@atomicobject.com"]
7
+ gem.description = %q{Enable Guice-like dependency injection and contextual object interactions.}
8
+ gem.summary = %q{Enable Guice-like dependency injection and contextual object interactions.}
9
+ gem.homepage = "https://github.com/dcrosby42/conject"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "conject"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Conject::VERSION
17
+
18
+ gem.add_development_dependency "rake"
19
+ gem.add_development_dependency "rspec"
20
+ gem.add_development_dependency "simplecov"
21
+ end
@@ -0,0 +1,40 @@
1
+ require "conject/version"
2
+
3
+ module Conject
4
+ #
5
+ # Provide access to the default ObjectContext.
6
+ # This context is created on first use, and can
7
+ # serve as the root of all other ObjectContexts.
8
+ #
9
+ def self.default_object_context
10
+ @default_object_context ||= create_object_context(nil)
11
+ end
12
+
13
+ def self.default_object_factory
14
+ @default_object_factory ||= Conject::ObjectFactory.new(
15
+ :class_finder => Conject::ClassFinder.new,
16
+ :dependency_resolver => Conject::DependencyResolver.new
17
+ )
18
+ end
19
+
20
+ def self.create_object_context(parent_context, object_factory=nil)
21
+ object_factory ||= default_object_factory
22
+ Conject::ObjectContext.new(
23
+ :parent_context => parent_context,
24
+ :object_factory => object_factory
25
+ )
26
+ end
27
+ end
28
+
29
+ # The rest of the libraries namespace themselves under Conject so
30
+ # they must be required AFTER the initial definition of Conject.
31
+ require 'conject/object_definition'
32
+ require 'conject/extended_metaid'
33
+ require 'conject/class_ext_construct_with'
34
+ require 'conject/object_context'
35
+ require 'conject/object_factory'
36
+ require 'conject/class_finder'
37
+ require 'conject/dependency_resolver'
38
+ require 'conject/utilities'
39
+ require 'conject/composition_error'
40
+ require 'conject/borrowed_active_support_inflector'
@@ -0,0 +1,525 @@
1
+ # in case active_support/inflector is required without the rest of active_support
2
+ module BorrowedActiveSupport
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
+ # BorrowedActiveSupport::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, @humans = [], [], [], []
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
+ # BorrowedActiveSupport::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 =~ /\b(#{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
+
213
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
214
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
215
+ # in inflections.rb.
216
+ #
217
+ # The Rails core team has stated patches for the inflections library will not be accepted
218
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
219
+ # If you discover an incorrect inflection and require it for your application, you'll need
220
+ # to correct it yourself (explained below).
221
+ module Inflector
222
+ extend self
223
+
224
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
225
+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
226
+ #
227
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
228
+ #
229
+ # Examples:
230
+ # "active_record".camelize # => "ActiveRecord"
231
+ # "active_record".camelize(:lower) # => "activeRecord"
232
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
233
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
234
+ #
235
+ # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
236
+ # though there are cases where that does not hold:
237
+ #
238
+ # "SSLError".underscore.camelize # => "SslError"
239
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
240
+ if first_letter_in_uppercase
241
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
242
+ else
243
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
244
+ end
245
+ end
246
+
247
+ # Makes an underscored, lowercase form from the expression in the string.
248
+ #
249
+ # Changes '::' to '/' to convert namespaces to paths.
250
+ #
251
+ # Examples:
252
+ # "ActiveRecord".underscore # => "active_record"
253
+ # "ActiveRecord::Errors".underscore # => active_record/errors
254
+ #
255
+ # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
256
+ # though there are cases where that does not hold:
257
+ #
258
+ # "SSLError".underscore.camelize # => "SslError"
259
+ def underscore(camel_cased_word)
260
+ word = camel_cased_word.to_s.dup
261
+ word.gsub!(/::/, '/')
262
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
263
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
264
+ word.tr!("-", "_")
265
+ word.downcase!
266
+ word
267
+ end
268
+
269
+ # Replaces underscores with dashes in the string.
270
+ #
271
+ # Example:
272
+ # "puni_puni" # => "puni-puni"
273
+ def dasherize(underscored_word)
274
+ underscored_word.gsub(/_/, '-')
275
+ end
276
+
277
+ # Removes the module part from the expression in the string.
278
+ #
279
+ # Examples:
280
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
281
+ # "Inflections".demodulize # => "Inflections"
282
+ def demodulize(class_name_in_module)
283
+ class_name_in_module.to_s.gsub(/^.*::/, '')
284
+ end
285
+
286
+ # Creates a foreign key name from a class name.
287
+ # +separate_class_name_and_id_with_underscore+ sets whether
288
+ # the method should put '_' between the name and 'id'.
289
+ #
290
+ # Examples:
291
+ # "Message".foreign_key # => "message_id"
292
+ # "Message".foreign_key(false) # => "messageid"
293
+ # "Admin::Post".foreign_key # => "post_id"
294
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
295
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
296
+ end
297
+
298
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
299
+ # #const_defined? and changes their default behavior.
300
+ if Module.method(:const_get).arity == 1
301
+ # Tries to find a constant with the name specified in the argument string:
302
+ #
303
+ # "Module".constantize # => Module
304
+ # "Test::Unit".constantize # => Test::Unit
305
+ #
306
+ # The name is assumed to be the one of a top-level constant, no matter whether
307
+ # it starts with "::" or not. No lexical context is taken into account:
308
+ #
309
+ # C = 'outside'
310
+ # module M
311
+ # C = 'inside'
312
+ # C # => 'inside'
313
+ # "C".constantize # => 'outside', same as ::C
314
+ # end
315
+ #
316
+ # NameError is raised when the name is not in CamelCase or the constant is
317
+ # unknown.
318
+ def constantize(camel_cased_word)
319
+ names = camel_cased_word.split('::')
320
+ names.shift if names.empty? || names.first.empty?
321
+
322
+ constant = Object
323
+ names.each do |name|
324
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
325
+ end
326
+ constant
327
+ end
328
+ else
329
+ def constantize(camel_cased_word) #:nodoc:
330
+ names = camel_cased_word.split('::')
331
+ names.shift if names.empty? || names.first.empty?
332
+
333
+ constant = Object
334
+ names.each do |name|
335
+ constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
336
+ end
337
+ constant
338
+ end
339
+ end
340
+
341
+ # Turns a number into an ordinal string used to denote the position in an
342
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
343
+ #
344
+ # Examples:
345
+ # ordinalize(1) # => "1st"
346
+ # ordinalize(2) # => "2nd"
347
+ # ordinalize(1002) # => "1002nd"
348
+ # ordinalize(1003) # => "1003rd"
349
+ # ordinalize(-11) # => "-11th"
350
+ # ordinalize(-1021) # => "-1021st"
351
+ def ordinalize(number)
352
+ if (11..13).include?(number.to_i.abs % 100)
353
+ "#{number}th"
354
+ else
355
+ case number.to_i.abs % 10
356
+ when 1; "#{number}st"
357
+ when 2; "#{number}nd"
358
+ when 3; "#{number}rd"
359
+ else "#{number}th"
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ # String inflections define new methods on the String class to transform names for different purposes.
367
+ # For instance, you can figure out the name of a table from the name of a class.
368
+ #
369
+ # "ScaleScore".tableize # => "scale_scores"
370
+ #
371
+ class String
372
+ # Returns the plural form of the word in the string.
373
+ #
374
+ # "post".pluralize # => "posts"
375
+ # "octopus".pluralize # => "octopi"
376
+ # "sheep".pluralize # => "sheep"
377
+ # "words".pluralize # => "words"
378
+ # "the blue mailman".pluralize # => "the blue mailmen"
379
+ # "CamelOctopus".pluralize # => "CamelOctopi"
380
+ def pluralize
381
+ BorrowedActiveSupport::Inflector.pluralize(self)
382
+ end
383
+
384
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
385
+ #
386
+ # "posts".singularize # => "post"
387
+ # "octopi".singularize # => "octopus"
388
+ # "sheep".singularize # => "sheep"
389
+ # "word".singularize # => "word"
390
+ # "the blue mailmen".singularize # => "the blue mailman"
391
+ # "CamelOctopi".singularize # => "CamelOctopus"
392
+ def singularize
393
+ BorrowedActiveSupport::Inflector.singularize(self)
394
+ end
395
+
396
+ # +constantize+ tries to find a declared constant with the name specified
397
+ # in the string. It raises a NameError when the name is not in CamelCase
398
+ # or is not initialized.
399
+ #
400
+ # Examples
401
+ # "Module".constantize # => Module
402
+ # "Class".constantize # => Class
403
+ def constantize
404
+ BorrowedActiveSupport::Inflector.constantize(self)
405
+ end
406
+
407
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
408
+ # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
409
+ #
410
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
411
+ #
412
+ # "active_record".camelize # => "ActiveRecord"
413
+ # "active_record".camelize(:lower) # => "activeRecord"
414
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
415
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
416
+ def camelize(first_letter = :upper)
417
+ case first_letter
418
+ when :upper then BorrowedActiveSupport::Inflector.camelize(self, true)
419
+ when :lower then BorrowedActiveSupport::Inflector.camelize(self, false)
420
+ end
421
+ end
422
+ alias_method :camelcase, :camelize
423
+
424
+ # Capitalizes all the words and replaces some characters in the string to create
425
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
426
+ # used in the Rails internals.
427
+ #
428
+ # +titleize+ is also aliased as +titlecase+.
429
+ #
430
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
431
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
432
+ def titleize
433
+ BorrowedActiveSupport::Inflector.titleize(self)
434
+ end
435
+ alias_method :titlecase, :titleize
436
+
437
+ # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
438
+ #
439
+ # +underscore+ will also change '::' to '/' to convert namespaces to paths.
440
+ #
441
+ # "ActiveRecord".underscore # => "active_record"
442
+ # "ActiveRecord::Errors".underscore # => active_record/errors
443
+ def underscore
444
+ BorrowedActiveSupport::Inflector.underscore(self)
445
+ end
446
+
447
+ # Replaces underscores with dashes in the string.
448
+ #
449
+ # "puni_puni" # => "puni-puni"
450
+ def dasherize
451
+ BorrowedActiveSupport::Inflector.dasherize(self)
452
+ end
453
+
454
+ # Removes the module part from the constant expression in the string.
455
+ #
456
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
457
+ # "Inflections".demodulize # => "Inflections"
458
+ def demodulize
459
+ BorrowedActiveSupport::Inflector.demodulize(self)
460
+ end
461
+
462
+ # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
463
+ #
464
+ # ==== Examples
465
+ #
466
+ # class Person
467
+ # def to_param
468
+ # "#{id}-#{name.parameterize}"
469
+ # end
470
+ # end
471
+ #
472
+ # @person = Person.find(1)
473
+ # # => #<Person id: 1, name: "Donald E. Knuth">
474
+ #
475
+ # <%= link_to(@person.name, person_path %>
476
+ # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
477
+ def parameterize(sep = '-')
478
+ BorrowedActiveSupport::Inflector.parameterize(self, sep)
479
+ end
480
+
481
+ # Creates the name of a table like Rails does for models to table names. This method
482
+ # uses the +pluralize+ method on the last word in the string.
483
+ #
484
+ # "RawScaledScorer".tableize # => "raw_scaled_scorers"
485
+ # "egg_and_ham".tableize # => "egg_and_hams"
486
+ # "fancyCategory".tableize # => "fancy_categories"
487
+ def tableize
488
+ BorrowedActiveSupport::Inflector.tableize(self)
489
+ end
490
+
491
+ # Create a class name from a plural table name like Rails does for table names to models.
492
+ # Note that this returns a string and not a class. (To convert to an actual class
493
+ # follow +classify+ with +constantize+.)
494
+ #
495
+ # "egg_and_hams".classify # => "EggAndHam"
496
+ # "posts".classify # => "Post"
497
+ #
498
+ # Singular names are not handled correctly.
499
+ #
500
+ # "business".classify # => "Busines"
501
+ def classify
502
+ BorrowedActiveSupport::Inflector.classify(self)
503
+ end
504
+
505
+ # Capitalizes the first word, turns underscores into spaces, and strips '_id'.
506
+ # Like +titleize+, this is meant for creating pretty output.
507
+ #
508
+ # "employee_salary" # => "Employee salary"
509
+ # "author_id" # => "Author"
510
+ def humanize
511
+ BorrowedActiveSupport::Inflector.humanize(self)
512
+ end
513
+
514
+ # Creates a foreign key name from a class name.
515
+ # +separate_class_name_and_id_with_underscore+ sets whether
516
+ # the method should put '_' between the name and 'id'.
517
+ #
518
+ # Examples
519
+ # "Message".foreign_key # => "message_id"
520
+ # "Message".foreign_key(false) # => "messageid"
521
+ # "Admin::Post".foreign_key # => "post_id"
522
+ def foreign_key(separate_class_name_and_id_with_underscore = true)
523
+ BorrowedActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
524
+ end
525
+ end