サイバーウェーブでエンジニアとして働いている林です。実務の中でRubyのsendメソッドを使用する機会があったので、文字に起こしていきたいと思います。
sendメソッドとは
レシーバが持つメソッドを、文字列またはシンボルで指定して呼び出せる機能です。具体的なサンプルコードを以下に記載しました。
サンプルコード
class Country
def japan(greeting)
puts "日本の挨拶は#{greeting}です"
end
def america(greeting)
puts "アメリカの挨拶は#{greeting}です"
end
def italy(greeting)
puts "イタリアの挨拶は#{greeting}です"
end
end
country = Country.new
country.send(:japan, "こんにちは") # => 日本の挨拶はこんにちはです
country.send(:america, "Hello") # => アメリカの挨拶はHelloです
country.send("italy", "Ciao") # => イタリアの挨拶はCiaoです
Countryクラスの中でjapanメソッド、americaメソッド、italyメソッドを定義しました。一番下の3行でsendメソッドを使い、Countryクラスで定義した3つのメソッドを呼び出しています。
ただ、上記の使い方だとsendメソッドによる恩恵を受けられないと思っています。
sendメソッドの最大のメリットは、レシーバのメソッドを動的に呼び出すことができる点です。メソッドを動的に呼び出すことで、繰り返し同じコードを書かなくて済むというメリットがあります。
例えば、以下のようなコードがあるとします。
class Country
def greeting(language)
case language
when "japan"
japan_greeting
when "america"
america_greeting
when "italy"
italy_greeting
end
end
def japan_greeting
puts "日本の挨拶はこんにちはです"
end
def america_greeting
puts "アメリカの挨拶はHelloです"
end
def italy_greeting
puts "イタリアの挨拶はCiaoです"
end
end
country = Country.new
country.greeting("japan")
このコードの特徴はcase文を使用しているところです。引数によって処理が変わるよう、「引数がjapanの場合」「引数がamericaの場合」「引数がitalyの場合」の3通りに処理方法の場合分けをしています。
こちらのコードを、sendメソッドを使って書き換えると次のコードになります。case文による処理分岐を書く必要がなくなり、コードがスッキリしました。
class Country
attr_accessor :language
def initialize(language)
@language = language
end
def japan_greeting
puts "日本の挨拶はこんにちはです"
end
def america_greeting
puts "アメリカの挨拶はHelloです"
end
def italy_greeting
puts "イタリアの挨拶はCiaoです"
end
end
country = Country.new("japan")
country.send("#{country.language}_greeting")
これにより、case文を書く必要もなくなり、メソッドを動的に呼び出すことができます。
実務で使用した例
複数のコントローラー内で繰り返す処理を一つにまとめ上げ、メタ的に書きたいときも使えます。
メタ的に書くとは「メタプログラミング」のことです。メタプログラミングとは、「コードを書くためのコードを書く」ことです。高位ロジックを定義する方法です。メタプログラミングを使用することで短く、再利用可能なコーディングができます。これを動的に書くともいいます。冗長なコードは見た目も悪く、修正も書いた分行う必要があります。高位ロジックを書くことで、綺麗かつ修正時の手間も省くメリットがあります。
メタ的なコードを書くためにsendメソッドが役立ちます。
例えば、ウェブサイトを管理するCMS(コンテンツマネジメントシステム)を実装しようとしたとしましょう。そのウェブサイトは「静的ページ」「ブログ」「ニュース」の3つから成っています。
それに対応して、コード側ではページテーブル、ブログテーブル、ニューステーブルという3つの独立するモデルがあります。ただし、3つのモデルとも機能やテーブル内のカラムは同じです。(実務であつかうCMS機能は、より複雑なことが多いです。履歴管理や予約公開、カテゴリーや言語機能を追加したりなど)
通常であればモデルを3個分作成し、ユーザーが入力した情報を受け取るコントローラー、情報を表示する機能であるビューも分けます。しかし、今回の場合、公開したり、下書きに戻したりする機能が3モデルとも同じなため、共通処理を一つのモジュールで管理することにしました。もちろん独立したそれぞれのコントローラーは別途作成します。
次のコードでは、記事を公開する処理を記述しています。それぞれのビュー内で公開するボタンがクリックされると、ボタンがクリックされたという情報がcms_controllerに入り、publishアクションでupdate処理が走ります。
その中で処理が成功したらredirect_toして画面遷移するというのが以下の一連の流れです。
cms_controller.rb:
module PageController
def publish
if hoge.update!
flash[:notice] = "公開しました"
redirect_to send("admin_#{resource_name}_path", 'in_public_status': :public)
else
flash[:alert] = "公開できませんでした"
render :edit
end
end
private
def hoge
... # update処理
end
def resource_name
controller_path.gsub(/\\Aadmin\\//, '')
end
end
注目してほしいのは7行目のコードです。
redirect_to send("admin_#{resource_name}_path", 'in_public_status': :public)
resource_nameは現在のコントローラーパスを取得してくれるため、ページから公開ボタンをクリックすればpagesを、ブログから公開ボタンをクリックすればblogsを取得してくれます。
それをsendメソッド内に送れば動的に呼び出すことが可能です。
仮に上記をメタ的に書かない場合、3つのコントローラー内に同じ処理を書かなければいけません。
blogs_controller.rb:
redirect_to(admin_blogs_path(in_public_status: :public))
pages_controller.rb:
redirect_to(admin_pages_path(in_public_status: :public))
news_controller.rb:
redirect_to(admin_news_path(in_public_status: :public))
メソッドを動的に呼び出すことのメリットを学べた機会でした。
sendメソッドを使用することのデメリット
脆弱性
sendメソッドは便利な一方で、動的であるが故に危険性も高いです。
例えば、ユーザーがフォームのsendメソッドに「exit」という文字列を打ち込んだとすると、sendメソッドは意図せずexitメソッド(Kernelモジュール)を呼び出し、アプリケーションが強制終了します。
予期しない動作を防ぐため、外部の入力に依存する値は渡さないようにするのが賢明です。sendメソッドにはハードコーディングしたメソッドを渡すまでにして、エンドユーザーや外部の入力内容をそのまま渡すことはやめましょう。
可読性の悪さ
sendメソッドは使用している側からすると便利ですが、コールドリーディングする側であるレビュワーにとっては厄介です。
今回は短いサンプルコードを用いて説明しましたが、非常に読みにくいです。コードの可読性が低いためです。sendメソッドの動作を直感的に理解することは難しく、デバッグツールで挙動を確かめたり、致命的なバグがないかまで確認する必要があります。人によっては、いつも以上に腰を据えて読まないといけません。
書く側は良いが読む側には辛いことがあるのがデメリットです。弊社ではコーディングルールを徹底してコントロールしています。
普段の開発で使うメソッドの中では抽象的な概念であり、sendメソッドを使用する場面も多くないことから初学者にとっても難しい箇所になります。
会社ごとのコーディングルールに則って使いこなす必要があります。
まとめ
sendメソッドの優位性、実務での使用例、sendメソッドの脆弱性をお伝えしました。使い分ける能力が必要ですが、一度使いこなせれば非常に強力な武器になります。動的かつメタ的に書くことができます。
参考文献
- https://docs.ruby-lang.org/ja/latest/method/Object/i/send.html
- https://qiita.com/igrep/items/b2fed2d467f8a16f5eb0
- https://qiita.com/hogucc/items/79da134520ee731fe298
サイバーウェーブでは一緒に働く仲間を募集しています
サイバーウェーブでは一緒に働く仲間を募集しています。当社は創業20年を機に「第2の創業期」として、事業を拡大方針へと舵を切りました。会社が急拡大しており、若いメンバーやインターン生がどんどん入社しています。個人の成長は、勢いのある環境のなかでこそ加速されるものです。成長事業に参画できるチャンスです!
サイバーウェーブはコード1行1行に対してこだわりを持って、プロ意識をもったエンジニアを育てている、技術力に自信のあるシステム開発会社です。社内には、創業23年のノウハウの詰まった研修コンテンツや、安定したシステム開発をするための手順が整っています。実力のあるシステム開発会社だからこそ、経験を積みながら、実践的なシステム開発の技術も学べます。自信をもって主義主張ができる『飯が食える』エンジニアを目指していただきます。
エンジニアとしてしっかりと飯を食べていけるまでには、道のりは決して短くありません。長期で頑張り、エンジニアになるという強い思いがあれば、実戦的な開発経験と、周りの仲間とコミュニケーションしながら、しっかりと成長できます。当社のノウハウを余すことなく活かし、技術力を大きく伸ばしていただきます。
ぜひ、エントリーをお待ちしております!
インターン採用
新卒・既卒・第二新卒採用