Rails Integer の拡張を追う
#Ruby_on_Rails #Ruby
rails/activesupport/lib/active_support/core_ext/integer/ 以下に Integer を拡張させたファイルが置かれている
inflections.rb
multiple.rb
time.rb
それぞれ見ていく
inflections.rb
github.icon GitHub repo URL: https://github.com/rails/rails/blob/9c0c90979a759a41628e0cd9d73821b0b34d03fc/activesupport/lib/active_support/core_ext/integer/inflections.rb
Integer#ordinalize Integer#ordinal の2つのメソッドが定義されている。
code:rails/activesupport/lib/active_support/core_ext/integer/inflections.rb(Integer#ordinalize)(ruby)
# Ordinalize turns a number into an ordinal string used to denote the
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# 1.ordinalize # => "1st"
# 2.ordinalize # => "2nd"
# 1002.ordinalize # => "1002nd"
# 1003.ordinalize # => "1003rd"
# -11.ordinalize # => "-11th"
# -1001.ordinalize # => "-1001st"
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
code:rails/activesupport/lib/active_support/core_ext/integer/inflections.rb(Integer#ordinal)(ruby)
# Ordinal returns the suffix used to denote the position
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# 1.ordinal # => "st"
# 2.ordinal # => "nd"
# 1002.ordinal # => "nd"
# 1003.ordinal # => "rd"
# -11.ordinal # => "th"
# -1001.ordinal # => "st"
def ordinal
ActiveSupport::Inflector.ordinal(self)
end
両方とも ActiveSupport::Inflector のクラスメソッドを呼び出している
rails/activesupport/lib/active_support/inflector.rb を眺める
code:rails/activesupport/lib/active_support/inflector.rb(ruby)
# in case active_support/inflector is required without the rest of active_support
require "active_support/inflector/inflections"
require "active_support/inflector/transliterate"
require "active_support/inflector/methods"
https://github.com/rails/rails/blob/9c0c90979a759a41628e0cd9d73821b0b34d03fc/activesupport/lib/active_support/inflector.rb#L3-L6
require active_support/inflector/methods とある。ここにありそう。
rails/activesupport/lib/active_support/inflector/methods.rb を見る
code:rails/activesupport/lib/active_support/inflector/methods.rb(ruby)
# Returns the suffix that should be added to a number to denote the position
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# ordinal(1) # => "st"
# ordinal(2) # => "nd"
# ordinal(1002) # => "nd"
# ordinal(1003) # => "rd"
# ordinal(-11) # => "th"
# ordinal(-1021) # => "st"
def ordinal(number)
abs_number = number.to_i.abs
if (11..13).include?(abs_number % 100)
"th"
else
case abs_number % 10
when 1; "st"
when 2; "nd"
when 3; "rd"
else "th"
end
end
end
# Turns a number into an ordinal string used to denote the position in an
# ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# ordinalize(1) # => "1st"
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
# ordinalize(1003) # => "1003rd"
# ordinalize(-11) # => "-11th"
# ordinalize(-1021) # => "-1021st"
def ordinalize(number)
"#{number}#{ordinal(number)}"
end
https://github.com/rails/rails/blob/9c0c90979a759a41628e0cd9d73821b0b34d03fc/activesupport/lib/active_support/inflector/methods.rb#L334-L369
実体を発見した。
mizukmb.icon rails/activesupport/lib/active_support/core_ext/integer/inflections.rb で拡張するクラスのメソッド内では ActiveSupport を呼び出すだけで、メソッドの定義と実装を分離させている。責務をわけてハードコーディングを防ぐため? どうやら初期の頃からこういう実装になってる
multiple.rb
github.icon GitHub URL: https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/core_ext/integer/multiple.rb
code:rails/activesupport/lib/active_support/core_ext/integer/multiple.rb(ruby)
# frozen_string_literal: true
class Integer
# Check whether the integer is evenly divisible by the argument.
#
# 0.multiple_of?(0) # => true
# 6.multiple_of?(5) # => false
# 10.multiple_of?(2) # => true
def multiple_of?(number)
number != 0 ? self % number == 0 : zero?
end
end
https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/core_ext/integer/multiple.rb
あ、でもここはハードコーディングなのか…
mizukmb.icon ordinalize との違いは引数の有無だけ。もしかして引数をリレーしないため?たしかに引数を渡して、渡して…だと読みにくいし、依存度は高くなるしなあ。
time.rb
github.icon GitHub URL: https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/core_ext/integer/time.rb
code:rails/activesupport/lib/active_support/core_ext/integer/time.rb(Integer#months)(ruby)
# Returns a Duration instance matching the number of months provided.
#
# 2.months # => 2 months
def months
ActiveSupport::Duration.months(self)
end
code:rails/activesupport/lib/active_support/core_ext/integer/time.rb(Integer#years)(ruby)
# Returns a Duration instance matching the number of years provided.
#
# 2.years # => 2 years
def years
ActiveSupport::Duration.years(self)
end
お、こっちは引数無し、 ActiveSupport を呼び出しだ。 引数をリレーしないため という予想は当たっているかもしれない…?
それぞれの実装は以下
code:rails/activesupport/lib/active_support/duration.rb(ruby)
def months(value) #:nodoc:
new(value * SECONDS_PER_MONTH, :months, value)
end
https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/duration.rb#L170-L172
code:rails/activesupport/lib/active_support/duration.rb(ruby)
def years(value) #:nodoc:
new(value * SECONDS_PER_YEAR, :years, value)
end
https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/duration.rb#L174-L176
あ、 ActiveSupport::Duration クラスのインスタンスを返すのか。 ActiveSupport クラスは内部で扱うだけで外側には表れないと (勝手に) 想像してた。
rails console で確かめてみたけどそうだった。
code:rails console(ruby)
4 pry(main)> ActiveSupport::Duration.months(10)
=> 10 months
5 pry(main)> ActiveSupport::Duration.months(10).class
=> ActiveSupport::Duration
rails/activesupport/lib/active_support/duration.rb を見ると演算子を定義してるので、そのためなのかな。
これとか
code:rails/activesupport/lib/active_support/duration.rb(ActiveSupport::Duration#+)(ruby)
# Adds another Duration or a Numeric to this Duration. Numeric values
# are treated as seconds.
def +(other)
if Duration === other
parts = @parts.dup
other.parts.each do |(key, value)|
partskey += value
end
Duration.new(value + other.value, parts)
else
seconds = @parts:seconds + other
Duration.new(value + other, @parts.merge(seconds: seconds))
end
end
https://github.com/rails/rails/blob/0979713abe2e22083e1beca01a1d113408c9ab36/activesupport/lib/active_support/duration.rb#L234-L247
mizukmb.icon Rails が、というよりは OOP としてこうやって実装するのかーという学びがあった。責務はわけるけど、依存度をあげないために全て ActiveSupport に集約させているわけではないというのが面白かった。