activesupport 4.2.0 → 5.0.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +630 -220
- data/MIT-LICENSE +2 -2
- data/README.rdoc +2 -3
- data/lib/active_support/array_inquirer.rb +44 -0
- data/lib/active_support/backtrace_cleaner.rb +1 -1
- data/lib/active_support/benchmarkable.rb +1 -1
- data/lib/active_support/cache/file_store.rb +36 -22
- data/lib/active_support/cache/mem_cache_store.rb +63 -54
- data/lib/active_support/cache/memory_store.rb +16 -21
- data/lib/active_support/cache/null_store.rb +1 -4
- data/lib/active_support/cache/strategy/local_cache.rb +31 -20
- data/lib/active_support/cache.rb +73 -89
- data/lib/active_support/callbacks.rb +195 -155
- data/lib/active_support/concern.rb +2 -2
- data/lib/active_support/concurrency/latch.rb +7 -15
- data/lib/active_support/concurrency/share_lock.rb +186 -0
- data/lib/active_support/configurable.rb +1 -0
- data/lib/active_support/core_ext/array/access.rb +27 -1
- data/lib/active_support/core_ext/array/conversions.rb +6 -4
- data/lib/active_support/core_ext/array/grouping.rb +9 -18
- data/lib/active_support/core_ext/array/inquiry.rb +17 -0
- data/lib/active_support/core_ext/array/wrap.rb +5 -4
- data/lib/active_support/core_ext/array.rb +1 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -10
- data/lib/active_support/core_ext/class/attribute.rb +10 -9
- data/lib/active_support/core_ext/class/subclasses.rb +3 -4
- data/lib/active_support/core_ext/class.rb +0 -1
- data/lib/active_support/core_ext/date/blank.rb +12 -0
- data/lib/active_support/core_ext/date/calculations.rb +1 -1
- data/lib/active_support/core_ext/date/conversions.rb +13 -6
- data/lib/active_support/core_ext/date.rb +1 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +109 -25
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +3 -4
- data/lib/active_support/core_ext/date_time/blank.rb +12 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +36 -10
- data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
- data/lib/active_support/core_ext/date_time.rb +2 -1
- data/lib/active_support/core_ext/enumerable.rb +49 -5
- data/lib/active_support/core_ext/file/atomic.rb +30 -25
- data/lib/active_support/core_ext/hash/conversions.rb +23 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -1
- data/lib/active_support/core_ext/hash/except.rb +9 -8
- data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +23 -19
- data/lib/active_support/core_ext/hash/slice.rb +1 -1
- data/lib/active_support/core_ext/hash/transform_values.rb +11 -5
- data/lib/active_support/core_ext/integer/time.rb +1 -16
- data/lib/active_support/core_ext/kernel/concern.rb +2 -0
- data/lib/active_support/core_ext/kernel/debugger.rb +3 -10
- data/lib/active_support/core_ext/kernel/reporting.rb +2 -83
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +4 -2
- data/lib/active_support/core_ext/marshal.rb +12 -11
- data/lib/active_support/core_ext/module/aliasing.rb +6 -1
- data/lib/active_support/core_ext/module/anonymous.rb +10 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -5
- data/lib/active_support/core_ext/module/attribute_accessors.rb +15 -15
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
- data/lib/active_support/core_ext/module/concerning.rb +4 -4
- data/lib/active_support/core_ext/module/delegation.rb +35 -25
- data/lib/active_support/core_ext/module/deprecation.rb +2 -2
- data/lib/active_support/core_ext/module/introspection.rb +4 -0
- data/lib/active_support/core_ext/module/method_transplanting.rb +3 -11
- data/lib/active_support/core_ext/module/qualified_const.rb +30 -12
- data/lib/active_support/core_ext/module/remove_method.rb +23 -0
- data/lib/active_support/core_ext/module.rb +1 -0
- data/lib/active_support/core_ext/name_error.rb +15 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +20 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +74 -64
- data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
- data/lib/active_support/core_ext/numeric/time.rb +24 -19
- data/lib/active_support/core_ext/numeric.rb +1 -0
- data/lib/active_support/core_ext/object/blank.rb +17 -5
- data/lib/active_support/core_ext/object/deep_dup.rb +10 -3
- data/lib/active_support/core_ext/object/duplicable.rb +8 -13
- data/lib/active_support/core_ext/object/inclusion.rb +2 -2
- data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
- data/lib/active_support/core_ext/object/json.rb +15 -7
- data/lib/active_support/core_ext/object/to_query.rb +1 -1
- data/lib/active_support/core_ext/object/try.rb +68 -22
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/object.rb +0 -1
- data/lib/active_support/core_ext/range/conversions.rb +18 -6
- data/lib/active_support/core_ext/range/each.rb +16 -18
- data/lib/active_support/core_ext/range/include_range.rb +20 -20
- data/lib/active_support/core_ext/securerandom.rb +23 -0
- data/lib/active_support/core_ext/string/access.rb +1 -1
- data/lib/active_support/core_ext/string/behavior.rb +1 -1
- data/lib/active_support/core_ext/string/conversions.rb +4 -3
- data/lib/active_support/core_ext/string/filters.rb +5 -5
- data/lib/active_support/core_ext/string/inflections.rb +32 -5
- data/lib/active_support/core_ext/string/multibyte.rb +11 -7
- data/lib/active_support/core_ext/string/output_safety.rb +18 -16
- data/lib/active_support/core_ext/string/strip.rb +3 -6
- data/lib/active_support/core_ext/struct.rb +3 -6
- data/lib/active_support/core_ext/time/calculations.rb +36 -11
- data/lib/active_support/core_ext/time/compatibility.rb +5 -0
- data/lib/active_support/core_ext/time/conversions.rb +4 -2
- data/lib/active_support/core_ext/time/marshal.rb +2 -29
- data/lib/active_support/core_ext/time/zones.rb +36 -4
- data/lib/active_support/core_ext/time.rb +1 -1
- data/lib/active_support/core_ext/uri.rb +1 -3
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/dependencies/interlock.rb +51 -0
- data/lib/active_support/dependencies.rb +87 -95
- data/lib/active_support/deprecation/behaviors.rb +16 -2
- data/lib/active_support/deprecation/method_wrappers.rb +42 -16
- data/lib/active_support/deprecation/proxy_wrappers.rb +47 -24
- data/lib/active_support/deprecation/reporting.rb +23 -5
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/duration/iso8601_parser.rb +122 -0
- data/lib/active_support/duration/iso8601_serializer.rb +51 -0
- data/lib/active_support/duration.rb +55 -10
- data/lib/active_support/evented_file_update_checker.rb +194 -0
- data/lib/active_support/execution_wrapper.rb +117 -0
- data/lib/active_support/executor.rb +6 -0
- data/lib/active_support/file_update_checker.rb +23 -3
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/hash_with_indifferent_access.rb +46 -13
- data/lib/active_support/i18n_railtie.rb +25 -4
- data/lib/active_support/inflector/inflections.rb +36 -5
- data/lib/active_support/inflector/methods.rb +97 -90
- data/lib/active_support/inflector/transliterate.rb +36 -21
- data/lib/active_support/json/decoding.rb +11 -10
- data/lib/active_support/json/encoding.rb +4 -49
- data/lib/active_support/key_generator.rb +7 -9
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +3 -3
- data/lib/active_support/log_subscriber.rb +1 -1
- data/lib/active_support/logger.rb +50 -1
- data/lib/active_support/logger_silence.rb +8 -4
- data/lib/active_support/logger_thread_safe_level.rb +31 -0
- data/lib/active_support/message_encryptor.rb +4 -4
- data/lib/active_support/message_verifier.rb +70 -8
- data/lib/active_support/multibyte/chars.rb +13 -4
- data/lib/active_support/multibyte/unicode.rb +44 -21
- data/lib/active_support/notifications/fanout.rb +6 -6
- data/lib/active_support/notifications/instrumenter.rb +20 -2
- data/lib/active_support/notifications.rb +2 -2
- data/lib/active_support/number_helper/number_to_currency_converter.rb +7 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +8 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +6 -4
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -2
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +11 -2
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +30 -25
- data/lib/active_support/number_helper.rb +90 -67
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +15 -1
- data/lib/active_support/per_thread_registry.rb +8 -3
- data/lib/active_support/rails.rb +2 -2
- data/lib/active_support/railtie.rb +6 -1
- data/lib/active_support/reloader.rb +129 -0
- data/lib/active_support/rescuable.rb +93 -47
- data/lib/active_support/security_utils.rb +7 -0
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/subscriber.rb +5 -10
- data/lib/active_support/tagged_logging.rb +3 -1
- data/lib/active_support/test_case.rb +15 -29
- data/lib/active_support/testing/assertions.rb +15 -13
- data/lib/active_support/testing/autorun.rb +8 -1
- data/lib/active_support/testing/deprecation.rb +9 -8
- data/lib/active_support/testing/file_fixtures.rb +34 -0
- data/lib/active_support/testing/isolation.rb +22 -8
- data/lib/active_support/testing/method_call_assertions.rb +41 -0
- data/lib/active_support/testing/stream.rb +42 -0
- data/lib/active_support/testing/time_helpers.rb +13 -10
- data/lib/active_support/time_with_zone.rb +135 -46
- data/lib/active_support/values/time_zone.rb +95 -47
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/xml_mini/jdom.rb +7 -6
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +7 -8
- data/lib/active_support/xml_mini.rb +22 -14
- data/lib/active_support.rb +20 -6
- metadata +33 -35
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
- data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
- data/lib/active_support/core_ext/date_time/zones.rb +0 -6
- data/lib/active_support/core_ext/object/itself.rb +0 -15
- data/lib/active_support/core_ext/thread.rb +0 -86
@@ -59,13 +59,19 @@ module ActiveSupport
|
|
59
59
|
if constructor.respond_to?(:to_hash)
|
60
60
|
super()
|
61
61
|
update(constructor)
|
62
|
+
|
63
|
+
hash = constructor.to_hash
|
64
|
+
self.default = hash.default if hash.default
|
65
|
+
self.default_proc = hash.default_proc if hash.default_proc
|
62
66
|
else
|
63
67
|
super(constructor)
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
67
|
-
def default(
|
68
|
-
|
71
|
+
def default(*args)
|
72
|
+
arg_key = args.first
|
73
|
+
|
74
|
+
if include?(key = convert_key(arg_key))
|
69
75
|
self[key]
|
70
76
|
else
|
71
77
|
super
|
@@ -73,11 +79,12 @@ module ActiveSupport
|
|
73
79
|
end
|
74
80
|
|
75
81
|
def self.new_from_hash_copying_default(hash)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
82
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
83
|
+
`ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
|
84
|
+
has been deprecated, and will be removed in Rails 5.1. The behavior of
|
85
|
+
this method is now identical to the behavior of `.new`.
|
86
|
+
MSG
|
87
|
+
new(hash)
|
81
88
|
end
|
82
89
|
|
83
90
|
def self.[](*args)
|
@@ -92,7 +99,7 @@ module ActiveSupport
|
|
92
99
|
# hash = ActiveSupport::HashWithIndifferentAccess.new
|
93
100
|
# hash[:key] = 'value'
|
94
101
|
#
|
95
|
-
# This value can be later fetched using either +:key+ or
|
102
|
+
# This value can be later fetched using either +:key+ or <tt>'key'</tt>.
|
96
103
|
def []=(key, value)
|
97
104
|
regular_writer(convert_key(key), convert_value(value, for: :assignment))
|
98
105
|
end
|
@@ -154,6 +161,20 @@ module ActiveSupport
|
|
154
161
|
alias_method :has_key?, :key?
|
155
162
|
alias_method :member?, :key?
|
156
163
|
|
164
|
+
|
165
|
+
# Same as <tt>Hash#[]</tt> where the key passed as argument can be
|
166
|
+
# either a string or a symbol:
|
167
|
+
#
|
168
|
+
# counters = ActiveSupport::HashWithIndifferentAccess.new
|
169
|
+
# counters[:foo] = 1
|
170
|
+
#
|
171
|
+
# counters['foo'] # => 1
|
172
|
+
# counters[:foo] # => 1
|
173
|
+
# counters[:zoo] # => nil
|
174
|
+
def [](key)
|
175
|
+
super(convert_key(key))
|
176
|
+
end
|
177
|
+
|
157
178
|
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
158
179
|
# either a string or a symbol:
|
159
180
|
#
|
@@ -188,7 +209,7 @@ module ActiveSupport
|
|
188
209
|
# dup[:a][:c] # => "c"
|
189
210
|
def dup
|
190
211
|
self.class.new(self).tap do |new_hash|
|
191
|
-
new_hash
|
212
|
+
set_defaults(new_hash)
|
192
213
|
end
|
193
214
|
end
|
194
215
|
|
@@ -206,7 +227,7 @@ module ActiveSupport
|
|
206
227
|
# hash['a'] = nil
|
207
228
|
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
|
208
229
|
def reverse_merge(other_hash)
|
209
|
-
super(self.class.
|
230
|
+
super(self.class.new(other_hash))
|
210
231
|
end
|
211
232
|
|
212
233
|
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
|
@@ -219,7 +240,7 @@ module ActiveSupport
|
|
219
240
|
# h = { "a" => 100, "b" => 200 }
|
220
241
|
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
|
221
242
|
def replace(other_hash)
|
222
|
-
super(self.class.
|
243
|
+
super(self.class.new(other_hash))
|
223
244
|
end
|
224
245
|
|
225
246
|
# Removes the specified key from the hash.
|
@@ -238,16 +259,20 @@ module ActiveSupport
|
|
238
259
|
def to_options!; self end
|
239
260
|
|
240
261
|
def select(*args, &block)
|
262
|
+
return to_enum(:select) unless block_given?
|
241
263
|
dup.tap { |hash| hash.select!(*args, &block) }
|
242
264
|
end
|
243
265
|
|
244
266
|
def reject(*args, &block)
|
267
|
+
return to_enum(:reject) unless block_given?
|
245
268
|
dup.tap { |hash| hash.reject!(*args, &block) }
|
246
269
|
end
|
247
270
|
|
248
271
|
# Convert to a regular hash with string keys.
|
249
272
|
def to_hash
|
250
|
-
_new_hash = Hash.new
|
273
|
+
_new_hash = Hash.new
|
274
|
+
set_defaults(_new_hash)
|
275
|
+
|
251
276
|
each do |key, value|
|
252
277
|
_new_hash[key] = convert_value(value, for: :to_hash)
|
253
278
|
end
|
@@ -267,7 +292,7 @@ module ActiveSupport
|
|
267
292
|
value.nested_under_indifferent_access
|
268
293
|
end
|
269
294
|
elsif value.is_a?(Array)
|
270
|
-
|
295
|
+
if options[:for] != :assignment || value.frozen?
|
271
296
|
value = value.dup
|
272
297
|
end
|
273
298
|
value.map! { |e| convert_value(e, options) }
|
@@ -275,6 +300,14 @@ module ActiveSupport
|
|
275
300
|
value
|
276
301
|
end
|
277
302
|
end
|
303
|
+
|
304
|
+
def set_defaults(target)
|
305
|
+
if default_proc
|
306
|
+
target.default_proc = default_proc.dup
|
307
|
+
else
|
308
|
+
target.default = default
|
309
|
+
end
|
310
|
+
end
|
278
311
|
end
|
279
312
|
end
|
280
313
|
|
@@ -37,10 +37,12 @@ module I18n
|
|
37
37
|
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
|
38
38
|
I18n.enforce_available_locales = false
|
39
39
|
|
40
|
+
reloadable_paths = []
|
40
41
|
app.config.i18n.each do |setting, value|
|
41
42
|
case setting
|
42
43
|
when :railties_load_path
|
43
|
-
|
44
|
+
reloadable_paths = value
|
45
|
+
app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
|
44
46
|
when :load_path
|
45
47
|
I18n.load_path += value
|
46
48
|
else
|
@@ -53,16 +55,29 @@ module I18n
|
|
53
55
|
# Restore available locales check so it will take place from now on.
|
54
56
|
I18n.enforce_available_locales = enforce_available_locales
|
55
57
|
|
56
|
-
|
58
|
+
directories = watched_dirs_with_extensions(reloadable_paths)
|
59
|
+
reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
|
60
|
+
I18n.load_path.keep_if { |p| File.exist?(p) }
|
61
|
+
I18n.load_path |= reloadable_paths.map(&:existent).flatten
|
62
|
+
|
63
|
+
I18n.reload!
|
64
|
+
end
|
65
|
+
|
57
66
|
app.reloaders << reloader
|
58
|
-
|
67
|
+
app.reloader.to_run do
|
68
|
+
reloader.execute_if_updated { require_unload_lock! }
|
69
|
+
# TODO: remove the following line as soon as the return value of
|
70
|
+
# callbacks is ignored, that is, returning `false` does not
|
71
|
+
# display a deprecation warning or halts the callback chain.
|
72
|
+
true
|
73
|
+
end
|
59
74
|
reloader.execute
|
60
75
|
|
61
76
|
@i18n_inited = true
|
62
77
|
end
|
63
78
|
|
64
79
|
def self.include_fallbacks_module
|
65
|
-
I18n.backend.class.
|
80
|
+
I18n.backend.class.include(I18n::Backend::Fallbacks)
|
66
81
|
end
|
67
82
|
|
68
83
|
def self.init_fallbacks(fallbacks)
|
@@ -90,5 +105,11 @@ module I18n
|
|
90
105
|
raise "Unexpected fallback type #{fallbacks.inspect}"
|
91
106
|
end
|
92
107
|
end
|
108
|
+
|
109
|
+
def self.watched_dirs_with_extensions(paths)
|
110
|
+
paths.each_with_object({}) do |path, result|
|
111
|
+
result[path.absolute_current] = path.extensions
|
112
|
+
end
|
113
|
+
end
|
93
114
|
end
|
94
115
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'concurrent/map'
|
2
2
|
require 'active_support/core_ext/array/prepend_and_append'
|
3
3
|
require 'active_support/i18n'
|
4
4
|
|
@@ -25,7 +25,38 @@ module ActiveSupport
|
|
25
25
|
# singularization rules that is runs. This guarantees that your rules run
|
26
26
|
# before any of the rules that may already have been loaded.
|
27
27
|
class Inflections
|
28
|
-
@__instance__ =
|
28
|
+
@__instance__ = Concurrent::Map.new
|
29
|
+
|
30
|
+
class Uncountables < Array
|
31
|
+
def initialize
|
32
|
+
@regex_array = []
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(entry)
|
37
|
+
super entry
|
38
|
+
@regex_array.delete(to_regex(entry))
|
39
|
+
end
|
40
|
+
|
41
|
+
def <<(*word)
|
42
|
+
add(word)
|
43
|
+
end
|
44
|
+
|
45
|
+
def add(words)
|
46
|
+
self.concat(words.flatten.map(&:downcase))
|
47
|
+
@regex_array += self.map {|word| to_regex(word) }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def uncountable?(str)
|
52
|
+
@regex_array.any? { |regex| regex === str }
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def to_regex(string)
|
57
|
+
/\b#{::Regexp.escape(string)}\Z/i
|
58
|
+
end
|
59
|
+
end
|
29
60
|
|
30
61
|
def self.instance(locale = :en)
|
31
62
|
@__instance__[locale] ||= new
|
@@ -34,7 +65,7 @@ module ActiveSupport
|
|
34
65
|
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
|
35
66
|
|
36
67
|
def initialize
|
37
|
-
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [],
|
68
|
+
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
|
38
69
|
end
|
39
70
|
|
40
71
|
# Private, for the test suite.
|
@@ -160,7 +191,7 @@ module ActiveSupport
|
|
160
191
|
# uncountable 'money', 'information'
|
161
192
|
# uncountable %w( money information rice )
|
162
193
|
def uncountable(*words)
|
163
|
-
@uncountables
|
194
|
+
@uncountables.add(words)
|
164
195
|
end
|
165
196
|
|
166
197
|
# Specifies a humanized form of a string by a regular expression rule or
|
@@ -185,7 +216,7 @@ module ActiveSupport
|
|
185
216
|
def clear(scope = :all)
|
186
217
|
case scope
|
187
218
|
when :all
|
188
|
-
@plurals, @singulars, @uncountables, @humans = [], [],
|
219
|
+
@plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
|
189
220
|
else
|
190
221
|
instance_variable_set "@#{scope}", []
|
191
222
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'active_support/inflections'
|
4
2
|
|
5
3
|
module ActiveSupport
|
@@ -22,58 +20,58 @@ module ActiveSupport
|
|
22
20
|
# pluralized using rules defined for that language. By default,
|
23
21
|
# this parameter is set to <tt>:en</tt>.
|
24
22
|
#
|
25
|
-
# 'post'
|
26
|
-
# 'octopus'
|
27
|
-
# 'sheep'
|
28
|
-
# 'words'
|
29
|
-
# 'CamelOctopus'
|
30
|
-
# 'ley'
|
23
|
+
# pluralize('post') # => "posts"
|
24
|
+
# pluralize('octopus') # => "octopi"
|
25
|
+
# pluralize('sheep') # => "sheep"
|
26
|
+
# pluralize('words') # => "words"
|
27
|
+
# pluralize('CamelOctopus') # => "CamelOctopi"
|
28
|
+
# pluralize('ley', :es) # => "leyes"
|
31
29
|
def pluralize(word, locale = :en)
|
32
30
|
apply_inflections(word, inflections(locale).plurals)
|
33
31
|
end
|
34
32
|
|
35
|
-
# The reverse of
|
33
|
+
# The reverse of #pluralize, returns the singular form of a word in a
|
36
34
|
# string.
|
37
35
|
#
|
38
36
|
# If passed an optional +locale+ parameter, the word will be
|
39
37
|
# singularized using rules defined for that language. By default,
|
40
38
|
# this parameter is set to <tt>:en</tt>.
|
41
39
|
#
|
42
|
-
# 'posts'
|
43
|
-
# 'octopi'
|
44
|
-
# 'sheep'
|
45
|
-
# 'word'
|
46
|
-
# 'CamelOctopi'
|
47
|
-
# 'leyes'
|
40
|
+
# singularize('posts') # => "post"
|
41
|
+
# singularize('octopi') # => "octopus"
|
42
|
+
# singularize('sheep') # => "sheep"
|
43
|
+
# singularize('word') # => "word"
|
44
|
+
# singularize('CamelOctopi') # => "CamelOctopus"
|
45
|
+
# singularize('leyes', :es) # => "ley"
|
48
46
|
def singularize(word, locale = :en)
|
49
47
|
apply_inflections(word, inflections(locale).singulars)
|
50
48
|
end
|
51
49
|
|
52
|
-
#
|
53
|
-
#
|
50
|
+
# Converts strings to UpperCamelCase.
|
51
|
+
# If the +uppercase_first_letter+ parameter is set to false, then produces
|
54
52
|
# lowerCamelCase.
|
55
53
|
#
|
56
|
-
#
|
54
|
+
# Also converts '/' to '::' which is useful for converting
|
57
55
|
# paths to namespaces.
|
58
56
|
#
|
59
|
-
# 'active_model'
|
60
|
-
# 'active_model'
|
61
|
-
# 'active_model/errors'
|
62
|
-
# 'active_model/errors'
|
57
|
+
# camelize('active_model') # => "ActiveModel"
|
58
|
+
# camelize('active_model', false) # => "activeModel"
|
59
|
+
# camelize('active_model/errors') # => "ActiveModel::Errors"
|
60
|
+
# camelize('active_model/errors', false) # => "activeModel::Errors"
|
63
61
|
#
|
64
62
|
# As a rule of thumb you can think of +camelize+ as the inverse of
|
65
|
-
#
|
63
|
+
# #underscore, though there are cases where that does not hold:
|
66
64
|
#
|
67
|
-
# 'SSLError'
|
65
|
+
# camelize(underscore('SSLError')) # => "SslError"
|
68
66
|
def camelize(term, uppercase_first_letter = true)
|
69
67
|
string = term.to_s
|
70
68
|
if uppercase_first_letter
|
71
|
-
string = string.sub(/^[a-z\d]*/) { inflections.acronyms[
|
69
|
+
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
|
72
70
|
else
|
73
|
-
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) {
|
71
|
+
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
|
74
72
|
end
|
75
73
|
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
|
76
|
-
string.gsub!(
|
74
|
+
string.gsub!('/'.freeze, '::'.freeze)
|
77
75
|
string
|
78
76
|
end
|
79
77
|
|
@@ -81,34 +79,34 @@ module ActiveSupport
|
|
81
79
|
#
|
82
80
|
# Changes '::' to '/' to convert namespaces to paths.
|
83
81
|
#
|
84
|
-
# 'ActiveModel'
|
85
|
-
# 'ActiveModel::Errors'
|
82
|
+
# underscore('ActiveModel') # => "active_model"
|
83
|
+
# underscore('ActiveModel::Errors') # => "active_model/errors"
|
86
84
|
#
|
87
85
|
# As a rule of thumb you can think of +underscore+ as the inverse of
|
88
|
-
#
|
86
|
+
# #camelize, though there are cases where that does not hold:
|
89
87
|
#
|
90
|
-
# 'SSLError'
|
88
|
+
# camelize(underscore('SSLError')) # => "SslError"
|
91
89
|
def underscore(camel_cased_word)
|
92
90
|
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
93
|
-
word = camel_cased_word.to_s.gsub(
|
94
|
-
word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
|
95
|
-
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
96
|
-
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
97
|
-
word.tr!("-", "_")
|
91
|
+
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
|
92
|
+
word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
|
93
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
|
94
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
|
95
|
+
word.tr!("-".freeze, "_".freeze)
|
98
96
|
word.downcase!
|
99
97
|
word
|
100
98
|
end
|
101
99
|
|
102
100
|
# Tweaks an attribute name for display to end users.
|
103
101
|
#
|
104
|
-
# Specifically,
|
102
|
+
# Specifically, performs these transformations:
|
105
103
|
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
104
|
+
# * Applies human inflection rules to the argument.
|
105
|
+
# * Deletes leading underscores, if any.
|
106
|
+
# * Removes a "_id" suffix if present.
|
107
|
+
# * Replaces underscores with spaces, if any.
|
108
|
+
# * Downcases all words except acronyms.
|
109
|
+
# * Capitalizes the first word.
|
112
110
|
#
|
113
111
|
# The capitalization of the first word can be turned off by setting the
|
114
112
|
# +:capitalize+ option to false (default is true).
|
@@ -127,9 +125,9 @@ module ActiveSupport
|
|
127
125
|
|
128
126
|
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
|
129
127
|
|
130
|
-
result.sub!(/\A_+/, '')
|
131
|
-
result.sub!(/_id\z/, '')
|
132
|
-
result.tr!('_', ' ')
|
128
|
+
result.sub!(/\A_+/, ''.freeze)
|
129
|
+
result.sub!(/_id\z/, ''.freeze)
|
130
|
+
result.tr!('_'.freeze, ' '.freeze)
|
133
131
|
|
134
132
|
result.gsub!(/([a-z\d]*)/i) do |match|
|
135
133
|
"#{inflections.acronyms[match] || match.downcase}"
|
@@ -142,60 +140,69 @@ module ActiveSupport
|
|
142
140
|
result
|
143
141
|
end
|
144
142
|
|
143
|
+
# Converts just the first character to uppercase.
|
144
|
+
#
|
145
|
+
# upcase_first('what a Lovely Day') # => "What a Lovely Day"
|
146
|
+
# upcase_first('w') # => "W"
|
147
|
+
# upcase_first('') # => ""
|
148
|
+
def upcase_first(string)
|
149
|
+
string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
|
150
|
+
end
|
151
|
+
|
145
152
|
# Capitalizes all the words and replaces some characters in the string to
|
146
153
|
# create a nicer looking title. +titleize+ is meant for creating pretty
|
147
154
|
# output. It is not used in the Rails internals.
|
148
155
|
#
|
149
156
|
# +titleize+ is also aliased as +titlecase+.
|
150
157
|
#
|
151
|
-
# 'man from the boondocks'
|
152
|
-
# 'x-men: the last stand'
|
153
|
-
# 'TheManWithoutAPast'
|
154
|
-
# 'raiders_of_the_lost_ark'
|
158
|
+
# titleize('man from the boondocks') # => "Man From The Boondocks"
|
159
|
+
# titleize('x-men: the last stand') # => "X Men: The Last Stand"
|
160
|
+
# titleize('TheManWithoutAPast') # => "The Man Without A Past"
|
161
|
+
# titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
|
155
162
|
def titleize(word)
|
156
|
-
humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) {
|
163
|
+
humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
|
157
164
|
end
|
158
165
|
|
159
|
-
#
|
160
|
-
# method uses the
|
166
|
+
# Creates the name of a table like Rails does for models to table names.
|
167
|
+
# This method uses the #pluralize method on the last word in the string.
|
161
168
|
#
|
162
|
-
# 'RawScaledScorer'
|
163
|
-
# '
|
164
|
-
# 'fancyCategory'
|
169
|
+
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
|
170
|
+
# tableize('ham_and_egg') # => "ham_and_eggs"
|
171
|
+
# tableize('fancyCategory') # => "fancy_categories"
|
165
172
|
def tableize(class_name)
|
166
173
|
pluralize(underscore(class_name))
|
167
174
|
end
|
168
175
|
|
169
|
-
#
|
176
|
+
# Creates a class name from a plural table name like Rails does for table
|
170
177
|
# names to models. Note that this returns a string and not a Class (To
|
171
|
-
# convert to an actual class follow +classify+ with
|
178
|
+
# convert to an actual class follow +classify+ with #constantize).
|
172
179
|
#
|
173
|
-
# '
|
174
|
-
# 'posts'
|
180
|
+
# classify('ham_and_eggs') # => "HamAndEgg"
|
181
|
+
# classify('posts') # => "Post"
|
175
182
|
#
|
176
183
|
# Singular names are not handled correctly:
|
177
184
|
#
|
178
|
-
# 'calculus'
|
185
|
+
# classify('calculus') # => "Calculus"
|
179
186
|
def classify(table_name)
|
180
187
|
# strip out any leading schema name
|
181
|
-
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
|
188
|
+
camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
|
182
189
|
end
|
183
190
|
|
184
191
|
# Replaces underscores with dashes in the string.
|
185
192
|
#
|
186
|
-
# 'puni_puni'
|
193
|
+
# dasherize('puni_puni') # => "puni-puni"
|
187
194
|
def dasherize(underscored_word)
|
188
|
-
underscored_word.tr('_', '-')
|
195
|
+
underscored_word.tr('_'.freeze, '-'.freeze)
|
189
196
|
end
|
190
197
|
|
191
198
|
# Removes the module part from the expression in the string.
|
192
199
|
#
|
193
|
-
# 'ActiveRecord::CoreExtensions::String::Inflections'
|
194
|
-
# 'Inflections'
|
195
|
-
# '::Inflections'
|
196
|
-
# ''
|
200
|
+
# demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
|
201
|
+
# demodulize('Inflections') # => "Inflections"
|
202
|
+
# demodulize('::Inflections') # => "Inflections"
|
203
|
+
# demodulize('') # => ""
|
197
204
|
#
|
198
|
-
# See also
|
205
|
+
# See also #deconstantize.
|
199
206
|
def demodulize(path)
|
200
207
|
path = path.to_s
|
201
208
|
if i = path.rindex('::')
|
@@ -207,13 +214,13 @@ module ActiveSupport
|
|
207
214
|
|
208
215
|
# Removes the rightmost segment from the constant expression in the string.
|
209
216
|
#
|
210
|
-
# 'Net::HTTP'
|
211
|
-
# '::Net::HTTP'
|
212
|
-
# 'String'
|
213
|
-
# '::String'
|
214
|
-
# ''
|
217
|
+
# deconstantize('Net::HTTP') # => "Net"
|
218
|
+
# deconstantize('::Net::HTTP') # => "::Net"
|
219
|
+
# deconstantize('String') # => ""
|
220
|
+
# deconstantize('::String') # => ""
|
221
|
+
# deconstantize('') # => ""
|
215
222
|
#
|
216
|
-
# See also
|
223
|
+
# See also #demodulize.
|
217
224
|
def deconstantize(path)
|
218
225
|
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
|
219
226
|
end
|
@@ -222,17 +229,17 @@ module ActiveSupport
|
|
222
229
|
# +separate_class_name_and_id_with_underscore+ sets whether
|
223
230
|
# the method should put '_' between the name and 'id'.
|
224
231
|
#
|
225
|
-
# 'Message'
|
226
|
-
# 'Message'
|
227
|
-
# 'Admin::Post'
|
232
|
+
# foreign_key('Message') # => "message_id"
|
233
|
+
# foreign_key('Message', false) # => "messageid"
|
234
|
+
# foreign_key('Admin::Post') # => "post_id"
|
228
235
|
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
|
229
236
|
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
|
230
237
|
end
|
231
238
|
|
232
239
|
# Tries to find a constant with the name specified in the argument string.
|
233
240
|
#
|
234
|
-
# 'Module'.constantize
|
235
|
-
# '
|
241
|
+
# 'Module'.constantize # => Module
|
242
|
+
# 'Foo::Bar'.constantize # => Foo::Bar
|
236
243
|
#
|
237
244
|
# The name is assumed to be the one of a top-level constant, no matter
|
238
245
|
# whether it starts with "::" or not. No lexical context is taken into
|
@@ -248,7 +255,7 @@ module ActiveSupport
|
|
248
255
|
# NameError is raised when the name is not in CamelCase or the constant is
|
249
256
|
# unknown.
|
250
257
|
def constantize(camel_cased_word)
|
251
|
-
names = camel_cased_word.split('::')
|
258
|
+
names = camel_cased_word.split('::'.freeze)
|
252
259
|
|
253
260
|
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
254
261
|
Object.const_get(camel_cased_word) if names.empty?
|
@@ -280,8 +287,8 @@ module ActiveSupport
|
|
280
287
|
|
281
288
|
# Tries to find a constant with the name specified in the argument string.
|
282
289
|
#
|
283
|
-
# 'Module'
|
284
|
-
# '
|
290
|
+
# safe_constantize('Module') # => Module
|
291
|
+
# safe_constantize('Foo::Bar') # => Foo::Bar
|
285
292
|
#
|
286
293
|
# The name is assumed to be the one of a top-level constant, no matter
|
287
294
|
# whether it starts with "::" or not. No lexical context is taken into
|
@@ -290,16 +297,16 @@ module ActiveSupport
|
|
290
297
|
# C = 'outside'
|
291
298
|
# module M
|
292
299
|
# C = 'inside'
|
293
|
-
# C
|
294
|
-
# 'C'
|
300
|
+
# C # => 'inside'
|
301
|
+
# safe_constantize('C') # => 'outside', same as ::C
|
295
302
|
# end
|
296
303
|
#
|
297
304
|
# +nil+ is returned when the name is not in CamelCase or the constant (or
|
298
305
|
# part of it) is unknown.
|
299
306
|
#
|
300
|
-
# 'blargle'
|
301
|
-
# 'UnknownModule'
|
302
|
-
# 'UnknownModule::Foo::Bar'
|
307
|
+
# safe_constantize('blargle') # => nil
|
308
|
+
# safe_constantize('UnknownModule') # => nil
|
309
|
+
# safe_constantize('UnknownModule::Foo::Bar') # => nil
|
303
310
|
def safe_constantize(camel_cased_word)
|
304
311
|
constantize(camel_cased_word)
|
305
312
|
rescue NameError => e
|
@@ -354,7 +361,7 @@ module ActiveSupport
|
|
354
361
|
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
|
355
362
|
# const_regexp("::") # => "::"
|
356
363
|
def const_regexp(camel_cased_word) #:nodoc:
|
357
|
-
parts = camel_cased_word.split("::")
|
364
|
+
parts = camel_cased_word.split("::".freeze)
|
358
365
|
|
359
366
|
return Regexp.escape(camel_cased_word) if parts.blank?
|
360
367
|
|
@@ -372,7 +379,7 @@ module ActiveSupport
|
|
372
379
|
def apply_inflections(word, rules)
|
373
380
|
result = word.to_s.dup
|
374
381
|
|
375
|
-
if word.empty? || inflections.uncountables.
|
382
|
+
if word.empty? || inflections.uncountables.uncountable?(result)
|
376
383
|
result
|
377
384
|
else
|
378
385
|
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
|