QA@IT

Rails で、Controller に定義されている action を一度に取得する方法はありますか?

6368 PV

表題のとおり、Rails で Controller に登録されている action を一覧できるメソッドなどはありますか?

用途としては、以下のように caches_action で action 名を指定する時など、Controller に定義されている action を取り出して渡すことができれば、action が追加されても caches_action に自動的に登録できると考えています。

class ProductsController < ActionController

  caches_action :index, :show
  # caches_action ProductsController.actions のように action 一覧を渡したい

  def index
    @products = Products.all
  end

  def show
    @product = Products.find(parmas[:id])
  end

end

追記

@a_matsuda さんの回答で、ProductsController.action_methods を使うことで一覧が取得出来ました。

しかし、追加で質問があるので追記します。

手元の環境で action_methods を実行したところ、自分で設定した action 以外のものが取れてしまいました。
["mobylette_resolver", "mobylette_resolver="] などで、これは mobylette という Gem の、https://github.com/tscolari/mobylette/blob/master/lib/mobylette/respond_to_mobile_requests.rb#L46 で Controller に追加されているようです。
このような、Gem などで追加されてしまうメソッドを除外して、ProductController に定義した action だけを取り出すことは可能でしょうか?

回答

ProductsController.action_methodsですね https://github.com/rails/rails/blob/6033e8a/actionpack/lib/abstract_controller/base.rb#L77

(以下追記の回答)
結論から言うと、ご質問の現象はmobyletteの不具合です。

AbstractControllerではactionをprocessする際に、このaction_methodsの中に渡された名前のactionが含まれているかどうかをチェックして、あれば呼び出すという仕様になっています。 https://github.com/rails/rails/blob/6033e8a/actionpack/lib/abstract_controller/base.rb#L130
つまり、action_methodsはActionDispatch(= routesとか)とコントローラーを大事な繋ぐAPIになっています。
コントローラーには、action以外のpublicメソッドを定義してはいけません。内部で使う共通処理などはすべてprivateにするのが正しい作法です。
ましてや、こんな神聖な場所にプラグインごときが勝手にpublicメソッドを生やすなんてもっての外です。

ところで、このようなコントローラーのAPIをチェックすることのできる、tracerouteという素晴らしいgemがあります。
お手元のプロジェクトに'traceroute' gemをbundleしてrake tracerouteを実行すると、今回の問題のようなお行儀の悪いactionがひと目でわかって大変便利ですね! :)

(実行例)

% rake traceroute
Unused routes:
Unreachable action methods:
  users#mobylette_options
  users#mobylette_options=
  users#mobylette_resolver
  users#mobylette_resolver=
編集 履歴 (1)
  • ありがとうございます!
    手元で試したところ、Controller のメソッドを取ることができました。

    しかし、追加で問題があることがわかったので、質問欄をアップデートしました...
    -
  • なるほど〜、mobylette の行儀が悪いということなんですね。そして、traceroute 大変素晴らしい Gem ですね!!!! -

全てのアクションを定義した後に action_methods でよさそうです。

$ cat app/controllers/users_controller.rb 
# coding: utf-8
class UsersController < ApplicationController

  p action_methods.to_a

  def create
  end

  p action_methods.to_a
end

$ rails c 
1] pry(main)> UsersController
[]
["create"]
=> UsersController
[2] pry(main)> exit
bundle exec rails c  3.66s user 0.49s system 39% cpu 10.593 total

また、Controller が継承しているアクションを外したい場合は、Module#instance_methodsに引数で false を渡した結果で & をとるといいのではないでしょうか?

$ cat app/controllers/admins_controller.rb 
# coding: utf-8
class AdminsController < UsersController

  p action_methods.to_a.map(&:to_sym)

  def update
  end

  p action_methods.to_a.map(&:to_sym)
  p(action_methods.to_a.map(&:to_sym) & instance_methods(false))
end

$ be rails c                              
Loading development environment (Rails 3.2.11)
[1] pry(main)> AdminsController
[:create]
[:update, :create]
[:update]
=> AdminsController

http://rurema.clear-code.com/1.9.3/method/Module/i/instance_methods.html

編集 履歴 (1)
  • ありがとうございます!追加で質問が発生してしまったので、追記させてもらいました... -
  • 追記ありがとうございます!
    `action_methods.to_a.map(&:to_sym) & instance_methods(false)` で希望のものを取ることができました!
    -

SpreeというEngineの上にアプリを書いているのですが、Controllerに親クラスからの引き算書いてごまかしました _callback_foobar(たしかなんか付いてた気がする)がどこから出てきたのかはよくわかってません&覚えてません… そのときはprintデバッグしました

def self.original_actions
  (action_methods - Spree::BaseController.action_methods).
    reject {|s| s =~ /^_callback/}.
  map(&:to_sym)
end
編集 履歴 (0)
  • ありがとうございます!実はわたしも Spree という Gem を使っているので、参考にさせていただきます! -
ウォッチ

この質問への回答やコメントをメールでお知らせします。