Weitere ähnliche Inhalte Ähnlich wie Active Support のコア拡張機能について (20) Mehr von Tomoya Kawanishi (20) Kürzlich hochgeladen (12) Active Support のコア拡張機能について2. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
1自己紹介
Tomoya Kawanishi a.k.a. @cuzic
エネチェンジ株式会社 チーフエンジニア
電力会社、ガス会社を切り替えるなら、エネチェンジ経由で!
一般家庭も!法人も!
Ruby関西の中の人
発表者として登壇くださる方、あとで声かけください。
第84回 Ruby関西勉強会 12月1日(土)
大手町.rb の中の人
毎月 大手町.rb の開催を予定
東京駅、各線大手町駅から直結!
Ruby の初級者がメインターゲット
3. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Disclaimer
大手町.rb は祝!10回!
人数がとても増えてきました!
大手町.rb は(比較的)初級者向けの勉強会
継続するためには2択!
同じネタをリピート!
初級者向けというポジションを維持
少しずつ難易度を上げていく!
いま、来てくれているオーディエンスとともに成長していく!
大手町.rb は今のオーディエンスを大切にし、
ともに成長していく道を選ぶことにしました!
2
4. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
今日のテーマ
Active Support のコア拡張機能について
Ruby on Rails を使っていて、
意識しているか、していないかに関わらず
みんな使っています
とはいえ、時間をとって学ぶことがあまりない
というのも事実
今日は一緒に学んでいきましょう
3
5. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#blank? と Object#present?
Ruby で 偽 なものは
nil と false 以外のすべて
ゼロ、空文字、空配列も true
present? を使うと、空文字、空配列は偽になる
4
if 0
"OK"
end
#=> "OK"
if ""
"OK"
end
#=> "OK"
if []
"OK"
end
#=> "OK"
if {}
"OK"
end
#=> "OK"
if nil
"OK"
end
#=> nil
if 0.present?
"OK"
end
#=> "OK"
if "".present?
"OK"
end
#=> nil
if [].present?
"OK"
end
#=> nil
if {}.present?
"OK"
end
#=> nil
if nil.present?
"OK"
end
#=> nil
6. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#presence
x.presence は x.present? ? x : nil と同じ意味
x.presence&.split(",")&.last のように &. (ぼっち演算子)と
組合せたり || と組合わせて使うとベンリ
5
if Rails.env.development?
email = `git config --get user.email`.chomp
email = email.presence || 'example@example.com'
name = `git config --get user.name`.chomp
name = name.presence || 'Naohiro Sakuma'
InternalUser::Admin.find_or_create_by(email: email) do |u|
u.name = name
end
end
7. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#try 、Object#try! (1/3)
nil の場合は何もしない
nil でない場合、引数の名前のメソッドを呼び出す
6
# tryメソッドを使用しない例1
unless @number.nil?
@number.next
end
# try メソッドを使用しない例2
!@number.nil? && @number.next
@number && @number.next
# tryメソッドを使用した場合
@number.try(:next)
# &. 演算子を使用した場合
@number&.next
8. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#try 、Object#try! (2/3)
メソッドがない場合、 nil を返す
メソッドがない場合、例外にしたいときは
try! を使う
7
def log_info(sql, name, ms)
# @logger.debug? メソッドが存在して、かつ true を返す場合だけ以下を実行する
# @logger.debug? がない場合もエラーにならない
if @logger.try(:debug?)
name = '%s (%.1fms)' % [name || 'SQL', ms]
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
@number.try(:nest) # => nil
@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer
9. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#try 、Object#try! (3/3)
ブロックを渡すこともできる。この場合、
nil でない場合にのみブロックを実行する
この場合、try の返り値は ブロックの評価結果
Ruby 2.5 以降の Object#yield_self 的な使い方もできる
8
@person.try { |p|
"#{p.first_name} #{p.last_name}"
}
[["a", 1], ["b", 2]].try do |array|
array.to_h
end
=> {"a"=>1, "b"=>2}
[["a", 1], ["b", 2]].yield_self do |array|
array.to_h
end
=> {"a"=>1, "b"=>2}
10. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#deep_dup
配列、ハッシュの要素も含めて複製する
配列やハッシュでない場合は、dup と同じ結果を返す
配列やハッシュなら、ディープコピーした結果を返す
ハッシュの場合は、キーも値もディープコピーする
9
array = ['str']
duplicate = array.dup
duplicate[0].gsub!('str', 'foo')
# 要素は複製されていない。
# 一方を変更するとどちらも変更される
array # => ['foo']
duplicate # => ['foo']
array = ['str']
duplicate = array.deep_dup
duplicate[0].gsub!('str', 'foo')
# 要素も複製されている。
# 元の配列は変更されない
array # => ['str']
duplicate # => ['foo']
11. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#to_query
to_query はクエリストリングを生成するのに使える
特に Hash#to_query はクエリストリングを生成する
ときにベンリ
10
# このメソッドは、キーと値のいずれについても、必要な箇所をすべてエスケープします。
account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"
# 配列にto_queryメソッドを適用した場合、to_queryを配列の各要素に適用して
# key[]をキーとして追加し、それらを"&"で連結したものを返します。
[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
# ハッシュもto_queryに応答しますが、異なるシグネチャを使用します。
# メソッドに引数が渡されない場合、このメソッド呼び出しは、一連のキー/値ペアを
# ソート済みの形で生成し、それぞれの値に対してto_query(key)を呼び出します。
# 続いて結果を"&"で連結します。
{c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3"
12. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Object#with_options
何度も同じ引数が出てきて、ウザいのが、、、
with_options を使うことでまとめられる
11
class Account < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :products, dependent: :destroy
has_many :invoices, dependent: :destroy
has_many :expenses, dependent: :destroy
end
class Account < ApplicationRecord
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
assoc.has_many :invoices
assoc.has_many :expenses
end
end
13. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Module の拡張: delegate
delegate を使うとメソッドを簡単に委譲できる。
複数の引数をとることで、複数の属性を一度に移譲できる
12
class User < ApplicationRecord
has_one :profile
def name
profile.name
end
end
class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
end
delegate :name, :age, :address, :twitter, to: :profile
# 定数 Rails に委譲する
delegate :logger, to: :Rails
# レシーバのクラスに委譲する
delegate :table_name, to: :class
# profile が nil のとき、例外を raise せず nil を返す
delegate :name, to: :profile, allow_nil: true
14. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Class の拡張: class_attribute
継承可能なクラス属性を定義
https://qiita.com/cuzic/items/ffd115f1e17458020b1b
13
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
15. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Class の拡張: cattr_reader、cattr_writer、cattr_accessor
cattr_accessor でクラス変数へのアクセサを定義可能
cattr_reader でリーダだけを定義
cattr_writer でライタだけを定義
14
class MysqlAdapter < AbstractAdapter
# @@emulate_booleansにアクセスできるクラスメソッドを生成する
cattr_accessor :emulate_booleans, default: true
end
module A
class B
# first_name readerは生成されない
cattr_accessor :first_name, instance_reader: false
# last_name= writerは生成されない
cattr_accessor :last_name, instance_writer: false
# reader(surname)も writer(surname=) も生成されない
cattr_accessor :surname, instance_accessor: false
end
end
16. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
String 拡張: 活用形 1/3 15
"table".pluralize # => "tables"
"ruby".pluralize # => "rubies"
"equipment".pluralize # => "equipment"
"tables".singularize # => "table"
"rubies".singularize # => "ruby"
"equipment".singularize # => "equipment"
"product".camelize # => "Product"
"admin_user".camelize # => "AdminUser"
"Product".underscore # => "product"
"AdminUser".underscore # => "admin_user"
"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize # => "Fermat's Enigma"
17. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
String 拡張: 活用形 2/3 16
"name".dasherize # => "name"
"contact_data".dasherize # => "contact-data"
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize # => "Inflections"
"".demodulize # => ""
"Product".deconstantize # => ""
"Backoffice::UsersController".deconstantize # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"
"Person".tableize # => "people"
"Invoice".tableize # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"
18. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
String拡張:活用形 3/3 17
"people".classify # => "Person"
"invoices".classify # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"
"Fixnum".constantize # => Fixnum
"name".humanize # => "Name"
"author_id".humanize # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize # => "Comments count"
"_id".humanize # => "Id"
"User".foreign_key # => "user_id"
"InvoiceLine".foreign_key # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"
19. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
String拡張 to_date, to_time, to_datetime
to_date で Date 型
to_time :Time型
to_datetime : DateTime 型
Time.zone.parse よりも文字数が少なく便利
18
"2010-07-27".to_date # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
"2010-07-27 23:42:00".to_time(:utc) # => 2010-07-27 23:42:00 UTC
"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200
# デフォルトは :local
20. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Numeric の拡張: フォーマッティング 1/3 19
Currency:
1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
-1234567890.50.to_s(:currency, negative_format: '(%u%n)')
# => "($1,234,567,890.50)"
1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '')
# => "£1234567890,50"
1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '',
format: '%n %u')
# => "1234567890,50 £"
Percentage:
100.to_s(:percentage) # => "100.000%"
100.to_s(:percentage, precision: 0) # => "100%"
1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
100.to_s(:percentage, format: '%n %') # => "100.000 %"
21. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Numeric の拡張: フォーマッティング 2/3 20
Delimited:
12345678.to_s(:delimited) # => "12,345,678"
12345678.05.to_s(:delimited) # => "12,345,678.05"
12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
# => "98 765 432,98"
Rounded:
111.2345.to_s(:rounded) # => "111.235"
111.2345.to_s(:rounded, precision: 2) # => "111.23"
13.to_s(:rounded, precision: 5) # => "13.00000"
389.32314.to_s(:rounded, precision: 0) # => "389"
111.2345.to_s(:rounded, significant: true) # => "111"
111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
111.234.to_s(:rounded, locale: :fr) # => "111,234"
13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
# => "13"
389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
# => "1.111,23"
22. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Numeric の拡張: フォーマッティング 3/3 21
Human-friendly size in Bytes:
123.to_s(:human_size) # => "123 Bytes"
1234.to_s(:human_size) # => "1.21 KB"
1234567.to_s(:human_size) # => "1.18 MB"
1234567890.to_s(:human_size) # => "1.15 GB"
1234567890123.to_s(:human_size) # => "1.12 TB"
1234567890123456.to_s(:human_size) # => "1.1 PB"
1234567890123456789.to_s(:human_size) # => "1.07 EB"
1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
483989.to_s(:human_size, precision: 2) # => "470 KB"
1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
524288000.to_s(:human_size, precision: 5) # => "500 MB"
Human-friendly format:
123.to_s(:human) # => "123"
1234.to_s(:human) # => "1.23 Thousand"
1234567.to_s(:human) # => "1.23 Million"
1234567890.to_s(:human) # => "1.23 Billion"
1234567890123.to_s(:human) # => "1.23 Trillion"
1234567890123456.to_s(:human) # => "1.23 Quadrillion"
489939.to_s(:human, precision: 2) # => "490 Thousand"
489939.to_s(:human, precision: 4) # => "489.9 Thousand"
1234567.to_s(:human, precision: 4,
significant: false) # => "1.2346 Million"
1234567.to_s(:human, precision: 1, separator: ',',
significant: false) # => "1,2 Million"
23. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Enumerable の拡張: sum
Enumerable#sum : 合計値を計算
空配列の場合のデフォルト値も指定できる。
数値でなくても、 + メソッドを使って sum を計算
22
[1, 2, 3].sum # => 6
(1..100).sum # => 5050
[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum # => "foobarbaz"
{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1]
[].sum(1) {|n| n**3} # => 1
(1..3).sum{|n| n**3} #=> 36
(1..3).sum(1){|n| n**3} #=> 36
24. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Enumerable の拡張: 抽出
exclude? で要素が含まれていないときの判定ができる
without を使うと、指定した要素を除外できる
pluck メソッドで、指定したキーに基づく配列を返す
23
# exclude? は、include? の逆の動作
to_visit << node if visited.exclude?(node)
# without
["David", "Rafael", "Aaron", "Todd"].without("Aaron", "Todd") # => ["David", "Rafael"]
# pluck
[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David",
"Rafael", "Aaron"]
25. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Hash の拡張: 抽出
Hash#reverse_merge はデフォルト設定のときに便利
except で不要な要素の除外、slice で必要な要素の抽出ができる
extract は、ハッシュから取り除いた上でそのキーのペアを返す
compact を使うと値が nil の要素を除外できる
24
# デフォルト値が左側で、右側がカスタマイズする引数
# 同じキーがあれば、右側が優先される
options = {length: 30, omission: "..."}.merge(options)
# reverse_merge を使うとデフォルト値が右側になり、見やすい
options = options.reverse_merge(length: 30, omission: "...")
# 破壊的に変更する
options.reverse_merge!(length: 30, omission: "...")
{a: 1, b: 2}.except(:a) # => {:b=>2}
{a: 1, b: 2, c: 3}.slice(:a, :c)
# => {:a=>1, :c=>3}
hash = {a: 1, b: 2}
rest = hash.extract!(:a) # => {:a=>1}
hash # => {:b=>2}
{a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2}
26. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
Hash の拡張: stringify、symbolize
文字列とシンボルの両方がキーになりえるとき、
stringify、symbolize で片方に寄せると扱いやすい
with_indifference_access を使うと
文字列とシンボルのどちらでも使えるようにできる
25
{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"}
{nil => nil, 1 => 1, "nested" => {"a" => 3, 5 => 5}}.deep_symbolize_keys
# => {nil=>nil, 1=>1, nested:{a:3, 5=>5}}
{a: 1}.with_indifferent_access["a"] # => 1
# 文字列とシンボルの両方がありえる場合に、ベンリ
def rewrite_path(options)
options = options.symbolize_keys
options.update(options[:params].symbolize_keys) if options[:params]
...
end
27. 大手町.rb #10 発表資料 「Active Support のコア拡張機能について」
まとめ
今日は Active Support のコア拡張機能について、
ピックアップして、紹介しました。
個人的には特に Object の拡張機能をよく使います
blank?、present?、presence、with_options
あと、 Hash の拡張もベンリです。
to_query、reverse_merge、except、slice、
with_indifferent_access
ドキュメントを熟読しましょう!
気が付かなかった発見がたくさんあります。
私自身、たくさんありました
特に Rails Guide と Ruby on Rails API は必読です。
https://railsguides.jp/
https://api.rubyonrails.org/
26