Repository navigation
Facets and ActiveSupport define many of the same methods, and whichever
loads last replaces the other's. This release makes a set of those methods
behave the same in both libraries, so it no longer matters which one wins.
Facets now matches ActiveSupport 8.1 in these cases, which fixes bugs in
Rails apps that load Facets, and adds the extra options ActiveSupport's
versions take.
It also stops Facets from changing what Ruby's own methods do. Under a new
rule, now in CONTRIBUTING.md, a Facets method may share a name with one of
Ruby's only if it gives Ruby's results and adds something useful. Otherwise
Facets gets out of Ruby's way.
Changes:
-
Bug Fixes
Hash#count, loaded byrequire 'facets', gives Ruby's own answers
again. Facets' version counted values rather than key-value pairs, which
changed Ruby's answer for every library in the program:
{a: 1}.count([:a, 1])returned 0. To count values, use
hash.values.count(value). Until 2027-09-30,Hash#countwarns when
given an argument that can't be a key-value pair, which is how Facets'
version was called.Array#shelljoinfromfacets/shellwordsis Ruby's own again. Facets'
version flattened nested arrays and turned hashes into options, so a
value meant as one argument could become several. A web request's
parameter can be an array (?term[]=&term[]=/home), and with it
['grep', '-r', '--', term, 'public/'].shelljoingave
grep -r -- '' /home public/, which prints every file under/home.
Ruby's escapes each element as one argument. To turn hashes into
options, usearray.shellwords.shelljoin. Until 2027-09-30,
Array#shelljoinwarns when the array has a hash or an array in it.Hash#shellwords, and soHash#shelljoinandArray#shellwords, no
longer put quote characters around values. Once the words were escaped
withshelljoin, the quotes became part of the value:
{name: 'jim'}.shelljoinpassed"jim", quotes included. Each flag and
its value is now one plain word,--name=jim.require 'facets'loadsString#trim. WhenString#marginwas renamed
to#trimin 3.0, the new file was never added to the core aggregator,
sotrimworked only with its own require.Enumerable#deferwith a block works again. Since 3.2 deprecated it,
deferignored its block, so
(1..10).defer { |out, i| out << i if i.even? }.first(5)returned
[1, 2, 3, 4, 5]instead of[2, 4, 6, 8, 10]. It is still
deprecated; the block form isEnumerator::Lazy.new(enum) { ... }.Range#overlap?on Ruby 3.1 and 3.2, where Ruby has no version of its
own, gives the same answers as Ruby's. Facets' version said an empty
range like3...3overlapped any range containing 3, raised RangeError
for(3..).overlap?(..5), and raised NoMethodError rather than
TypeError for a non-Range argument. It now follows Ruby's algorithm.Time.currentuses ActiveSupport'sTime.zonewhen it is set. Before,
when Facets loaded after ActiveSupport,Time.currentignored the Rails
app's time zone and returned the system's local time.Time#changekeeps the time's UTC offset or time zone. Before, a time
such as15:30 +09:00changed to the system's local time zone. It also
keeps fractions of a microsecond.- Facets' deprecated
Time#to_time, used only whendateisn't loaded,
returnsself, as Ruby's own does. Before, it converted to local time. String#blank?andString#squishtreat Unicode whitespace, such as a
non-breaking space, as whitespace.String#blank?also works for strings
in encodings such as UTF-16.Class#descendantsno longer raisesArgumentErrorwhen ActiveSupport
is loaded. Calling it with a number of generations still does, because
ActiveSupport wraps the method in one that takes no argument.Integer#ordinaland#ordinalizegive"-1st"for -1, not"-1th".String#unindentwith no argument removes only the least indented
line's indentation from each line. Before, it removed more from the more
indented lines, so its own example,"ohaie\n wurld", came out as
"ohaie\nwurld". It also raisedNoMethodErrorfor an empty string.String#indentwith a negative number and an indent string of more than
one character removes only whole copies of that string.require 'facets/fileutils/ln_r'replaced Ruby'sFileUtils.link_entry
and an internal method it uses with old copies, so Ruby's own
FileUtils.cp_lrraisedNameError.FileUtils.ln_rnow uses Ruby's
link_entry, and the file requiresfileutilsitself.Pathname#globfromfacets/pathname/globignored a block; it now gives
the block each match, as Ruby's does. With symbol flags, such as
:dotmatch, it now uses Ruby's glob too, so it works in a directory
whose name has glob characters such as[, and returns paths in the
same form as it does without them. The file requirespathnameitself.Kernel#extendwith no arguments and no block raisesArgumentError,
as Ruby's does; before, it returned the object.ruby -wno longer warns
thatextendwas redefined.require 'facets/digest'raisedLoadError, because it required a file
that had moved.#salted_digest's automatic salt now comes from
SecureRandominstead ofrand.facets/zlibno longer defines its own copies of Ruby'sZlib.inflate
andZlib.deflate. It now requireszlibandstringioitself;
before,Zlib.compressraisedNameErrorunless they were already
loaded.OpenStruct#initializefromfacets/ostructcalls Ruby's own
initializefor the hash, instead of a copy of it, andruby -wno
longer warns that it was redefined.
-
Additions
Time#changetakesnsec:andoffset:.String#removeand#remove!take several patterns.Hash#deep_mergeand#deep_merge!take a block, asHash#mergedoes,
to decide the value of a key both hashes have.Array#splittakes a block instead of a pattern, and splits onnil
when given neither.String#indentand#indent!take a third argument; when true, empty
lines are indented too.
-
Deprecations
Kernel#extendwith a block, which Facets has added since 2.8.0, now
warns, and will be removed after 2027-09-30. It's the only reason Facets
overrides Ruby'sextend, which everyextendcall in a program then
goes through. Useobj.extend(Module.new { ... })instead. Without
Facets, Ruby raises if given only a block, and ignores a block given
with modules.
-
Changes that can affect existing code
Hash#count(value)andArray#shelljoinno longer do what Facets'
versions did; see Bug Fixes. Both warn until 2027-09-30 when called
the old way.Hash#shellwordsgives--name=jimrather than--name="jim". Code
that joined the words with spaces into a shell command should use
shelljoininstead.Array#splitkeeps an empty piece where a match is at either end:
[2, 1, 2].split(2)is[[], [1], []], and[].split(2)is[[]].
Before, empty pieces at the end were dropped.String#indentleaves empty lines unchanged unless asked. Without an
indent string, it indents with the string's own indentation character,
so a tab-indented string gets more tabs.String#remove!returns the string even when nothing was removed.
Before, it returnednilthen.Hash#deep_merge!replaces a sub-hash with a merged copy instead of
changing it, so other references to the sub-hash no longer see the
change.Class#descendantslists all subclasses before their own descendants.
On Ruby 4.1, which adds its ownModule#descendants, a call without a
number of generations is passed to Ruby's, so it gives Ruby's list, in
Ruby's order. Onlydescendants(n)is still Facets' own there.