Railsのソースコード読んでみる | Active Support to_query編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事で読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照ください
shitake4.hatenablog.com

読んだ箇所

to_query を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
RailsGuidesの日本語ドキュメントを見てみると

このメソッドは、エスケープされていないkeyを受け取ると、そのキーをto_paramが返す値に対応させるクエリ文字列の一部を生成します
引用:Active Support コア拡張機能:present

使い方はこんな感じのようです

current_user.to_query('user') # => "user=357-john-smith"

引数=レシーバを変換した値 という文字列を生成します

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def to_query を探してみます

f:id:sktktk1230:20180118143800p:plain

2. 該当箇所が3個ほどあったので、それぞれみてみます

activesupport > lib > active_support > core_ext > object > to_query.rb
1. class Object
class Object

省略

  # Converts an object into a string suitable for use as a URL query string,
  # using the given <tt>key</tt> as the param name.
  def to_query(key)
    "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
  end
end

まず CGI.escape() を見てみます

与えられた文字列を URL エンコードした文字列を新しく作成し返します。
[PARAM] string:
URL エンコードしたい文字列を指定します。

引用:Rubyリファレンスマニュアル:CGI.escape

文字列の引数を1つ取り、それに対してURLエンコードする処理です

CGI.escape() の引数部分 key.to_param で何をやっているのかは以前書いたこちらを参照ください

shitake4.hatenablog.com

続いて、 CGI.escape(to_param.to_s)を見ます

CGI.escape はさきほどと同様です
to_param.to_s はレシーバを to_param し、その戻り値を to_sしています

to_s はレシーバを文字列に変換するメソッドです
次のクラスに実装されています

ここまでをまとめると
key=valueという文字列を生成するメソッドになります

key部分を生成する際には to_query の引数に対して to_paramし、それをHTMLエスケープして作ります
value部分を生成する際には レシーバを to_paramし、それをHTMLエスケープするということです

2. class Array
class Array

省略

  # Converts an array into a string suitable for use as a URL query string,
  # using the given +key+ as the param name.
  #
  #   ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
  def to_query(key)
    prefix = "#{key}[]"

    if empty?
      nil.to_query(prefix)
    else
      collect { |value| value.to_query(prefix) }.join "&"
    end
  end
end

変数prefixの値の例を見てみます
仮にkeyがrubyMineだとすると

prefix = "rubyMine[]"

という文字列になります

次にif empty? を見てみます
empty?はRubyの標準メソッドです
使い方を見てみると

empty?メソッドは、配列が空であればtrue、1つ以上の要素があればfalseを返します。
引用:Rubyリファレンス:empty?

if empty? はレシーバである配列の中身が空であるかを判定しています

次の処理nil.to_query(prefix)を見てみます
どんな挙動かというと
f:id:sktktk1230:20180118144405p:plain

最後に collect { |value| value.to_query(prefix) }.join "&" を見ます
collect { |value| value.to_query(prefix) } では

レシーバに対して collect を実行しています
collectとはどういうものかというと

collectメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。ブロック引数itemには各要素が入ります。
mapメソッドはcollectメソッドの別名です。
次の例では、16進数を表す文字列を数値に変換した配列を作成しています。
引用: Rubyリファレンス:collect

numbers = ["68", "65", "6C", "6C", "6F"]
p numbers.collect {|item| item.to_i(16) }

=> [104, 101, 108, 108, 111]

引用: Rubyリファレンス:collect

レシーバの中身1つずつに対して、value.to_query(prefix) を実行しています
そして戻り値を配列にしています
実行例としてはこちらになります
f:id:sktktk1230:20180118144355p:plain

.join "&" では配列中身を&で連結し文字列生成しています

joinの挙動はこちらです

joinメソッドは、配列の各要素を文字列に変換し、引数sepを区切り文字として結合した文字列を返します。
引用:Rubyリファレンス:join

読んでみて

HTTPGetメソッドでパラメータを生成する際の使用するメソッドはWebサービスを作る上で欠かせないものだったりするので、 他のフレームワークや言語で作る際にはここで読んだ内容が活かせるなと思いました

Railsのソースコード読んでみる | Active Support to_param編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照ください
shitake4.hatenablog.com

読んだ箇所

to_param を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
RailsGuidのActive Support コア拡張機能を見てみると

Railsのあらゆるオブジェクトはto_paramメソッドに応答します。
これは、オブジェクトを値として表現するものを返すということです。返された値はクエリ文字列やURLの一部で使用できます。
引用:Active Support コア拡張機能:to_param

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def to_param を探してみます

f:id:sktktk1230:20180115104828p:plain

2. 該当箇所が8個ほどあったので、それぞれみてみます

1. activesupport > lib > active_support > core_ext > object > to_query.rb
1. class Object
# frozen_string_literal: true

require "cgi"

class Object
  # Alias of <tt>to_s</tt>.
  def to_param
    to_s
  end

単純にto_s しているだけです
オブジェクトに対して行いたい場合は、オーバーライドして使うということを想定しているのでしょう

2. class NilClass
class NilClass
  # Returns +self+.
  def to_param
    self
  end
end

レシーバ自身を返しています NilClassの戻り値はnilですので、このようなコードでもいいのではないか?と思いました

def to_param
  nil
end

もしかしたらコミットログに戻り値をselfにした理由や経緯があるかもしれないと思ったので、調べてみます
RubyMineの機能で選択範囲のコミットログを見る機能がある為、それを使います f:id:sktktk1230:20180115104852p:plain

残念ながら1コミットしかなかったため、わかりませんでした
f:id:sktktk1230:20180115104905p:plain

3. class TrueClass
class TrueClass
  # Returns +self+.
  def to_param
    self
  end
end
4. class FalseClass
class FalseClass
  # Returns +self+.
  def to_param
    self
  end
end
5. class Array
class Array
  # Calls <tt>to_param</tt> on all its elements and joins the result with
  # slashes. This is used by <tt>url_for</tt> in Action Pack.
  def to_param
    collect(&:to_param).join "/"
  end

レシーバに対して collect を実行しています
collectとはどういうものかというと

collectメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。ブロック引数itemには各要素が入ります。
mapメソッドはcollectメソッドの別名です。
次の例では、16進数を表す文字列を数値に変換した配列を作成しています。
引用: Rubyリファレンス:collect

numbers = ["68", "65", "6C", "6C", "6F"]
p numbers.collect {|item| item.to_i(16) }

=> [104, 101, 108, 108, 111]

引用: Rubyリファレンス:collect

mapも同じ処理を行うメソッドです
(&:to_param) の&:は配列の各要素に対して to_param を実行しています
たとえば、

[1, 2, 3].map(&:to_s)
=> ["1", "2", "3"]

という感じです ※詳しい解説は@kasei-san氏のこちらの記事がわかりやすいかと思います
qiita.com

各要素に to_param した配列に対して .join しています
joinについて調べてみると

joinメソッドは、配列の各要素を文字列に変換し、引数sepを区切り文字として結合した文字列を返します。
引数のデフォルト値は組み込み変数$,の値です。$,の初期値はnilなので、引数を省略すると区切り文字なしで要素を結合した文字列になります。
引用: Rubyリファレンス:join

配列の中身を連結し文字列に変換する処理です
たとえば、

array = ["Ruby", "Mine"]
puts array.join(", ")

の場合は Ruby, Mine と出力されます

区切り文字がなが場合は

array = ["Ruby", "Mine"]
puts array.join

RubyMine というようにそのまま連結し出力します

ということで .join "/" ではto_param された各要素を"/"で連結し、文字列として出力するという処理になります

2. activesupport > lib > active_support > core_ext > string > output_safety.rb
module ActiveSupport #:nodoc:
  class SafeBuffer < String

中略
    def to_param
      to_str
    end

Stringを拡張したSafeBufferクラスのインスタンスに対して to_param するのは to_str と同様になります
SafeBufferクラスがどのような用途で使われるものなのか分からない為、さきほどと同様にコミットログから理由、経緯を調べてみます

選択範囲をSafeBufferクラス内にしコミットログを調べます

class SafeBuffer < String

省略

end

f:id:sktktk1230:20180115104938p:plain

一番古いコミットログを見付け足ので、こちらのリビジョンナンバーをコピーしRailsのリポジトリで調べます

f:id:sktktk1230:20180115104951p:plain

GitHubの検索窓にさきほどコピーしたリビジョンナンバーをペーストし検索します
f:id:sktktk1230:20180115105007p:plain

該当のコミットを見てみると
f:id:sktktk1230:20180115105020p:plain

パフォーマンスの改善の為 html_safe を呼び出す場合にはSafeBufferを使うということでした
コメントも読んでみると
f:id:sktktk1230:20180115105032p:plain

Stringに追加していると +<< を実行する時に遅くなっているということでした

読んでみて

コミットログを追ってみるとなぜ実装されているのかが分かるので、コードを読み込むだけでなく、歴史まで追ってみるとより理解が深まるなと思いました

Railsのソースコード読んでみる | Active Support acts_like?編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照ください
shitake4.hatenablog.com

読んだ箇所

acts_like? を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
Railsの日本語ドキュメントを見てみると

2.7 acts_like?(duck)
acts_like?メソッドは、一部のクラスがその他のクラスと同様に振る舞うかどうかのチェックを、ある慣例に則って実行します。Stringクラスと同じインターフェイスを提供するクラスがあり、その中で以下のメソッドを定義しておくとします。
def acts_like_string?
end
このメソッドは単なる目印であり、メソッドの本体と戻り値の間には関連はありません。これにより、クライアントコードで以下のようなダックタイピングチェックを行なうことができます。
some_klass.acts_like?(:string)
RailsにはDateクラスやTimeクラスと同様に振る舞うクラスがいくつかあり、この手法を使用できます。
引用:ActiveSupport コア機能:acts_like?

レシーバのクラスが引数に入れたクラスと同じ振る舞いをするか確認するメソッドです
安全にダックタイピングする為、レシーバを確認したい場合などに利用します

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def acts_like? を探してみます

f:id:sktktk1230:20180111152611p:plain

2. 該当箇所が1箇所あったので、それぞれみてみます

activesupport > lib > active_support > core_ext > object > acts_like.rb
# frozen_string_literal: true

class Object
  # A duck-type assistant method. For example, Active Support extends Date
  # to define an <tt>acts_like_date?</tt> method, and extends Time to define
  # <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and
  # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
  # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
  def acts_like?(duck)
    case duck
    when :time
      respond_to? :acts_like_time?
    when :date
      respond_to? :acts_like_date?
    when :string
      respond_to? :acts_like_string?
    else
      respond_to? :"acts_like_#{duck}?"
    end
  end
end

レシーバに acts_like_引数 メソッドが実装されているか確認しています
acts_like_引数 メソッドの内、time, date, stringはactivesupport内ですでに実装されているということがここらか分かりました
それ以外にも拡張することもできるようです

それでは acts_like_引数 はどう実装すればいいのか見てみます

3. まずはdef acts_like_time? を探してみます

f:id:sktktk1230:20180111152627p:plain

4. 該当箇所が4箇所あったので、それぞれ見てみます

そのままtrueで返しています

1. activesupport > lib > active_support > core_ext > date_time > acts_like.rb
# frozen_string_literal: true

require "date"
require "active_support/core_ext/object/acts_like"

class DateTime
  # Duck-types as a Date-like class. See Object#acts_like?.
  def acts_like_date?
    true
  end

  # Duck-types as a Time-like class. See Object#acts_like?.
  def acts_like_time?
    true
  end
end
2. activesupport > lib > active_support > time_with_zone.rb
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
  true
end
3. activesupport > lib > active_support > core_ext > time > acts_like.rb
# frozen_string_literal: true

require "active_support/core_ext/object/acts_like"

class Time
  # Duck-types as a Time-like class. See Object#acts_like?.
  def acts_like_time?
    true
  end
end
4. activesupport > test > core_ext > object > acts_like_test.rb

テスト用に定義されたものの為、省略します

4. 次にdef acts_like_date? を探してみます

f:id:sktktk1230:20180111152644p:plain

5. 該当箇所が2箇所あったので、それぞれ見てみます

さきほどと重複になりますが、そのままtrueを返しています

1. activesupport > lib > active_support > core_ext > date_time > acts_like.rb
# frozen_string_literal: true

require "date"
require "active_support/core_ext/object/acts_like"

class DateTime
  # Duck-types as a Date-like class. See Object#acts_like?.
  def acts_like_date?
    true
  end

  # Duck-types as a Time-like class. See Object#acts_like?.
  def acts_like_time?
    true
  end
end
2. activesupport > lib > active_support > core_ext > date > acts_like.rb
# frozen_string_literal: true

require "active_support/core_ext/object/acts_like"

class Date
  # Duck-types as a Date-like class. See Object#acts_like?.
  def acts_like_date?
    true
  end
end

5. 最後にdef acts_like_string? を探してみます

f:id:sktktk1230:20180111152706p:plain

5. 該当箇所が2箇所あったので、それぞれ見てみます

さきほどと重複になりますが、そのままtrueを返しています

1. activesupport > lib > active_support > core_ext > string > behavior.rb
# frozen_string_literal: true

class String
  # Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
  def acts_like_string?
    true
  end
end
2. guides > source > active_support_core_extensions.md

ソースコードではなくactivesupportのcore_extensionのドキュメントだったのでここでは省略します

6. 一応さきほど調べた以外の def acts_like_xxx が存在しないか確認してみます

他の実装はありませんでした
f:id:sktktk1230:20180111152718p:plain

読んでみて

安全なダックタイピングをするために acts_like_xxx が実装されているのは知りませんでした
ダックタイピングをする場合には、このメソッドを活用していければと思います

Railsのソースコード読んでみる | ActiveSupport class_eval編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

仕事でもたまに使われてたりする class_eval を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
Ruby on RailsのAPIドキュメントを見てみると

class_eval on an object acts like singleton_class.class_eval.
引用:Ruby on Rails API:class_eval

調べてみてもどんな動きをするのか分かりませんでした singleton_class.class_eval のように振る舞うそうなので、調べてみます

まずはsingleton_classです

singleton_classメソッドは、オブジェクトの特異クラスを返します。
小さい整数(Fixnum)およびシンボルに対してsingleton_classを呼び出すと、例外TypeErrorが発生します。true、false、nilに対して呼び出すと、特異クラスではなくTrueClass、FalseClass、NilClassを返します。
次の例では、オブジェクトcatの特異クラスをsingletonに取り出し、define_methodで特異メソッドを定義しています。
引用:Rubyリファレンス:singleton_class

次にclass_evalです

class_evalメソッドは、ブロックをクラス定義やモジュール定義の中のコードであるように実行します。ブロックの戻り値がメソッドの戻り値になります。
引用:Rubyリファレンス:class_eval

使い方はこんなかんじです

class User  
  attr_accessor :name  
  def initialize(name)  
    @name = name  
  end  
  [:downcase, :upcase].each do |method|  
    class_eval <<-EOS  
      def #{method}  
        @name.#{method}  
      end  
    EOS  
  end  
end  
 
user = User.new("taro")  
puts user.upcase  

### 引用:[Rubyリファレンス:class_eval](https://ref.xaio.jp/ruby/classes/module/class_eval)

eachでdowncase,upcaseを回し、downcase,upcaseを定義しています

[:downcase, :upcase].each do |method|
  class_eval <<-EOS
    def #{method}
      @name.#{method}
    end
  EOS
end

#{}で変数の値を埋め込んでいるので、downcaseの場合はこんな感じで定義されているということです

class_eval <<-EOS
  def downcase
    @name.downcase
  end
EOS

つまり、特異クラスに対して、与えられたブロックをクラス定義の中のコードであるように実行することが出来るということです

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def class_eval を探してみます

f:id:sktktk1230:20180110160708p:plain

2. 該当箇所が1箇所だったので、それを見てみます

1. activesupport > lib > active_support > core_ext > kernel > singleton_class.rb
# frozen_string_literal: true

module Kernel
  # class_eval on an object acts like singleton_class.class_eval.
  def class_eval(*args, &block)
    singleton_class.class_eval(*args, &block)
  end
end

これを見てみるとsingleton_class.class_eval(*args, &block) を呼び出しているだけなので、このメソッドいるのかな?と思えてしまいます
なので、コミットログで現在に至るまでの経緯を見てみます

こちらがコミットログです
f:id:sktktk1230:20180110160723p:plain

ruby1.9.2でsingleton_classが実装されたようです
その為、それ以前のバージョンにも対応出来るようにsingleton_class メソッドが記述されていたようです
f:id:sktktk1230:20180110160736p:plain

一応singleton_classの実装がRuby1.9.2で行われているかリポジトリのチェンジログ等で確認してみます
RubyのGithubのdoc配下にNEWS-1.9.2があったので見てみると
f:id:sktktk1230:20180110160752p:plain

読んでみて

class_evalは使い方だけ覚えて使っていたけど、しっかり実装を追ってみたからこそわかることがあったので、引き続き続けていきます

Railsのソースコード読んでみる | ActiveSupport try編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

Rails書いているとよく使う try を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
RailsのAPIリファレンスを見てみると

try(*a, &b)

Invokes the public method whose name goes as first argument just like public_send does, except that if the receiver does not respond to it the call returns nil rather than raising an exception.
This method is defined to be able to write

@person.try(:name)

instead of

@person.name if @person

引用:Ruby on Rails API: try

レシーバ(@person)がnilならnilを返し、別のオブジェクトならnameメソッドを実行するというかんじです

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def try を探してみます

f:id:sktktk1230:20171225145117p:plain

2. 該当箇所が2個ほどあったので、それぞれみてみます

1. activesupport > lib > active_support > core_ext > object > try.rb
ActiveSupport::Tryable.try
# frozen_string_literal: true

require "delegate"

module ActiveSupport
  module Tryable #:nodoc:
    def try(*a, &b)
      try!(*a, &b) if a.empty? || respond_to?(a.first)
    end

まず def try(*a, &b)を見てみます
引数の *aですが、rubyは仮引数に*をつけることで引数をすべて配列として受け取ることができます(可変長引数といいます)
&b はブロックを引数として受け取ってます

ブロックとは

eachメソッドなど、Rubyでは「ブロック」という仕組みを活用して、メソッドに「処理そのものを引数として渡す」というパターンが頻出します。
引用:Rubyの面白さを理解するためのメソッド、ブロック、Proc、lambda、クロージャの基本

次に try!(*a, &b) if a.empty? を見てみると 仮引数のaが空の場合は try!(*a, &b) を実行するようです

try!を見てみます 同じmodule内にtry!メソッドがありました

def try!(*a, &b)
  if a.empty? && block_given?
    if b.arity == 0
      instance_eval(&b)
    else
      yield self
    end
  else
    public_send(*a, &b)
  end
end

if a.empty? && block_given? は配列aに対して empty? をしています
Array.empty?を調べてみると

empty?メソッドは、配列が空であればtrue、1つ以上の要素があればfalseを返します。

arr = [1, 2, 3] puts arr.empty? arr = [] puts arr.empty?

引用:Rubyリファレンス:empty?

なので、配列の中身がnilでもfalseになります
以下が配列の中身がnilの場合の挙動です
f:id:sktktk1230:20171225145142p:plain

block_given? はメソッドにブロックが与えられているか確認するメソッドです

メソッドにブロックが与えられていれば真を返します。
このメソッドはカレントコンテキストにブロックが与えられているかを調べるので、 メソッド内部以外で使っても単に false を返します。
iterator? は (ブロックが必ずイテレートするとはいえないので)推奨されていないの で block_given? を使ってください。
Ruby2.4.0リファレンスマニュアル

つまり、仮引数aの要素が空かつブロックが渡されているときがこちらの判定式の内容です

※ ブロックについては@kidach1氏のこちらの記事が分かりやすかったです
qiita.com

次にif b.arity == 0を見てみます
arity を調べてみると

メソッドが受け付ける引数の数を返します。
ただし、メソッドが可変長引数を受け付ける場合、負の整数
-(必要とされる引数の数 + 1) を返します。C 言語レベルで実装されたメソッドが可変長引数を 受け付ける場合、-1 を返します。
引用:Ruby2.4.0リファレンスマニュアル

ブロックの引数が無い場合に真となります
その場合には instance_eval にブロックを渡すということになります

instance_evalメソッドは、渡されたブロックをレシーバのインスタンスの元で実行します。ブロックの戻り値がメソッドの戻り値になります。
ブロック内では、インスタンスメソッド内でコードを実行するときと同じことができます。ブロック内でのselfはレシーバのオブジェクトを指します。なお、ブロックの外側のローカル変数はブロック内でも使えます。
Ruby 1.9 Ruby 1.9では、instance_evalメソッドはBasicObjectに移されました(この変更はRuby 1.8用に書いたプログラムに特に影響はありません)。
次の例では、instance_evalメソッドに渡したブロック内でインスタンス変数やprivateメソッドを利用しています。

class Cat  
   def initialize(name)  
     @name = name  
   end  
   private  
   def hello  
     "meow..."  
   end  
end  
cat = Cat.new("Piko")  
puts cat.instance_eval { @name + ": " + hello }  

引用:Rubyリファレンス:instance_eval

yield self はブロックが引数を受け取る場合を想定しています
ブロックが引数を受け取るのはどんな記述かというとeachメソッドであればこのような書き方です

each do |i|
  puts i
end

次に if a.empty? && block_given?がfalseだった場合を見てみます
public_send(*a, &b) を調べると

public_sendメソッドは、レシーバの持っているpublicなメソッドを呼び出します。引数や戻り値については、sendメソッドの説明をご覧ください。
引用:Rubyリファレンス:public_send

レシーバのpublicなメソッドに対してtry!で受け取った引数をそのまま渡しています
try!メソッドは以上です

tryメソッドに戻ります
respond_to?(a.first) を見てみます
aは可変長引数の為、配列です。 a.firstで配列の先頭の値を取得しています そして、respond_to?でレシーバに配列の先頭の値がメソッドとして実装されているかを確認しています

先程、tryの使い方を調べた際に出てきた例person.try(:name)でいうとaには[:name]が入っており、a.first:name が取れるので、 respond_to?(:name) という感じです

NilClass.try
class NilClass
  # Calling +try+ on +nil+ always returns +nil+.
  # It becomes especially helpful when navigating through associations that may return +nil+.
  #
  #   nil.try(:name) # => nil
  #
  # Without +try+
  #   @person && @person.children.any? && @person.children.first.name
  #
  # With +try+
  #   @person.try(:children).try(:first).try(:name)
  def try(*args)
    nil
  end

nilに対してtryした場合は、常にnilを返すという動きです

ついでにnil.try!も見てみると

# Calling +try!+ on +nil+ always returns +nil+.
#
#   nil.try!(:name) # => nil
def try!(*args)
  nil
end

tryと同じ動きです

tryが色々なオブジェクトで使えるのは、ActiveSupport::TryableモジュールをObjectにincludeしてからです
該当の箇所は下記になります

class Object
  include ActiveSupport::Tryable

class Delegator
  include ActiveSupport::Tryable

読んでみて

ブロックを引き受けるメソッドを作る場合の書き方が非常に参考になったので、ここらへんは活かしていきたいと思います

Railsのソースコード読んでみる | ActiveSupport deep_dup編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

deep_dup を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
RailsガイドのActive Supportコア拡張機能の 2.4 deep_dupの項目を見てみます

deep_dupメソッドは、与えられたオブジェクトの「ディープコピー」を返します。
Rubyは通常の場合、他のオブジェクトを含むオブジェクトをdupしても、他のオブジェクトについては複製しません。
このようなコピーは「浅いコピー (shallow copy)」と呼ばれます。たとえば、以下のように文字列を含む配列があるとします。

array = ['string']
duplicate = array.dup
duplicate.push 'another-string'
# このオブジェクトは複製されたので、複製された方にだけ要素が追加された
array # => ['string']
duplicate # => ['string', 'another-string']
duplicate.first.gsub!('string', 'foo')

# 1つ目の要素は複製されていないので、一方を変更するとどちらの配列も変更される

array # => ['foo']
duplicate # => ['foo', 'another-string']

上で見たとおり、Arrayのインスタンスを複製して別のオブジェクトができたことにより、一方を変更しても他方は変更されないようになりました。
ただし、配列は複製されましたが、配列の要素はそうではありません。dupメソッドはディープコピーを行わないので、配列の中にある文字列は複製後も同一オブジェクトのままです。
オブジェクトをディープコピーする必要がある場合はdeep_dupをお使いください。例:

array = ['string']
duplicate = array.deep_dup
duplicate.first.gsub!('string', 'foo')
array # => ['string']
duplicate # => ['foo']

オブジェクトが複製不可能な場合、deep_dupは単にそのオブジェクトを返します。

number = 1
duplicate = number.deep_dup
number.object_id == duplicate.object_id # => true

引用:Active Supportコア拡張機能:2.4 deep_dup

オブジェクトをコピーしたいが、別物として利用したい場合に利用するメソッドのようです

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def deep_dup を探してみます

f:id:sktktk1230:20171220113937p:plain

2. 該当箇所が3箇所だったので、それぞれみてみます

activesupport > lib > active_support > core_ext > object > deep_dup.rb
Object.deep_dup
class Object
  # Returns a deep copy of object if it's duplicable. If it's
  # not duplicable, returns +self+.
  #
  #   object = Object.new
  #   dup    = object.deep_dup
  #   dup.instance_variable_set(:@a, 1)
  #
  #   object.instance_variable_defined?(:@a) # => false
  #   dup.instance_variable_defined?(:@a)    # => true
  def deep_dup
    duplicable? ? dup : self
  end
end

まずduplicable? かどうか判定しているようです
※ duplicable?のソースコードリーディングについてはこちら
shitake4.hatenablog.com

trueの場合は dup が実行されるようです
Rubyリファレンスのclone,dupで挙動を調べてみると

cloneメソッドとdupメソッドは、レシーバのオブジェクトのコピーを作成して返します。オブジェクトのコピーとは、同じ内容を持つ別のオブジェクトです。具体的には、元のオブジェクトと同じクラスの新しいオブジェクトで、元のオブジェクトのインスタンス変数を新しいオブジェクトにコピーしたものです。

中略

浅いコピー

cloneとdupは「浅いコピー」を作ることに注意してください。
上記のCatクラスの例では、@nameがoriginalからcopiedにコピーされますが、@nameが指しているオブジェクト(文字列)は同じものです。
copiedの@nameに対して破壊的なメソッド(レシーバ自身を変更するメソッド)を呼び出すと、originalの@nameが指している文字列も変更されます。

Objectに対するdeep_dupは浅いコピーになるようです
Rubyリファレンスのclone,dupに記述している例をrails consoleで確認してみると
f:id:sktktk1230:20171220114045p:plain

originalのnameも変わっています

Array.deep_dup
class Array
  # Returns a deep copy of array.
  #
  #   array = [1, [2, 3]]
  #   dup   = array.deep_dup
  #   dup[1][2] = 4
  #
  #   array[1][2] # => nil
  #   dup[1][2]   # => 4
  def deep_dup
    map(&:deep_dup)
  end
end

レシーバに対してmapしています self.map(&:deep_dup)と同一です
(&:deep_dup) の&:は配列の各要素に対して deep_dup を実行しています
例えば、

[1, 2, 3].map(&:to_s)
=> ["1", "2", "3"]

という感じです ※詳しい解説は@kasei-san氏のこちらの記事がわかりやすいかと思います
qiita.com

ということで、配列の各要素に対してdeep_dupをおこなっています

Hash.deep_dup
class Hash
  # Returns a deep copy of hash.
  #
  #   hash = { a: { b: 'b' } }
  #   dup  = hash.deep_dup
  #   dup[:a][:c] = 'c'
  #
  #   hash[:a][:c] # => nil
  #   dup[:a][:c]  # => "c"
  def deep_dup
    hash = dup
    each_pair do |key, value|
      if key.frozen? && ::String === key
        hash[key] = value.deep_dup
      else
        hash.delete(key)
        hash[key.deep_dup] = value.deep_dup
      end
    end
    hash
  end
end

まず浅いコピーでレシーバをhashに入れています
each_pair を調べてみると

eachメソッドは、ハッシュの要素(キーと値)の数だけブロックを繰り返し実行します。繰り返しごとにブロック引数にはキーkeyと値valが入ります。each_pairメソッドは、eachの別名です。
引用:Rubyリファレンス:each, each_pair (Hash)

ということなので、レシーバのpairのkey,valueを取り出しています

次に

if key.frozen? && ::String === key
  hash[key] = value.deep_dup

を見ていきます
まずfrozen? を調べてみると

frozen?メソッドは、オブジェクトが凍結状態ならtrueを、そうでなければfalseを返します。オブジェクトを凍結状態にするには、freezeメソッドを使います。
引用:Rubyリファレンス:frozen?

更に freeze を調べてみると

freezeメソッドは、オブジェクトを凍結、つまり変更不可にします。凍結状態のオブジェクトを変更しようとすると、Ruby 1.8では例外TypeErrorが、Ruby 1.9では例外RuntimeErrorが発生します。
凍結状態を調べるには、frozen?メソッドを使います。凍結状態を元に戻すメソッドはありません。
標準クラスのオブジェクトでは、凍結したあとで破壊的なメソッド(レシーバ自身を変更するメソッド)を呼び出すと例外が発生します。
引用:Rubyリファレンス:freeze

freezeされた状態 = 破壊的変更が出来なくなっているということのようです

つまり if key.frozen? && ::String === key とは破壊的変更が出来ないStringクラスのキーの場合、valueのみをdeep_dupしています

そして

else
  hash.delete(key)
  hash[key.deep_dup] = value.deep_dup
end

はkeyが破壊的変更が可能な値の為、key,valueともにdeep_dupしています
これでコピー元のkeyに影響が無いようにしています

読んでみて

Objectでdeep_dupする場合は浅いコピーの為、気をつけなければいけないと思いました
コードを読んでみてわかることもあるので、プロジェクトでライブラリなどを導入する場合はちゃんと実装まで理解しないとバグを発生させる可能性も出てきてしまうので気をつけたいです

Railsのソースコード読んでみる | ActiveSupport duplicable?編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

Active Support コア拡張機能を見ていて知った duplicable? を今日は読んでみようと思います

どんな使い方だっけ?

Railsを読んでみる前にまずは使い方を調べてみます
Active Support コア拡張機能の2.3 duplicable?を読んでみると

Rubyにおける基本的なオブジェクトの一部はsingletonオブジェクトです。たとえば、プログラムのライフサイクルが続く間、整数の1は常に同じインスタンスを参照します。

1.object_id # => 3
Math.cos(0).to_i.object_id # => 3

従って、このようなオブジェクトはdupメソッドやcloneメソッドで複製することはできません。

true.dup # => TypeError: can't dup TrueClass

singletonでない数字にも、複製不可能なものがあります。

0.0.clone # => allocator undefined for Float
(2**1024).clone # => allocator undefined for Bignum

Active Supportには、オブジェクトがプログラム的に複製可能かどうかを問い合わせるためのduplicable?メソッドがあります。

"foo".duplicable? # => true
"".duplicable? # => true
0.0.duplicable? # => false
false.duplicable? # => false

デフォルトでは、nil、false、true、シンボル、数値、クラス、モジュール、メソッドオブジェクトを除くすべてのオブジェクトがduplicable? #=> trueです。
引用:Active Support コア拡張機能:2.3 duplicable?

dupメソッドやcloneメソッドで複製出来ない値が存在しているので、複製可能か確認する為の機能のようです

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def duplicable? を探してみます

f:id:sktktk1230:20171218175426p:plain

2. 該当箇所が10個ほどあったので、それぞれみてみます

activesupport > lib > active_support > core_ext > object > duplicable.rb
Object.duplicable?
class Object
  # Can you safely dup this object?
  #
  # False for method objects;
  # true otherwise.
  def duplicable?
    true
  end
end

上記箇所のコミットログを見てみると
f:id:sktktk1230:20171218180129p:plain

Ruby2.4以前では NilClass, FalseClass, TrueClass, Symbol, Numeric はdup出来ませんでしたが、2.4から可能となったようです
f:id:sktktk1230:20171218180531p:plain

処理がどう変わったのか知りたいので、Ruby2.4.0のリファレンスを見てみます

オブジェクトの複製を作成して返します。
dup はオブジェクトの内容, taint 情報をコピーし、 clone はそれに加えて freeze, 特異メソッドなどの情報も含めた完全な複製を作成します。
clone や dup は浅い(shallow)コピーであることに注意してください。後述。
TrueClass, FalseClass, NilClass, Symbol, そして Numeric クラスのインスタンスなど一部のオブジェクトは複製ではなくインスタンス自身を返します。
[PARAM] freeze:
false を指定すると freeze されていないコピーを返します。
[EXCEPTION] ArgumentError:
TrueClass などの常に freeze されているオブジェクトの freeze されていないコピーを作成しようとしたときに発生します。
引用:Ruby 2.4.0 リファレンスマニュアル:Object#clone

NilClass, FalseClass, TrueClass, Symbol, Numeric が複製ではなくインスタンス自身を返すようになったようです

合わせて以前の挙動はどうだったのか、Ruby2.3.0のリファレンスを見てみます

オブジェクトの複製を作成して返します。
dup はオブジェクトの内容, taint 情報をコピーし、 clone はそれに加えて freeze, 特異メソッドなどの情報も含めた完全な複製を作成します。
clone や dup は浅い(shallow)コピーであることに注意してください。後述。
[EXCEPTION] TypeError:
TrueClass, FalseClass, NilClass, Symbol, そして Numeric クラスのインスタンスなど一部のオブジェクトを複製しようとすると発生します。
引用:Ruby 2.3.0 リファレンスマニュアル:Object#clone

Ruby2.3.0のバージョンだとレシーバが NilClass, FalseClass, TrueClass, Symbol, Numeric の場合にTypeErrorが発生するという仕様だったんですね

該当コミット
github.com

NilClass.duplicable?
class NilClass
  begin
    nil.dup
  rescue TypeError

    # +nil+ is not duplicable:
    #
    #   nil.duplicable? # => false
    #   nil.dup         # => TypeError: can't dup NilClass
    def duplicable?
      false
    end
  end
end

Ruby2.4.0からレシーバ NilClass, FalseClass, TrueClass, Symbol, Numeric に対して dup/cloneが複製ではなくインスタンス自身を返すようになったため、オープンクラスする際にdupがTypeErrorを起こす(Ruby2.3.x以前のバージョンを使用している)場合に限りfalseを返すというようにしています

FalseClass.duplicable?
class FalseClass
  begin
    false.dup
  rescue TypeError

    # +false+ is not duplicable:
    #
    #   false.duplicable? # => false
    #   false.dup         # => TypeError: can't dup FalseClass
    def duplicable?
      false
    end
  end
end

こちらも同様です

TrueClass.duplicable?
class TrueClass
  begin
    true.dup
  rescue TypeError

    # +true+ is not duplicable:
    #
    #   true.duplicable? # => false
    #   true.dup         # => TypeError: can't dup TrueClass
    def duplicable?
      false
    end
  end
end

こちらも同様です

Numeric.duplicable?
class Numeric
  begin
    1.dup
  rescue TypeError

    # Numbers are not duplicable:
    #
    #  3.duplicable? # => false
    #  3.dup         # => TypeError: can't dup Integer
    def duplicable?
      false
    end
  end
end

こちらも同様です

Complex.duplicable?
class Complex
  begin
    Complex(1).dup
  rescue TypeError

    # Complexes are not duplicable:
    #
    #   Complex(1).duplicable? # => false
    #   Complex(1).dup         # => TypeError: can't copy Complex
    def duplicable?
      false
    end
  end
end

こちらも同様です

Rational.duplicable?
class Rational
  begin
    Rational(1).dup
  rescue TypeError

    # Rationals are not duplicable:
    #
    #   Rational(1).duplicable? # => false
    #   Rational(1).dup         # => TypeError: can't copy Rational
    def duplicable?
      false
    end
  end
end

こちらも同様です

BigDecimal.duplicable?
require "bigdecimal"
class BigDecimal
  # BigDecimals are duplicable:
  #
  #   BigDecimal.new("1.2").duplicable? # => true
  #   BigDecimal.new("1.2").dup         # => #<BigDecimal:...,'0.12E1',18(18)>
  def duplicable?
    true
  end
end

Objectと同様で常にtrueを返すようです

Method.duplicable?
class Method
  # Methods are not duplicable:
  #
  #  method(:puts).duplicable? # => false
  #  method(:puts).dup         # => TypeError: allocator undefined for Method
  def duplicable?
    false
  end
end

Methodは常にfalseを返すようです

Symbol.duplicable?
class Symbol
  begin
    :symbol.dup # Ruby 2.4.x.
    "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
  rescue TypeError

    # Symbols are not duplicable:
    #
    #   :my_symbol.duplicable? # => false
    #   :my_symbol.dup         # => TypeError: can't dup Symbol
    def duplicable?
      false
    end
  end
end

beginからrescueまでのコードを見てみると Ruby 2.4.0において特定のシンボルはdup出来ないようです
詳しい内容が気になったのでコミットログから探してみます
f:id:sktktk1230:20171218180338p:plain

Githubで探してみると
f:id:sktktk1230:20171218175648p:plain

.to_sym.dup するとダメな文字列があったりするようです

該当コミット
github.com

読んでみて

普段使ってなかったメソッドだったのですが、調べてみることで、詳細な仕様が把握できたため、たまに使う時などにとてもハマるということはなくなりそうだなと思いました

Railsのソースコード読んでみる | ActiveSupport presence編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

Rails書いているとよく使う presence を今日は読んでみようと思います

どんな使い方だっけ?

Railsの日本語ドキュメントには記載がなかったので、APIリファレンスから確認してみます

1. APIリファレンスでの探し方

http://api.rubyonrails.org/ をブラウザで開くとページの左側にsearchがあるので、そこに presence を入力します

f:id:sktktk1230:20171218113512p:plain

2. 検索結果

該当する項目があったため、クリックしてみると
f:id:sktktk1230:20171218113529p:plain

以下の説明がありました

presence()
Returns the receiver if it's present otherwise returns nil. object.presence is equivalent to
object.present? ? object : nil

For example, something like

state = params[:state] if params[:state].present?
country = params[:country] if params[:country].present?
region = state || country || 'US'

becomes
region = params[:state].presence || params[:country].presence || 'US'
@return [Object]
引用:APIリファレンス

object.present?の結果がtrueの場合は、objectそのものを返し、falseの場合は、nilを返すという仕様のようです
For exampleにあるように変数に値がある場合はその値を使用したい。ただ空の場合はデフォルト値を入れたいなどの場合に 非常にコードをシンプルに記述することが可能です

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で presence を探してみます

f:id:sktktk1230:20171218113555p:plain

2. 該当箇所が1箇所だったので、それを見てみます

1. activesupport > lib > active_support > core_ext > object > blank.rb
  # Returns the receiver if it's present otherwise returns +nil+.
  # <tt>object.presence</tt> is equivalent to
  #
  #    object.present? ? object : nil
  #
  # For example, something like
  #
  #   state   = params[:state]   if params[:state].present?
  #   country = params[:country] if params[:country].present?
  #   region  = state || country || 'US'
  #
  # becomes
  #
  #   region = params[:state].presence || params[:country].presence || 'US'
  #
  # @return [Object]
  def presence
    self if present?
  end

メソッドの中身を見てみると、レシーバに対して present? しており、trueであればselfを返すようです
falseの場合はnilが返り値となります
rubyは最後に評価された値を返すという言語仕様な為、評価するものがなにも無い = nilということになります

※ blank?メソッドのソースコードリーディングについてはこちら

shitake4.hatenablog.com

読んでみて

丁寧なコメントが書いてあり、仕様や使い方が書いてあり非常に読みやすいと感じます

Railsのソースコード読んでみる | ActiveSupport present?編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com

読んだ箇所

Rails書いているとよく使う present? を今日は読んでみようと思います

どんな使い方だっけ?

読んでみる前にまずは使い方を調べてみます
Railsの日本語ドキュメントを見てみると

変数.present?
!blank? を実行するメソッド。if 変数.present?とunless 変数.blank?は同じ意味
nil, "", " "(半角スペースのみ), , {}(空のハッシュ) のときにfalseを返します。
Railsで拡張されたメソッドで、Rubyのみでは使えないのでご注意ください。
引用:Railsドキュメント:present

とのことです こちらも blank? 同様によく使うメソッドです

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def present? を探してみます

f:id:sktktk1230:20171218105833p:plain

2. 該当箇所が1箇所なので、それをみてみます

activesupport > lib > active_support > core_ext > object > blank.rb
class Object
  中略
  # An object is present if it's not blank.
  #
  # @return [true, false]
  def present?
    !blank?
  end

blank?メソッドの返り値を否定演算子で評価しています
blank?の返り値は、truefalse なので、present?も必ずtruefalse となります

※ blank?メソッドのソースコードリーディングについてはこちら

shitake4.hatenablog.com

読んでみて

今回は実装箇所が一箇所のみだったのとすでに blank? を読んでいたので、すんなりと理解することができました
読み進めていくうちにそれぞれの実装が関連しあっていくとより楽しめそうです

Railsのソースコード読んでみる | ActiveSupport blank?編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました

読めるようにするまで

1. Githubのrails/railsリポジトリから任意のフォルダにクローンする

  1. ブラウザで https://github.com/rails/rails を開く
  2. Clone or Downloadをクリック f:id:sktktk1230:20171212153907p:plain

  3. クリップボードアイコンをクリック f:id:sktktk1230:20171212153931p:plain

  4. 任意のディレクトリ配下で $ git clone https://github.com/rails/rails.git rails

2. 任意のエディタで閲覧

僕の場合はRubyMineを使ってます
※以下はRubyMineの場合の設定方法です

  1. Rubymineを開き、openを選択 f:id:sktktk1230:20171212154018p:plain

  2. 先程cloneした任意のフォルダを選択する

  3. 下記のように取り込めている f:id:sktktk1230:20171212154031p:plain

読んだ箇所

Rails書いているとよく使う blank? を今日は読んでみようと思います

どんな使い方だっけ?

読んで見る前に使い方を調べてみます Railsの日本語ドキュメントを見てみると

ruby 変数.blank?
nil? + empty? のようなメソッド。nilまたは空のオブジェクトをチェックできる。
nil, "", " "(半角スペースのみ), , {}(空のハッシュ) のときにfalseを返します。
Railsで拡張されたメソッドで、Rubyのみでは使えないのでご注意ください。
引用:Railsドキュメント

ざっくり値がないものを確認したいときとかに便利ですね。ruby標準でも欲しいなと思ったりします

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def blank? を探してみます

f:id:sktktk1230:20171212154210p:plain

2. 該当箇所が10個ほどあったので、それぞれみてみます

1. activesupport > lib > active_support > core_ext > date > blank.rb
# frozen_string_literal: true

require "date"

class Date #:nodoc:
  # No Date is blank:
  #
  #   Date.today.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

Dateクラスに対してblank?をすると必ずfalseが返り値のようです
オープンクラスで実装しているようです

※ オープンクラスとは?

既存するクラスを好きな場所で再オープンし、メソッド修正・追加など任意の変更を加えられる機能のこと。
引用: [Ruby] メタプログラミングの入り口、オープンクラスを理解する

2. activesupport > lib > active_support > time_with_zone.rb
中略

# An instance of ActiveSupport::TimeWithZone is never blank
def blank?
  false
end

中略

ActiveSupport::TimeWithZoneの場合も同じように必ずfalseが返り値のようです

3. activesupport > lib > active_support > core_ext > date_time > blank.rb
# frozen_string_literal: true

require "date"

class DateTime #:nodoc:
  # No DateTime is ever blank:
  #
  #   DateTime.now.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

DateTimeクラスに対してblank?をすると必ずfalseが返り値のようです

4. activesupport > lib > active_support > core_ext > object > blank.rb
Object.blank?
# An object is blank if it's false, empty, or a whitespace string.
# For example, +false+, '', '   ', +nil+, [], and {} are all blank.
#
# This simplifies
#
#   !address || address.empty?
#
# to
#
#   address.blank?
#
# @return [true, false]
def blank?
  respond_to?(:empty?) ? !!empty? : !self
end

respond_to?の使い方を調べてみると

respond_to?メソッドは、レシーバのオブジェクトに対してメソッドを呼び出せるかどうかを調べます。引数nameにはメソッド名をシンボルか文字列で指定します。メソッドnameを持っていればtrue、なければfalseが返ります。
レシーバのクラスのメソッドだけでなく、親クラスやインクルードしているモジュールのメソッドも対象になります。デフォルトではpublicなメソッドとprotectedなメソッドを調べますが、第2引数にtrueを指定するとprivateなメソッドも含めて調べます。
引用:Rubyリファレンス:respond_to? (Object)

empty?が実装されているかチェックしているようです
そしてtrueの場合は!!empty? で falseの場合は !selfが返ります

なぜempty?の前に!!が付いているのかわからなかったので、更に調べてみます

該当箇所のコミットログを見てみると f:id:sktktk1230:20171212160213p:plain

126dc47で変更しているようです
Githubで見てみると

github.com

コントリビューターから質問されているところを見つけました f:id:sktktk1230:20171212160539p:plain

rubyという言語仕様上empty?が書き換えられて必ず TrueClass / FalseClass のシングルトンが返り値となるわけではない為、こういう書き方をしているようです

!self はObjectクラスのインスタンスに対して行っているので、基本的にfalseが返り値となります
※ trueになるケースが思いつかなかったので、誰か知っている方いらっしゃいましたら教えてください

NilClass
class NilClass
  # +nil+ is blank:
  #
  #   nil.blank? # => true
  #
  # @return [true]
  def blank?
    true
  end
end

Dateクラスのときと同じようにオープンクラスしてblank?メソッドを追加しているようです
下記クラスも同様ですね

FalseClass
class FalseClass
  # +false+ is blank:
  #
  #   false.blank? # => true
  #
  # @return [true]
  def blank?
    true
  end
end
TrueClass
class TrueClass
  # +true+ is not blank:
  #
  #   true.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end
String
BLANK_RE = /\A[[:space:]]*\z/

# A string is blank if it's empty or contains whitespaces only:
#
#   ''.blank?       # => true
#   '   '.blank?    # => true
#   "\t\n\r".blank? # => true
#   ' blah '.blank? # => false
#
# Unicode whitespace is supported:
#
#   "\u00a0".blank? # => true
#
# @return [true, false]
def blank?
  # The regexp that matches blank strings is expensive. For the case of empty
  # strings we can speed up this method (~3.5x) with an empty? call. The
  # penalty for the rest of strings is marginal.
  empty? || BLANK_RE.match?(self)
end

レシーバがempty?またはBLANK_REにマッチする場合はtrueのようです
BLANK_REをみてみると BLANK_RE = /\A[[:space:]]*\z/
[:space:]はPOSIX文字クラスというものです。スペースとタブと改ページにマッチします

※以前の記事でPOSIX文字クラスについて調べています

上記2パターンのどちらかにマッチした場合にtrueが返り値になるようです

Numeric
class Numeric #:nodoc:
  # No number is blank:
  #
  #   1.blank? # => false
  #   0.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

Numericの場合も必ずfalseが返り値のようです
0だともしかしたらtrueが返り値かもとか思ってしまいそうなので、確認するのは大事ですね

Time
class Time #:nodoc:
  # No Time is blank:
  #
  #   Time.now.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

読んでみて

ソースコードを読むのは結構億劫だったりしますが、よく使う処理などは意外な発見があったりして楽しめそうです

Rails Developers Meetup #7を見てみました | イベントレポート

f:id:sktktk1230:20180726121247p:plain

2017年11月16日(木) Rails Developers Meetup #7のイベントレポートです。当日はリモートから参加させて頂きました!

イベント概要

第一線で活躍する開発者・導入企業から、RubyやRailsに関する 発想・アプローチ・成功体験・失敗体験を学ぶ、非営利勉強会です。

RailsでECサービスをゼロから作ってみて

  • Time 19:35〜20:05
  • Speaker 株式会社spice life 赤松 祐希 氏
  • Slide

概要

オリジナルTシャツ販売サービス「STEERS」の内部の設計や開発で得た知見・経験について話します。
この発表を通じて普段のRailsの開発のちょっとした指針になるような話をできたらと思っています。
また、プロダクトマネージメントを行いながらの開発だったので、プロダクトマネージメントと開発の関係性などについても言及できたらと思っています。

発表ではチーム開発においてプロダクトマネージャーとエンジニアの意思疎通の大事さとそこをspice lifeさんはどうやって取り組んだのか。 Rails開発において原則を守っていくことの大事さと開発時にどういったことを考え開発していたのかを学ぶことができました

spice lifeさんでは以下のようなチーム構成と特色をもっているようです

チーム構成

  • プロダクトマネージャー兼アプリケーションエンジニア
  • インフラ兼アプリケーションエンジニア
  • デザイナ
チームの特色
  • プロダクトマネージャーと開発者が同一人物

メリットとして、意思疎通のコストがかからない
また負債を積むとしてもどれくらい苦労しそうかビジネス的に今後どうしたいのか自分がわかっているので判断が楽とのことでした

多くのプロジェクトではプロダクトマネージャとエンジニアが別れているケースが多いと思いますが、プロダクトマネージャーと開発者が別の場合は認識違いが発生するというのは非常に共感しました f:id:sktktk1230:20171128143838p:plain

ただ仕様変更も発生するし、すべてがスムーズにいくわけではないとのことです f:id:sktktk1230:20171128144019p:plain

Railsの話では開発時に考えておきたい必要なことを発表されていました - データベース設計をどうするべきか - マイグレーションをどうするべきか - サービスレイヤをどうするべきか - フォームクラスを利用する場合の、バリデーションをどこに書くのかetc

QA

  1. Rails原則はどうやって学びましたか?

スタートアップでのRails開発/運用でやってよかったこと

  • Time 20:05〜20:20
  • Speaker 株式会社トレタ 沢田 洋平 氏
  • Slide

概要

トレタ社では飲食店向けの予約/顧客管理サービスをつくっています。
サービス起ち上げ前から今までの4年を振り返って、スタートアップのエンジニアの視点から、ログや定期処理の扱いなどの開発/運用面でやってよかったことをいくつか紹介したいと思います。

開発に集中していく為にいかに運用を整備して、回していくかというのが勉強になりました。そのためにどういう部分を整備するのが あまり手間をかけずに運用が楽になっていくのかを発表されていました

スタートアップの特徴

プロダクト開発に集中するため、運用がまわらなくなってしまう。そのため、合間に地道に運用面を整備する必要がある

あまり手間をかけずに運用を楽にする方法

  1. リクエストのログが構造化されておらず検索性が低い為、アプリケーション固有のリクエストログの作成
  2. リクエストのログを集めて貯める為に、BigQueryを使用
  3. ジョブキューのログもリクエストログを同じくらい大事なためしっかりと収集
    • リクエストに比べて処理時間がユーザ体験に直結しないので見過ごしがち

グローバルサービスを作る時に考えておくこと

  • Time 20:20〜20:35
  • Speaker 株式会社トレタ 中村 真人 氏
  • Slide

概要

トレタは日本以外でも、シンガポールや台湾など15ヶ国以上の飲食店で使われています。
グローバルで使われるサービスでは時刻や電話番号ような、国による違いを考慮しておかないといけません。Railsでグローバルサービスを作る時に考えておくべきポイントを共有します。

タイムゾーンを考慮したRailsアプリケーション開発と海外対応の事例について発表されておりました。
タイムゾーンを考慮したときのハマりどころなど非常に勉強になりました

DBにタイムゾーン文字列を保存する際の注意点や f:id:sktktk1230:20171128144304p:plain

Railsアプリで日時を扱う場合のおすすめ f:id:sktktk1230:20171128144500p:plain

ケース別使用例 f:id:sktktk1230:20171128144818p:plain

トレタさんでの祝日対応もサービスならではの特徴があり非常に学びがありました f:id:sktktk1230:20171128144947p:plain f:id:sktktk1230:20171128145129p:plain

SMSの利用用途では以下のものがあるため、なるべく国番号(例:+81(日本))を入れるようにしていたのも グローバルサービスならではと感じました f:id:sktktk1230:20171128145449p:plain

クックパッドでの Webアプリケーション開発 2017

  • Time 20:45〜21:15
  • Speaker クックパッド株式会社 鈴木 康平 氏
  • Slide

概要

クックパッドではこれまで Microservices 化を推し進め、1つの巨大な Rails アプリを大勢で開発するのではなく、いくつもの小さいアプリを小さいチームで開発しようとしてきました。
その結果として2017年現在ではどのような開発が標準となっているのか、そしてそれを支える技術としてどのようなものを開発してきたかについて共有できればと思います。

クックパッドさんでのwebアプリケーション開発における自動化Hakoでの学びです

デプロイフロー

f:id:sktktk1230:20171128145726p:plain

Hakoが作れれた経緯

f:id:sktktk1230:20171128145835p:plain

Hakoが出来たことによって新規webアプリケーションの開発フローが以下のように f:id:sktktk1230:20171128150024p:plain f:id:sktktk1230:20171128150038p:plain

しかし課題感はまだのこっていたため統合コンソールの開発(hako-console)したそうです。 それによって開発フローが変化したとのことでした f:id:sktktk1230:20171128150147p:plain

また今後やっていきたいこととして以下に取り組んでいるそうです

  1. サービスメッシュ
  2. RPC

コンテナは友だち! in Rails Developers Meetup

  • Time 21:15〜21:45
  • Speaker GMOペパボ株式会社 近藤 宇智朗 氏
  • Slide

概要

いわゆるコンテナ仮想化と呼ばれるものは、正体はホストOSからリソース、権限等を隔離・制限したプロセスです。皆さんは隔離されていますか?
今回は、今流行中のDockerを例に、コンテナ仮想化がどのように実現されているかをデモしつつ覗いてみます。
時間があれば、拙作コンテナランタイム「Haconiwa」に同梱されたコンテナ作成専用のRuby(mruby)でコンテナ自作に挑戦してみます。
GMOペパボの新卒研修 コンテナは友だち!のRailsdm版です。

途中で離席してしまったため、メモを残せず。。

まとめ

定期開催は今回が最後とのことで残念ではありますが、また参加させて頂きたいと思います。初のリモートの参加でしたが、不自由なく非常に便利だったため、また開催するときには利用してみたいです

togetter.com

[:blank:][:alnum:]ってなんだろう | rails commit log流し読みを読んでみた

f:id:sktktk1230:20180726121250p:plain

概要

y_yagiさんのrails commit log流し読みを読んでいてわからなかったこと調べてみました

y-yagi.hatenablog.com

わからなかったこと

  1. POSIX文字クラス

POSIX文字クラス

github.com:word: という記述を初めてみました

Unicodeプロパティと 似た機能を持つ記法として、POSIX 文字クラスと呼ばれるものがあります。 これらは上の省略記法とは異なり、文字クラスの中でしか用いることが できません。これらは [:クラス名:] という記法を持ちます。 また、[:^クラス名:]という記法でその否定を意味します。 以下の括弧では実際にどの文字にマッチするかが Unicode プロパティや Unicode コードポイントで示されています。
引用:https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html

種類は以下のものがあります

文字 説明
[:alnum:] 英数字
[:alpha:] 英字
[:ascii:] ASCIIに含まれる文字 (0000 - 007F)
[:blank:] スペースとタブ
[:cntrl:] 制御文字
[:digit:] 数字 (Decimal_Number)
[:graph:] 空白以外の表示可能な文字(つまり空白文字、制御文字、以外)
[:lower:] 小文字
[:print:] 表示可能な文字(空白を含む)
[:punct:] 句読点
[:space:] 空白、改行、復帰
[:upper:] 大文字
[:xdigit:] 16進表記で使える文字
[:word:] 単語構成文字

Rubyのバージョンによって挙動が変わったりすることもあるみたいです qiita.com

RspecでStrongParamtersを使ってハマった

f:id:sktktk1230:20180726121250p:plain

rspecでStrongParamtersを利用したテストを書いた時にはまったので、備忘録として書いてみる

StrongParamtersとは?

4.5 Strong Parameters strong parametersを使用することで、Action ControllerのパラメータがActive Modelのマスアサインメントに利用されることを禁止できます。ホワイトリストに追記したもののみ使用できます。これは、多くの属性を一度に更新したいときに、どの属性の更新を許可し、どの属性の更新を禁止するかを明示的に決定しなければならないことを意味します。大雑把にすべての属性の更新をまとめて許可してしまうと、外部に公開する必要のない属性まで誤って公開されてしまう可能性が生じますので、そのような事態を防ぐために行います。 (Railsガイド)https://railsguides.jp/action_controller_overview.html

渡ってきたパラメータのホワイトリストを用意し、ホワイトリストの項目が存在しない場合にエラーとして処理することができるもの

今回ダメだった書き方

Rspec 3.1.0 使用 Rails 4.1.8 使用

let(:params) { { first_name: '佐藤', last_name: '太郎'} }

正しい書き方

単純でHashに対してStrongParametersを書くのではなく、ActionController::ParametersでNewする必要があった

    let(:input) { ActionController::Parameters.new(user: params) }
    let(:params) { { first_name: '佐藤', last_name: '太郎'} }