ActiveSupport一時調べ
<h3>Active Supportとは</h3>
<p>Active Supportは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>であり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>言語の拡張、ユーティリティ、その他横断的な作業を担っています。</p>
<p>Active Supportは言語レベルで基本部分を底上げして豊かなものにし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションの開発と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>それ自体の開発に役立てるべく作られています。
(引用:Active Support コア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>)
<a href="https://railsguides.jp/active_support_core_extensions.html">https://railsguides.jp/active_support_core_extensions.html</a></p>
<p>StringやInteger、Date、Datetimeなどのコアなクラスのメソッドを拡張してくれるもの</p>
<hr />
<h3>拡張対象</h3>
<p>Active Supportが拡張する対象を
ざっと挙げてみました(全部ではないです)</p>
<ul>
<li>Object</li>
<li>Module</li>
<li>Class</li>
<li>String</li>
<li>Numeric
<ul>
<li><a class="keyword" href="http://d.hatena.ne.jp/keyword/BigDecimal">BigDecimal</a></li>
<li>Integer</li>
</ul>
</li>
<li>Enumerable</li>
<li>Array</li>
<li>Hash</li>
<li>Range</li>
<li>Date</li>
<li>Time</li>
<li>File</li>
<li>NameError</li>
<li>LoadError</li>
</ul>
<p>...</p>
<hr />
<h3>全てのオブジェクトで使える拡張</h3>
<ul>
<li>Object#blank?</li>
<li>Object#present?</li>
</ul>
<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/ruby">ruby</a>にあるのは<code>nil?</code>と<code>empty?</code>のみ</p>
<pre class="code" data-lang="" data-unlink>nil.nil?
=> true
[].empty?
=> true
''.empty?
=> true</pre>
<hr />
<ul>
<li>Object#blank?</li>
</ul>
<p><code>nil?</code>と<code>empty?</code>の合わせ技</p>
<pre class="code" data-lang="" data-unlink>nil.blank?
=> true
[].blank?
=> true
''.blank?
=> true
nil.blank?
=> true</pre>
<ul>
<li>Object#present?</li>
</ul>
<p><code>!blank?</code>と同じ意味</p>
<pre class="code" data-lang="" data-unlink>nil.present?
=> false
[].present?
=> false
[1,2,3].present?
=> true</pre>
<p>blank?とpresent?の定義は下のURL先
<a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb">https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb</a></p>
<hr />
<ul>
<li>Object#try</li>
</ul>
<p>おなじみtryメソッド。
レシーバが<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>、もしくはメソッドが定義されていない時には<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>を返す。<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>でないなら引数で渡されたメソッドを実行。</p>
<pre class="code" data-lang="" data-unlink>"test".try(:name)
=> nil
"test".try(:size)
=> 4</pre>
<ul>
<li>Object#try!</li>
</ul>
<p><code>try</code>と違う点は、レシーバが<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>なら<code>nil</code>でなく例外を返すところ。</p>
<pre class="code" data-lang="" data-unlink>"test".try!(:name)
NoMethodError: undefined method `name' for "test":String
# NoMethodErrorクラスの例外が発生</pre>
<hr />
<ul>
<li>Object#with_options</li>
</ul>
<p>オプションを一つにまとめられる。
<code>validates</code>メソッドのオプションを一つにまとめたりとかもできると思うよ。</p>
<pre class="code" data-lang="" data-unlink>class Company < ApplicationRecord
...
has_many :contracts, dependent: :destroy
has_one :billing_address, dependent: :destroy
has_many :billings, dependent: :destroy
...
end
↓
class Company < ApplicationRecord
...
with_options dependent: :destroy to |comp|
comp.has_many :contracts
comp.has_one :billing_address
comp.has_many :billings
...
end
</pre>
<hr />
<ul>
<li>tapの補足</li>
</ul>
<p>ブロックを渡した場合には、<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>でない場合のみブロックの評価を行い、tryの返り値はブロックの評価結果となる。</p>
<pre class="code" data-lang="" data-unlink>array
=> [["test1", 1], ["test2", 2]]
array.try do |a|
a.to_h
end
=> {"test1"=>1, "test2"=>2}
array.try do |a|
a.to_h
end.values
=> [1, 2]
# Object#yield_selfも同様の挙動
array.yield_self do |a|
a.to_h
end.values
=> [1, 2]</pre>
<hr />
<h3>Classの拡張</h3>
<h4>class_attribute</h4>
<p>継承可能なクラス属性を定義する。
以下のようなことができるようになる。</p>
<ul>
<li>子クラスで上書きが可能</li>
<li>アクセサメソッドを自動で定義</li>
<li>子クラスで定義されていなければ親クラスの値を参照</li>
</ul>
<p>親クラスの定数を汚染する恐れがない。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>メソッド内からでもアクセスできる。</p>
<pre class="code" data-lang="" data-unlink>class A
class_attribute :x
end
class B < A; end
class C < B; end
A.x = :a
B.x # => :a
C.x # => :a
B.x = :b
A.x # => :a
C.x # => :b
C.x = :c
A.x # => :a
B.x # => :b
# インスタンスからもアクセス・書き換え可能
# インスタンスからクラスのclass_attributeを書き換えることはない
test = B.new
test.x = :t
test.x # => :t
B.x = :b
</pre>
<hr />
<h3>Stringの拡張</h3>
<ul>
<li>活用系</li>
</ul>
<p>レシーバに対して何かしら操作を行う系</p>
<pre class="code" data-lang="" data-unlink>"tomato".pluralize
=> "tomatoes"
"katsui".pluralize
=> "katsuis"</pre>
<hr />
<p>活用系の続き</p>
<pre class="code" data-lang="" data-unlink>"admin_user".camelize
=> "AdminUser"
"admin_user".camelize(:lower)
=> "adminUser"
"account/financial_account".camelize
=> "Account::FinancialAccount"
# camelizeと反対の操作を行うのがunderscore
"Backoffice::UsersController".demodulize
=> "UsersController"
"::UsersController".demodulize
=> "UsersController"</pre>
<hr />
<h3>Numericの拡張</h3>
<ul>
<li>バイト</li>
</ul>
<p>全ての数値が以下のメソッドに対応している。</p>
<pre class="code" data-lang="" data-unlink>bytes
kilobytes
megabytes
gigabytes
terabytes
petabytes
exabytes</pre>
<pre class="code" data-lang="" data-unlink>2.bytes
=> 2
2.kilobytes
=> 2048
2.megabytes
=> 2097152</pre>
<hr />
<ul>
<li>書式設定</li>
</ul>
<p>指定した形式で数値をフォーマットできる</p>
<pre class="code" data-lang="" data-unlink># 電話番号
1234567.to_s(:phone)
=> "123-4567"
1234567891.to_s(:phone)
=> "123-456-7891"
1234567891.to_s(:phone, area_code: true)
=> "(123) 456-7891"
// 桁区切り
12345678.to_s(:delimited)
=> "12,345,678"
pry(main)> 12345678.to_s(:delimited, delimiter: ".")
=> "12.345.678"
// 丸める
111.2345.to_s(:rounded)
=> "111.235"
111.2345.to_s(:rounded, precision: 2)
=> "111.23"
1112903.to_s(:rounded, precision: 3)
=> "1112903.000"</pre>
<hr />
<h3>Enumerableの拡張</h3>
<pre class="code" data-lang="" data-unlink># sum
# 要素の合計値を返す
# 数値でない場合は+を使ってsumを計算する
[2,3,4].sum
=> 9
(1..10).sum
=> 55
%w(rb yml haml).sum
=> "rbymlhaml"
# many?
# collection.size > 1 の短縮系
[1,2,3,4].many?
=> true
# many?ではブロックの評価も行える
# ブロックの場合、評価結果のcollection > 1かどうか判定する
[1,2,3,4].many? { |a| a % 2 == 0 }
=> true
[1,2,3,4].many? { |a| a % 3 == 0 }
=> false
# pluck
# 指定したkeyに基づく配列を返す
[ { id: 1 }, { id: 2 }, { id: 3 } ].pluck(:id)
=> [1, 2, 3]</pre>
<hr />
<h3>Hashの拡張</h3>
<pre class="code" data-lang="" data-unlink># deep_merge
# レシーバと引数に同じkeyが存在し、かつ値がHashの時に下位のHashをmergeする
{a: {b: 1}}.deep_merge({a: {c: 2}})
=> {:a=>{:b=>1, :c=>2}}
# exceptとexcept!
# 引数で指定したkeyがあればレシーバのHashから取り除く
{a: 1, b: 2}.except(:a)
=> {:b=>2}
# stringify_keysとsymbolize_keys
# レシーバのHashのkeyをシンボル→文字列に変換(stringify_keys)
# レシーバのHashのkeyを文字列→シンボルに変換(symbolize_keys)
{nil => nil, 1 => 1, a: :a}.stringify_keys
=> {""=>nil, "1"=>1, "a"=>:a}
{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys
=> {nil=>nil, 1=>1, :a=>"a"}
</pre>
<hr />
<h3>まとめ</h3>
<ul>
<li><p>あなたが実装しようとしているメソッドは、既にActive Supportが提供しているかもしれないので、一旦落ち着いて探しましょう</p></li>
<li><p>比較的Active Supportのドキュメントは読みやすく、発見も色々とあるので読んでみるといいよ!</p></li>
</ul>