最近の ActiveSupport (7.1.1) の便利機能
フロントエンドの荒波から生還したところ、しばらく見てなかった ActiveSupport がまた便利になっていたので、気づいた点を簡単にまとめた。昔からあったけど知らなかっただけなのもたぶんある。
EnvironmentInquirer#local?
ActiveSupport::EnvironmentInquirer.new("development").local? # => true
あっちこっちで if Rails.env.development? || Rails.env.test?
を書いている自分向け。if Rails.env.local?
だけで済むようになる。
BroadcastLogger
logger = ActiveSupport::BroadcastLogger.new
logger.broadcast_to(Logger.new("a.log"))
logger.broadcast_to(Logger.new("b.log"))
logger.info("foo")
一箇所から複数の logger に投げる。
Object#with
ブロック内で一時的にメンバを変更する。
class User
attr_accessor :name
end
user = User.new
user.name = "alice"
user.with(name: "bob") do
user.name # => "bob"
end
user.name # => "alice"
public なメソッド name=
に反応できないといけない。
Pathname#existence
ファイルが存在しなければ nil を返すので、
file = Pathname("file.txt")
if file.exist?
file.delete
end
は、
Pathname("file.txt").existence&.delete
と書ける。
Pathname("file.txt").delete rescue nil
と書くのに抵抗がある場合の代替に使いたい。
Pathname#blank?
Pathname(" ").blank? # => false
ファイル名には空白も含まれるのだから空白だけのファイル名は空ではないということのようだ。
Range#include?
(1..4).include?(2..3) # => true
引数が Range の場合に賢くなる。
数値の範囲の意味合いを持つ cover? なら元々 true なので include? ではなく cover? を使えばいいんじゃないかという気はする。
Regexp#multiline?
//m.multiline? # => true
m オプションをつけていたかすぐわかる。
ActiveSupport がない場合は、
//m.options.allbits?(Regexp::MULTILINE) # => true
と書く。
SecureRandom.base36
["0", "O", "I", "l"]
を除いたランダム文字列を作る。
SecureRandom.base36 # => "zw6e66f3b6xkyt5d"
SecureRandom.base58 # => "WH7QUHj9cc7BS4xG"
base58
のほうは大文字を含む。
日本人が ツ と シ や ソ と ン を見分けられるように英語圏の人たちは 0 と O や I と l を平然と見分けられるのだと思っていたが、そうでもないらしい。
String#upcase_first
"aBc".upcase_first # => "ABc"
"ABc".downcase_first # => "aBc"
capitalize と似ているけど変更するのは先頭の文字のみ。
ERB::Util.html_escape_once
ERB::Util.html_escape_once("&") # => "&"
ERB::Util.html_escape_once("&") # => "&"
何度呼んでも &
がエスケープされ続けない。
delegate が速くなっていた
class C1
def self.x = nil
delegate :x, to: :class
end
class C2
delegate :x, to: :class
def self.x = nil
end
c1 = C1.new
c2 = C2.new
"%.2f ms" % Benchmark.ms { 1000000.times { c1.x } } # => "84.50 ms"
"%.2f ms" % Benchmark.ms { 1000000.times { c2.x } } # => "120.13 ms"
引数がないかつ delegate した時点でメソッドを見つけられる場合は速くなる。
TimeWithZone#next_day?
Time.now.next_day? # => false
Time.now.prev_day? # => false
tomorrow? と yesterday? のエイリアス。英語になると明日と昨日がわからなくなる自分向け。
ActiveSupport::Duration#inspect
ActiveSupport::Duration.build(10000) # => 2 hours, 46 minutes, and 40 seconds
さくっと人間向け表記に変換する。
ActiveSupport::Duration#parts
ActiveSupport::Duration.build(10000).parts # => {:hours=>2, :minutes=>46, :seconds=>40}
自力で表記したいとき用。
expires_in をブロック内で変更できるようになっていた
cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
cache.fetch("foo") do |name, options|
name # => "foo"
options.expires_in # => 5 minutes
options.expires_in = 1.minutes
true
end
キャッシュ対象によって expires_in が動的に変わる場合にブロック内で最初に1回設定するだけでよくなる。
Enumerable#maximum
c = Data.define(:foo)
a = [c.new(5), c.new(6)]
としたとき
a.collect(&:foo).max # => 6
が、
a.maximum(:foo) # => 6
と書ける。minimum もある。
Enumerable#index_by
c = Data.define(:foo)
a = [c.new(5), c.new(6)]
としたとき
a.inject({}) { |a, e| a.merge(e.foo => e) } # => {5=>#<data foo=5>, 6=>#<data foo=6>}
が、
a.index_by(&:foo) # => {5=>#<data foo=5>, 6=>#<data foo=6>}
と書ける。
Enumerable#index_with
[:a, :b].inject({}) { |a, e| a.merge(e => 1) } # => {:a=>1, :b=>1}
[:a, :b].inject({}) { |a, e| a.merge(e => e.next) } # => {:a=>:b, :b=>:c}
が、
[:a, :b].index_with(1) # => {:a=>1, :b=>1}
[:a, :b].index_with(&:next) # => {:a=>:b, :b=>:c}
と書ける。
Enumerable#sole
[:a].sole # => :a
[:a, :b].sole rescue $! # => #<Enumerable::SoleItemExpectedError: multiple items found>
1つしかないところから1つ参照するのを保証する。
Object#instance_values
class C
attr_accessor :a, :b
end
c = C.new
c.a = 1
c.b = 2
c.instance_values # => {"a"=>1, "b"=>2}
ActiveRecord の attributes のような機能。
Object#presence_in
1.presence_in([1, 2]) # => 1
1.presence_in([2, 3]) # => nil
in? なら self で偽なら nil を返す。presence_in?
ではないので注意。
応用すると x が 1 か 2 でなければ 1 としたいときなど簡単に書ける。
x = 3
x = x.presence_in([1, 2]) || 1
x # => 1
Discussion