activesupport 4.1.15 → 4.2.11.3
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 +5 -5
- data/CHANGELOG.md +395 -574
- data/README.rdoc +7 -2
- data/lib/active_support.rb +19 -0
- data/lib/active_support/backtrace_cleaner.rb +4 -4
- data/lib/active_support/cache.rb +17 -19
- data/lib/active_support/cache/file_store.rb +5 -0
- data/lib/active_support/cache/mem_cache_store.rb +1 -1
- data/lib/active_support/cache/strategy/local_cache.rb +5 -4
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +5 -0
- data/lib/active_support/callbacks.rb +41 -33
- data/lib/active_support/concern.rb +10 -2
- data/lib/active_support/core_ext/array/access.rb +9 -1
- data/lib/active_support/core_ext/array/grouping.rb +5 -0
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +2 -0
- data/lib/active_support/core_ext/class/delegating_attributes.rb +4 -0
- data/lib/active_support/core_ext/class/subclasses.rb +0 -2
- data/lib/active_support/core_ext/date/conversions.rb +6 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +11 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_time.rb +1 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +34 -4
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +2 -2
- data/lib/active_support/core_ext/digest/uuid.rb +51 -0
- data/lib/active_support/core_ext/enumerable.rb +16 -0
- data/lib/active_support/core_ext/file/atomic.rb +1 -1
- data/lib/active_support/core_ext/hash.rb +1 -0
- data/lib/active_support/core_ext/hash/compact.rb +20 -16
- data/lib/active_support/core_ext/hash/conversions.rb +3 -5
- data/lib/active_support/core_ext/hash/except.rb +8 -2
- data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +10 -6
- data/lib/active_support/core_ext/hash/slice.rb +8 -2
- data/lib/active_support/core_ext/hash/transform_values.rb +23 -0
- data/lib/active_support/core_ext/integer/time.rb +0 -15
- data/lib/active_support/core_ext/kernel.rb +3 -2
- data/lib/active_support/core_ext/kernel/concern.rb +10 -0
- data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +15 -0
- data/lib/active_support/core_ext/load_error.rb +4 -1
- data/lib/active_support/core_ext/marshal.rb +8 -5
- data/lib/active_support/core_ext/module/aliasing.rb +2 -2
- data/lib/active_support/core_ext/module/delegation.rb +34 -18
- data/lib/active_support/core_ext/module/method_transplanting.rb +3 -1
- data/lib/active_support/core_ext/numeric/conversions.rb +11 -3
- data/lib/active_support/core_ext/numeric/time.rb +1 -34
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/duplicable.rb +62 -33
- data/lib/active_support/core_ext/object/itself.rb +15 -0
- data/lib/active_support/core_ext/object/json.rb +2 -2
- data/lib/active_support/core_ext/object/to_query.rb +2 -1
- data/lib/active_support/core_ext/object/try.rb +35 -13
- data/lib/active_support/core_ext/object/with_options.rb +30 -3
- data/lib/active_support/core_ext/string/access.rb +5 -5
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +44 -6
- data/lib/active_support/core_ext/string/inflections.rb +4 -1
- data/lib/active_support/core_ext/string/output_safety.rb +33 -14
- data/lib/active_support/core_ext/thread.rb +7 -0
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/time/calculations.rb +31 -7
- data/lib/active_support/core_ext/time/compatibility.rb +14 -0
- data/lib/active_support/core_ext/time/conversions.rb +1 -1
- data/lib/active_support/dependencies.rb +32 -18
- data/lib/active_support/dependencies/autoload.rb +1 -1
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/deprecation/behaviors.rb +1 -1
- data/lib/active_support/duration.rb +47 -5
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/hash_with_indifferent_access.rb +35 -7
- data/lib/active_support/i18n_railtie.rb +1 -7
- data/lib/active_support/inflector/inflections.rb +2 -2
- data/lib/active_support/inflector/methods.rb +43 -19
- data/lib/active_support/json/decoding.rb +1 -1
- data/lib/active_support/json/encoding.rb +3 -2
- data/lib/active_support/logger.rb +36 -0
- data/lib/active_support/logger_silence.rb +4 -22
- data/lib/active_support/logger_thread_safe_level.rb +32 -0
- data/lib/active_support/message_encryptor.rb +10 -2
- data/lib/active_support/message_verifier.rb +11 -12
- data/lib/active_support/multibyte/chars.rb +1 -1
- data/lib/active_support/multibyte/unicode.rb +5 -4
- data/lib/active_support/notifications.rb +8 -3
- data/lib/active_support/notifications/fanout.rb +12 -7
- data/lib/active_support/number_helper.rb +12 -13
- data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +1 -1
- data/lib/active_support/per_thread_registry.rb +5 -3
- data/lib/active_support/test_case.rb +46 -12
- data/lib/active_support/testing/assertions.rb +1 -1
- data/lib/active_support/testing/constant_lookup.rb +1 -5
- data/lib/active_support/testing/declarative.rb +1 -25
- data/lib/active_support/testing/isolation.rb +16 -6
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +23 -16
- data/lib/active_support/time.rb +0 -2
- data/lib/active_support/time_with_zone.rb +48 -29
- data/lib/active_support/values/time_zone.rb +81 -75
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/xml_mini.rb +30 -15
- data/lib/active_support/xml_mini/libxml.rb +1 -3
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -4
- data/lib/active_support/xml_mini/nokogiri.rb +1 -3
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -3
- data/lib/active_support/xml_mini/rexml.rb +1 -3
- metadata +21 -36
- data/lib/active_support/core_ext/object/to_json.rb +0 -5
- data/lib/active_support/file_watcher.rb +0 -36
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
class Object
|
4
4
|
# An object is blank if it's false, empty, or a whitespace string.
|
5
|
-
# For example, '', ' ', +nil+, [], and {} are all blank.
|
5
|
+
# For example, +false+, '', ' ', +nil+, [], and {} are all blank.
|
6
6
|
#
|
7
7
|
# This simplifies
|
8
8
|
#
|
9
|
-
# address
|
9
|
+
# !address || address.empty?
|
10
10
|
#
|
11
11
|
# to
|
12
12
|
#
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#--
|
2
|
-
# Most objects are cloneable, but not all. For example you can't dup
|
2
|
+
# Most objects are cloneable, but not all. For example you can't dup methods:
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# method(:puts).dup # => TypeError: allocator undefined for Method
|
5
5
|
#
|
6
6
|
# Classes may signal their instances are not duplicable removing +dup+/+clone+
|
7
7
|
# or raising exceptions from them. So, to dup an arbitrary object you normally
|
@@ -19,7 +19,7 @@
|
|
19
19
|
class Object
|
20
20
|
# Can you safely dup this object?
|
21
21
|
#
|
22
|
-
# False for +nil+, +false+, +true+, symbol, and
|
22
|
+
# False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects;
|
23
23
|
# true otherwise.
|
24
24
|
def duplicable?
|
25
25
|
true
|
@@ -27,57 +27,86 @@ class Object
|
|
27
27
|
end
|
28
28
|
|
29
29
|
class NilClass
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
begin
|
31
|
+
nil.dup
|
32
|
+
rescue TypeError
|
33
|
+
|
34
|
+
# +nil+ is not duplicable:
|
35
|
+
#
|
36
|
+
# nil.duplicable? # => false
|
37
|
+
# nil.dup # => TypeError: can't dup NilClass
|
38
|
+
def duplicable?
|
39
|
+
false
|
40
|
+
end
|
36
41
|
end
|
37
42
|
end
|
38
43
|
|
39
44
|
class FalseClass
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
begin
|
46
|
+
false.dup
|
47
|
+
rescue TypeError
|
48
|
+
|
49
|
+
# +false+ is not duplicable:
|
50
|
+
#
|
51
|
+
# false.duplicable? # => false
|
52
|
+
# false.dup # => TypeError: can't dup FalseClass
|
53
|
+
def duplicable?
|
54
|
+
false
|
55
|
+
end
|
46
56
|
end
|
47
57
|
end
|
48
58
|
|
49
59
|
class TrueClass
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
60
|
+
begin
|
61
|
+
true.dup
|
62
|
+
rescue TypeError
|
63
|
+
|
64
|
+
# +true+ is not duplicable:
|
65
|
+
#
|
66
|
+
# true.duplicable? # => false
|
67
|
+
# true.dup # => TypeError: can't dup TrueClass
|
68
|
+
def duplicable?
|
69
|
+
false
|
70
|
+
end
|
56
71
|
end
|
57
72
|
end
|
58
73
|
|
59
74
|
class Symbol
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
75
|
+
begin
|
76
|
+
:symbol.dup # Ruby 2.4.x.
|
77
|
+
'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
|
78
|
+
rescue TypeError
|
79
|
+
|
80
|
+
# Symbols are not duplicable:
|
81
|
+
#
|
82
|
+
# :my_symbol.duplicable? # => false
|
83
|
+
# :my_symbol.dup # => TypeError: can't dup Symbol
|
84
|
+
def duplicable?
|
85
|
+
false
|
86
|
+
end
|
66
87
|
end
|
67
88
|
end
|
68
89
|
|
69
90
|
class Numeric
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
91
|
+
begin
|
92
|
+
1.dup
|
93
|
+
rescue TypeError
|
94
|
+
|
95
|
+
# Numbers are not duplicable:
|
96
|
+
#
|
97
|
+
# 3.duplicable? # => false
|
98
|
+
# 3.dup # => TypeError: can't dup Integer
|
99
|
+
def duplicable?
|
100
|
+
false
|
101
|
+
end
|
76
102
|
end
|
77
103
|
end
|
78
104
|
|
79
105
|
require 'bigdecimal'
|
80
106
|
class BigDecimal
|
107
|
+
# Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
|
108
|
+
# raises TypeError exception. Checking here on the runtime whether BigDecimal
|
109
|
+
# will allow dup or not.
|
81
110
|
begin
|
82
111
|
BigDecimal.new('4.56').dup
|
83
112
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Object
|
2
|
+
# TODO: Remove this file when we drop support for Ruby < 2.2
|
3
|
+
unless respond_to?(:itself)
|
4
|
+
# Returns the object itself.
|
5
|
+
#
|
6
|
+
# Useful for chaining methods, such as Active Record scopes:
|
7
|
+
#
|
8
|
+
# Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
|
9
|
+
#
|
10
|
+
# @return Object
|
11
|
+
def itself
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -26,9 +26,9 @@ require 'active_support/core_ext/module/aliasing'
|
|
26
26
|
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
|
27
27
|
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
|
28
28
|
# should give exactly the same results with or without active support.
|
29
|
-
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
|
29
|
+
[Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
|
30
30
|
klass.class_eval do
|
31
|
-
def to_json_with_active_support_encoder(options = nil)
|
31
|
+
def to_json_with_active_support_encoder(options = nil) # :nodoc:
|
32
32
|
if options.is_a?(::JSON::State)
|
33
33
|
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
|
34
34
|
self.to_json_without_active_support_encoder(options)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
1
3
|
class Object
|
2
4
|
# Alias of <tt>to_s</tt>.
|
3
5
|
def to_param
|
@@ -7,7 +9,6 @@ class Object
|
|
7
9
|
# Converts an object into a string suitable for use as a URL query string,
|
8
10
|
# using the given <tt>key</tt> as the param name.
|
9
11
|
def to_query(key)
|
10
|
-
require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
|
11
12
|
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
|
12
13
|
end
|
13
14
|
end
|
@@ -9,7 +9,23 @@ class Object
|
|
9
9
|
#
|
10
10
|
# instead of
|
11
11
|
#
|
12
|
-
# @person
|
12
|
+
# @person.name if @person
|
13
|
+
#
|
14
|
+
# +try+ calls can be chained:
|
15
|
+
#
|
16
|
+
# @person.try(:spouse).try(:name)
|
17
|
+
#
|
18
|
+
# instead of
|
19
|
+
#
|
20
|
+
# @person.spouse.name if @person && @person.spouse
|
21
|
+
#
|
22
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
23
|
+
#
|
24
|
+
# @person.try(:non_existing_method) #=> nil
|
25
|
+
#
|
26
|
+
# instead of
|
27
|
+
#
|
28
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
|
13
29
|
#
|
14
30
|
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
15
31
|
# to the method:
|
@@ -24,7 +40,7 @@ class Object
|
|
24
40
|
#
|
25
41
|
# The number of arguments in the signature must match. If the object responds
|
26
42
|
# to the method the call is attempted and +ArgumentError+ is still raised
|
27
|
-
#
|
43
|
+
# in case of argument mismatch.
|
28
44
|
#
|
29
45
|
# If +try+ is called without arguments it yields the receiver to a given
|
30
46
|
# block unless it is +nil+:
|
@@ -33,24 +49,30 @@ class Object
|
|
33
49
|
# ...
|
34
50
|
# end
|
35
51
|
#
|
36
|
-
#
|
52
|
+
# You can also call try with a block without accepting an argument, and the block
|
53
|
+
# will be instance_eval'ed instead:
|
54
|
+
#
|
55
|
+
# @person.try { upcase.truncate(50) }
|
56
|
+
#
|
57
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
37
58
|
# with instances of classes that do not have +Object+ among their ancestors,
|
38
59
|
# like direct subclasses of +BasicObject+. For example, using +try+ with
|
39
60
|
# +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
|
40
|
-
# delegator itself.
|
61
|
+
# the delegator itself.
|
41
62
|
def try(*a, &b)
|
42
|
-
if a.empty?
|
43
|
-
yield self
|
44
|
-
else
|
45
|
-
public_send(*a, &b) if respond_to?(a.first)
|
46
|
-
end
|
63
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
47
64
|
end
|
48
65
|
|
49
|
-
# Same as #try, but will raise a NoMethodError exception if the
|
66
|
+
# Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
|
50
67
|
# does not implement the tried method.
|
68
|
+
|
51
69
|
def try!(*a, &b)
|
52
70
|
if a.empty? && block_given?
|
53
|
-
|
71
|
+
if b.arity == 0
|
72
|
+
instance_eval(&b)
|
73
|
+
else
|
74
|
+
yield self
|
75
|
+
end
|
54
76
|
else
|
55
77
|
public_send(*a, &b)
|
56
78
|
end
|
@@ -59,12 +81,12 @@ end
|
|
59
81
|
|
60
82
|
class NilClass
|
61
83
|
# Calling +try+ on +nil+ always returns +nil+.
|
62
|
-
# It becomes
|
84
|
+
# It becomes especially helpful when navigating through associations that may return +nil+.
|
63
85
|
#
|
64
86
|
# nil.try(:name) # => nil
|
65
87
|
#
|
66
88
|
# Without +try+
|
67
|
-
# @person &&
|
89
|
+
# @person && @person.children.any? && @person.children.first.name
|
68
90
|
#
|
69
91
|
# With +try+
|
70
92
|
# @person.try(:children).try(:first).try(:name)
|
@@ -34,9 +34,36 @@ class Object
|
|
34
34
|
# body i18n.t :body, user_name: user.name
|
35
35
|
# end
|
36
36
|
#
|
37
|
+
# When you don't pass an explicit receiver, it executes the whole block
|
38
|
+
# in merging options context:
|
39
|
+
#
|
40
|
+
# class Account < ActiveRecord::Base
|
41
|
+
# with_options dependent: :destroy do
|
42
|
+
# has_many :customers
|
43
|
+
# has_many :products
|
44
|
+
# has_many :invoices
|
45
|
+
# has_many :expenses
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
37
49
|
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
|
38
|
-
#
|
39
|
-
|
40
|
-
|
50
|
+
#
|
51
|
+
# NOTE: Each nesting level will merge inherited defaults in addition to their own.
|
52
|
+
#
|
53
|
+
# class Post < ActiveRecord::Base
|
54
|
+
# with_options if: :persisted?, length: { minimum: 50 } do
|
55
|
+
# validates :content, if: -> { content.present? }
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# The code is equivalent to:
|
60
|
+
#
|
61
|
+
# validates :content, length: { minimum: 50 }, if: -> { content.present? }
|
62
|
+
#
|
63
|
+
# Hence the inherited default for `if` key is ignored.
|
64
|
+
#
|
65
|
+
def with_options(options, &block)
|
66
|
+
option_merger = ActiveSupport::OptionMerger.new(self, options)
|
67
|
+
block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
|
41
68
|
end
|
42
69
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class String
|
2
|
-
# If you pass a single
|
2
|
+
# If you pass a single integer, returns a substring of one character at that
|
3
3
|
# position. The first character of the string is at position 0, the next at
|
4
4
|
# position 1, and so on. If a range is supplied, a substring containing
|
5
5
|
# characters at offsets given by the range is returned. In both cases, if an
|
@@ -64,7 +64,7 @@ class String
|
|
64
64
|
|
65
65
|
# Returns the first character. If a limit is supplied, returns a substring
|
66
66
|
# from the beginning of the string until it reaches the limit value. If the
|
67
|
-
# given limit is greater than or equal to the string length, returns self.
|
67
|
+
# given limit is greater than or equal to the string length, returns a copy of self.
|
68
68
|
#
|
69
69
|
# str = "hello"
|
70
70
|
# str.first # => "h"
|
@@ -76,7 +76,7 @@ class String
|
|
76
76
|
if limit == 0
|
77
77
|
''
|
78
78
|
elsif limit >= size
|
79
|
-
self
|
79
|
+
self.dup
|
80
80
|
else
|
81
81
|
to(limit - 1)
|
82
82
|
end
|
@@ -84,7 +84,7 @@ class String
|
|
84
84
|
|
85
85
|
# Returns the last character of the string. If a limit is supplied, returns a substring
|
86
86
|
# from the end of the string until it reaches the limit value (counting backwards). If
|
87
|
-
# the given limit is greater than or equal to the string length, returns self.
|
87
|
+
# the given limit is greater than or equal to the string length, returns a copy of self.
|
88
88
|
#
|
89
89
|
# str = "hello"
|
90
90
|
# str.last # => "o"
|
@@ -96,7 +96,7 @@ class String
|
|
96
96
|
if limit == 0
|
97
97
|
''
|
98
98
|
elsif limit >= size
|
99
|
-
self
|
99
|
+
self.dup
|
100
100
|
else
|
101
101
|
from(-limit)
|
102
102
|
end
|
@@ -13,6 +13,9 @@ class String
|
|
13
13
|
end
|
14
14
|
|
15
15
|
# Performs a destructive squish. See String#squish.
|
16
|
+
# str = " foo bar \n \t boo"
|
17
|
+
# str.squish! # => "foo bar boo"
|
18
|
+
# str # => "foo bar boo"
|
16
19
|
def squish!
|
17
20
|
gsub!(/\A[[:space:]]+/, '')
|
18
21
|
gsub!(/[[:space:]]+\z/, '')
|
@@ -20,14 +23,25 @@ class String
|
|
20
23
|
self
|
21
24
|
end
|
22
25
|
|
23
|
-
# Returns a new string with all occurrences of the
|
24
|
-
|
25
|
-
|
26
|
+
# Returns a new string with all occurrences of the patterns removed.
|
27
|
+
# str = "foo bar test"
|
28
|
+
# str.remove(" test") # => "foo bar"
|
29
|
+
# str.remove(" test", /bar/) # => "foo "
|
30
|
+
# str # => "foo bar test"
|
31
|
+
def remove(*patterns)
|
32
|
+
dup.remove!(*patterns)
|
26
33
|
end
|
27
34
|
|
28
|
-
# Alters the string by removing all occurrences of the
|
29
|
-
|
30
|
-
|
35
|
+
# Alters the string by removing all occurrences of the patterns.
|
36
|
+
# str = "foo bar test"
|
37
|
+
# str.remove!(" test", /bar/) # => "foo "
|
38
|
+
# str # => "foo "
|
39
|
+
def remove!(*patterns)
|
40
|
+
patterns.each do |pattern|
|
41
|
+
gsub! pattern, ""
|
42
|
+
end
|
43
|
+
|
44
|
+
self
|
31
45
|
end
|
32
46
|
|
33
47
|
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
|
@@ -62,4 +76,28 @@ class String
|
|
62
76
|
|
63
77
|
"#{self[0, stop]}#{omission}"
|
64
78
|
end
|
79
|
+
|
80
|
+
# Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
|
81
|
+
#
|
82
|
+
# 'Once upon a time in a world far far away'.truncate_words(4)
|
83
|
+
# # => "Once upon a time..."
|
84
|
+
#
|
85
|
+
# Pass a string or regexp <tt>:separator</tt> to specify a different separator of words:
|
86
|
+
#
|
87
|
+
# 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>')
|
88
|
+
# # => "Once<br>upon<br>a<br>time<br>in..."
|
89
|
+
#
|
90
|
+
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."):
|
91
|
+
#
|
92
|
+
# 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
|
93
|
+
# # => "And they found that many... (continued)"
|
94
|
+
def truncate_words(words_count, options = {})
|
95
|
+
sep = options[:separator] || /\s+/
|
96
|
+
sep = Regexp.escape(sep.to_s) unless Regexp === sep
|
97
|
+
if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
|
98
|
+
$1 + (options[:omission] || '...')
|
99
|
+
else
|
100
|
+
dup
|
101
|
+
end
|
102
|
+
end
|
65
103
|
end
|
@@ -31,7 +31,7 @@ class String
|
|
31
31
|
def pluralize(count = nil, locale = :en)
|
32
32
|
locale = count if count.is_a?(Symbol)
|
33
33
|
if count == 1
|
34
|
-
self
|
34
|
+
self.dup
|
35
35
|
else
|
36
36
|
ActiveSupport::Inflector.pluralize(self, locale)
|
37
37
|
end
|
@@ -130,6 +130,8 @@ class String
|
|
130
130
|
#
|
131
131
|
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
|
132
132
|
# 'Inflections'.demodulize # => "Inflections"
|
133
|
+
# '::Inflections'.demodulize # => "Inflections"
|
134
|
+
# ''.demodulize # => ''
|
133
135
|
#
|
134
136
|
# See also +deconstantize+.
|
135
137
|
def demodulize
|
@@ -197,6 +199,7 @@ class String
|
|
197
199
|
# 'employee_salary'.humanize # => "Employee salary"
|
198
200
|
# 'author_id'.humanize # => "Author"
|
199
201
|
# 'author_id'.humanize(capitalize: false) # => "author"
|
202
|
+
# '_id'.humanize # => "Id"
|
200
203
|
def humanize(options = {})
|
201
204
|
ActiveSupport::Inflector.humanize(self, options)
|
202
205
|
end
|