ドキュメントを読み込むのは大事、ということでRailsガイドを頭から読んでいく取り組みをしています。 各章ごとに、(Railsガイドにちゃんと書いてあるのに)知らなかった機能を雑にまとめていきます。
今回は、Active Support コア拡張の章です。
deep_dup
リンクはこちら
dup
では、コピーした配列の中身がgsub!
などで変化する場合、コピー元の配列の中身まで影響しますが、deep_dup
では影響を受けないようにできます。
このあたりは意図せぬバグを混入させてしまわないよう、きちんと理解しておく必要があるなと思いました。
try
リンクはこちら
&.
とtry
は似ていますが、存在しないメソッドを指定した際に、前者はNoMethodError
を返し、後者はnil
を返します。
&.
とtry!
は挙動自体は同じようです。
ただし、try!
は&.
と比べると遅いようなので、使うメリットがあまりなさそうにも思います。
メソッドが存在しないケースにnil
を返したいならtry
で、エラーを返したいなら&.
を使うのが良いのかなと思いました。
コード | 戻り値 |
---|---|
object.try(:not_exist_method) | nil |
object.try!(:not_exist_method) | NoMethodError |
object&.not_exist_method | NoMethodError |
acts_like?でオブジェクトが特定の型のように振る舞うかどうかを判断する
リンクはこちら
acts_like?
で、そのオブジェクトが特定のクラスのように振る舞うかを確認できます。
DateTime.new.acts_like?(:time) #=> true
クラスにacts_like_xxx?
を定義することで、acts_like?(:xxx)
でtrueを返すようにできます。
class Dummy def acts_like_string?; end # メソッドの中身は関係ない(定義されているかどうか) end Dummy.new.acts_like?(:string) #=> true
to_param
リンクはこちら
ActiveRecordのインスタンスにto_param
を使うと、デフォルトでは:id
の値を返すようです。
book = Book.first #=> <id: 2, title: "本1",content: "内容1",...> book.to_param #=> "2"
配列に対してto_param
すると、/
でjoinするのも知らなかったです。
[book, false, nil, true].to_param #=> "2/false//true"
to_query
リンクはこちら
引数をキーとし、レシーバにto_param
したものを値としたクエリ文字列を返してくれます。
知ってたらスマートに書けそうな場面もありそうです。
book.to_query(:book) # Railsガイドの例では引数は文字列でしたが、hashでもOKでした。 #=> "book=2" true.to_query('admin') #=> "admin=true"
rubyのto_jsonとrailsのto_json
リンクはこちら
rubyのjson
ライブラリでのto_json
と、ActiveSupportの提供するto_json
は結構異なるようです。
例えば、only
などのオプションはrubyでは使えません。
恥ずかしながら、あまり違いを意識していなかったので、勉強になりました。
- irb環境で実行
hash = { a: 1, b: 2 } #=> {:a=>1, :b=>2} hash.to_json(only: :a) #=> "{\"a\":1,\"b\":2}"
- rails consoleで実行
hash = { a: 1, b:2 } #=> {:a=>1, :b=>2} irb(main):003> hash.to_json(only: :a) #=> "{\"a\":1}
警告や例外の抑制
リンクはこちら
silence_warnings, enable_warnings
特に設定しないと$VERBOSE
はfalse
となっており、重要な警告のみ出力します。
silence_warnings
を使うと、ブロック内のコードでは警告が発生しなくなります。($VERBOSE
にnil
が設定される)
silence_warnings do CONSTANT = 'constant' CONSTANT = 'CONSTANT' end # 特に警告は表示されない
enable_warnings
のブロック内では、$VERBOSE
はtrue
になり、すべての警告が表示されるようになります。
suppress
suppress
のブロック内では、引数に指定した例外の範囲であれば例外を出さないようにできるようです。
suppress(TypeError) do 1 + nil p 'abc' end # 例外は発生しないが、次の行が実行されるわけでもない
suppress(NameError) do 1 + nil end #=> TypeError # 指定した例外と異なるので例外が発生する
attr_internal
リンクはこちら
attr_internal
を使ってライブラリのサブクラスとの名前の衝突を避ける工夫があるようです。
これを使って定義した属性値は、_
から始まるインスタンス変数に保管されます。
class Parent attr_internal :dummy def initialize(str) self.dummy = str end end parent = Parent.new('abc') #=> <Parent: @_dummy="abc">
module_parent, module_parent_name, module_parents
リンクはこちら
module_parent
はModuleのクラスを返し、module_parent_name
はstringを返します。
module_parents
はObject
クラスまで遡って、クラスの配列を返します。
X::Y::Z
のmoduleに対しての戻り値
method | 戻り値 |
---|---|
module_parent | X::Y |
module_parent_name | 'X::Y' |
module_parents | [X::Y, X, Object] |
anonymous?
リンクはこちら
無名モジュールに対してはtrue
を返すとのこと。
delegate_missing_to
リンクはこちら
ごそっとdelegate
できる機能のようです。
丸ごとdelegate
しなくてはいけないケースは設計上多くはなさそうですが、使えばスッキリかけますね。
redefine_method
リンクはこちら
すでに定義したいメソッドと同じ名前のメソッドが存在している場合にdefine_method
だと警告を出す*ところ、redefind_method
は名前の通り再定義を考慮されたメソッドなので、警告は発生しません。
- デフォルトの重要な警告のみを表示する
$VERBOSE = false
の状態だと、出ない警告のようです。
class Sample def hello 'original hello' end end enable_warnings do # ブロック内はすべての警告を表示($VERBOSE = true) Sample.define_method(:hello) do 'define hello' end end #=> warning: method redefined; discarding old hello #=> warning: previous definition of hello was here #=> 上書き自体はされるが、警告が表示される enable_warnings do # ブロック内はすべての警告を表示($VERBOSE = true) Sample.redefine_method(:hello) do 'redefine hello' end end #=> 警告は表示されず、上書きされる。
class_attribute
リンクはこちら
class_attribute
はざっくり知っていたものの、細かいオプションや知らない仕様もあったので勉強になりました。
class A class_attribute :x end A.x = 'A' A.x #=> "A" A.new.x #=> "A"
インスタンスで使えなくするには、instance_reader: false
, instance_write: false
, instance_predicate: false
を使用可能。
?
のメソッドも使えるようになります。
A.x = false A.x? #=> false
Stringの拡張
リンクはこちら
rawヘルパーとhtml_safe
html_safe
を使うより、raw
を使いましょうとのこと。
理由は書かれていないですが、raw
であれば必ず最後のviewで使われるので、使いたい場面以外では使われにくいというメリットがありそうです。
remove
gsub(/xxx/, '')
と同じような効果があります。(全く同じ、、?)
remove
だとメソッド名が明確なのでgsub
を使って正規表現に一致する文字列を削除するよりも良いかもしれません。
truncate_words
truncate
は知っていましたが、単語数で区切れるこちらのメソッドは知らなかったです。
'Hello, I am dummy user'.truncate_words(3) #=> "Hello, I am..." 'Hello, I am dummy user'.truncate_words(4) #=> "Hello, I am dummy..."
inquiry
文字列をStringInquirer
オブジェクトに変換するメソッド。
StringInquirer
オブジェクトというのをよく理解してなかったんですが、== 'string'
をスマートにかけるオブジェクトのようです。
str = 'abc'.inquiry str.abc? #=> true
Rails.env
もこのオブジェクトの子クラスになっているのも知りました。
Rails.env.class #=> ActiveSupport::EnvironmentInquirer Rails.env.class.superclass #=> ActiveSupport::StringInquirer
strip_heredoc
ヒアドキュメントのインデントを削除するメソッドです。
コピペで文章を貼り付けた際にできてしまうインデントを削除するとか?あまりユースケースは思いつかないですが、面白いメソッドだなと思いました。
indent
というインデントを追加するメソッドもあるようです。
at
rubyでは、配列でindexで要素を取得する際に使えますね。
rubyでは[]
は使えるものの、at
は使えないので、それが揃ったイメージかなと思いました。
from, to
指定したindexの位置から、もしくは位置までの文字列を返してくれます。
自分は範囲の書き方の方('abcde'[1..]
)がわかりやすいと思う派ですが、こっちの方が慣れれば見やすいかもですね。
first, last
名前通り、最初の文字や最後の文字を取得できるメソッドです。これは[0]
とか[-1]
とかやるより遥かにわかりやすいですね。
Timeの拡張(fortnights)
リンクはこちら
fortnights
という単語の意味を知らなかったのですが、「2週間」という意味なんですね。
2.fortnights #=> 6weeks
Integerの拡張(multiple_of?)
リンクはこちら
引数の倍数かどうかを判定してくれます。
余りが0か?といったロジックよりスマートになりますね。
15.multiple_of?(5) # true 0.multiple_of?(5) # true
index_by
リンクはこちら
ブロックで実行された値をキーとしたハッシュを返してくれます。
Book.all.index_by(&:id) #=> { 1=> bookオブジェクト1, 2=> bookオブジェクト2 } #=> 上記はHash
index_with
リンクはこちら
index_byとは逆に、ブロックで実行された値をvalueとするハッシュを返してくれます。
Book.all.index_with(&:id) #=> { bookオブジェクト1 => 1, bookオブジェクト2=> 2 } #=> 上記はHash
rubyのwith_index
と混乱しそうな気もしますね(英語をきちんと考えれば明らかですが)。
including, excluding
リンクはこちら
引数をレシーバに結合して配列を返してくれるようです。
配列で使うと+
と同じような感じですが、hashがレシーバだと、レシーバの方は配列にされました。
[1,2].including(1,3) #=> [1,2,1,3] { a: 1 }.including({b: 2}) #=> [[:a, 1], {:b=>2}]
excluding
は逆です。
pick
リンクはこちら
配列等から、最初の要素のうち引数にしていたキーの値を取得します。
SQLを見る限り、ORDER BY
を使っていないのでそこは注意が必要かなと思いました。
Book.pick(:title) # SELECT "books"."title" FROM "books" LIMIT $1 [["LIMIT", 1]] # '本1'
Arrayの拡張
リンクはこちら
second_to_last
英語名をパッと見た感じだと、2番目の要素から最後までの要素(=[1..]
)と同じかな?と思ったのですが、違いました。
正しくは末尾(last)から2番目の要素を返す([-2]
)でした。
[1,2,3,4].second_to_last #=> 3
extract!
一見select
の逆かな?と思ったのですが、違いました。
戻り値としては、ブロックを評価してtrue
を返したものの配列である一方、レシーバ自体は左記の配列を削除したものに変更されます。
array = [1,2,3,4] #=> [1, 2, 3, 4] array.extract! { _1.odd? } #=> [1, 3] array #=> [2, 4]
extract_options!
配列の最後の要素がhashであればそれを戻り値では返しつつ、レシーバはそのhashを取り除いたものに変更します。
ary = ['a', 'b', c: '1', d: '2'] #=> ["a", "b", {:c=>"1", :d=>"2"}] ary.extract_options! #=> {:c=>"1", :d=>"2"} ary #=> ["a", "b"] ary = ['a', 'b'] #=> ["a", "b"] ary.extract_options! #=> {}
to_sentence
配列を言語ごとにいい感じに結合した文字列にしてくれます。
I18n.with_locale(:en) do ['Apple', 'Orange', 'Grape'].to_sentence end #=> "Apple, Orange, and Grape"
ja: support: array: words_connector: "、" two_words_connector: "と" last_word_connector: "そして"
I18n.with_locale(:ja) do ['Apple', 'Orange', 'Grape'].to_sentence end #=> "Apple、OrangeそしてGrape"
wrap
概ねArray()
と同じ挙動をしますが、細かいところで異なるので違いは意識しておく必要がありそうですね。
in_groups_of
配列を何個かずつ取り出すときにとても便利そうです。
オプションも豊富で使い勝手もいいですね。
[1,2,3,4,5].in_groups_of(2) #=> [[1, 2], [3, 4], [5, nil]] [1,2,3,4,5].in_groups_of(2, 'dummy') #=> [[1, 2], [3, 4], [5, "dummy"]] [1,2,3,4,5].in_groups_of(2, false) #=> [[1, 2], [3, 4], [5]]
in_groups
in_groups_of
は引数の個数ごとに配列を作る、でしたが、こちらは引数の個数に配列を分ける、です。
%w(1 2 3 4 5 6 7 8 9 10).in_groups(2) #=> [["1", "2", "3", "4", "5"], ["6", "7", "8", "9", "10"]]
split
引数をセパレータとして配列を分割します。
['Yamada', 'Ito', nil, 'Suzuki', nil, 'Sato'].split #=> [["Yamada", "Ito"], ["Suzuki"], ["Sato"]]
Hashの拡張
リンクはこちら
reverse_merge
引数の方の先頭に持ってきてくれるメソッドです。merge
はよく使いますが、reverse_
もあるんですね。
{ a: 1 }.reverse_merge({ b: 2 }) #=> {:b=>2, :a=>1}
assert_valid_keys
レシーバのhashが引数に渡されたものをキーにもつなら値を、持たない場合はArgumentError
を返します。
deep_transform_values
ハッシュがネストされていても一律値をブロックの中身で処理できます。かなり便利に使えるケースがありそうです。
nested_hash = { a: 1, b: { b_a: 2, b_b: { c_a: 3, c_b: 4 } } } nested_hash.deep_transform_values { _1 * 2 } #=> {:a=>2, :b=>{:b_a=>4, :b_b=>{:c_a=>6, :c_b=>8}}}
missing_name?
リンクはこちら
引数の名前が原因でエラーが発生したかどうかを判定してくれます。
begin NoExistClass.some_method rescue NameError => e if e.missing_name?("NoExistClass") puts "NoExistClass はないです" end end #=> NoExistClass はないです