Tallman

技術とか読書とかいろいろ

rails consoleに簡単にオプションを足せるようになるgemを作った

作ったgemはこれです

github.com

使い方

config/application.rbに下のようにオプションをセットします。
Rails::Applicationの上に書くようにしてください。

Bundler.require(*Rails.groups)

D4C::Console.add_option 'hello' do
  puts "hello"
end

module YourRailsApplication
  class Application < Rails::Application

これでrails consoleに渡せるオプションを追加できました。実際にオプションが呼ばれると渡したブロックがcallされます。

rails c --hello
Running via Spring preloader in process 7922
hoge
Loading development environment (Rails 5.2.3)
irb(main):001:0> 

なんで作ったか

rails consoleを叩くときに、「デフォルトで実行されたくないけど実行したいときはさっと実行したい」処理があったからです。
職場でact_as_tenantというgemを使っています。dbのレコードに制限をかけるgemです。
act_as_tenantの都合上、rails consoleからdbのレコードを叩くには

ActsAsTenant.without_tenant do
end

として作業しなくてはなりませんでした。
とはいえRailtieconsoleメソッドでせっかくかけたセキュリティをデフォルトで外してしまうことはしたくありません。
--sandboxみたいにオプションで選択できると良いと考え、このgemを作りました。 1

実装の話

そもそもrails console --sandboxをした時の流れ

bin/rails console自体の具体的な流れはRailsガイドにとっても詳しく書いてあるので読みましょう。

railsguides.jp

では--sandboxはどうやって処理されているのでしょうか。 (注 Railsのコードを載せていますが色々省いています。)

Rails::Command::ConsoleCommandクラスは下のようになっています。

      class_option :sandbox, aliases: "-s", type: :boolean, default: false,
        desc: "Rollback database modifications on exit."

      def perform
        Rails::Console.start(Rails.application, options)
      end

受け取れるコマンドラインのオプションをclass_optionで追加しています。
Rails::Command::ConsoleCommandはThorというコマンドラインツールの作成を助けるgemを継承しています。class_optionはそのgemのメソッドです。

Rails::Applicationクラス内では--sandboxが入力されたかを保存できるようにしてあります。

    attr_accessor :assets, :sandbox

Rails::Consoleクラスでは、コマンドラインから渡されたオプションの有無をアプリケーションのインスタンスに渡しています。

    def initialize(app, options = {})
      @app     = app
      @options = options

      app.sandbox = sandbox?
    end

    def sandbox?
      options[:sandbox]
    end

これによりif app.sandboxで条件分岐できるようになります。

実際にD4Cで行った拡張

module Rails
  Application.prepend D4C::PrependOptionAccessor
  Console.prepend D4C::PrependOptionSetter
  Command::ConsoleCommand.prepend D4C::PrependClassOption
end

D4CのmoduleはD4C::Console.add_optionに渡されたオプションをRailsのクラスが扱えるようにするものです。 それぞれprependしてmoduleの中でsuperすることで処理を足しています。
D4C::Consoleの中身はオプションを構造体にして保存するクラスのインスタンスです。

Gemの名前

ジョジョの奇妙な冒険のスタンド名です。
最初はbaby_faceって名前でした、コンソールなので。
ディ・モールトベネなgemの名前だと思いながら作成して、いざ公開しようと思ったら同じ名前のgemが既にあって泣きました。皆さんはこうならないようにちゃんとrubygemsで名前を検索してから作りましょう。
最終的にたやすくオプションを足せるってところからD4Cにしました。
最後にこれが一番書きたかったんですけど、D4Cってまさに「Ruby」だと思うんですよね。

f:id:sasa5740:20190819222624p:plain


  1. ruby-jpでgemを晒したら「pryrcirbcに書くのも手ですがよりチームでコミットしやすいメリットもあるかも。」というお言葉もらいました。pryrcirbc書くのも手ですね。ありがとうございます。ruby-jpは本当に神。