問題となる背景
Railsアプリの開発を続けていくとconfig/routes.rb
が肥大化して見辛くなります。
例えば...
# config/routes.rb require 'sidekiq/web' Rails.application.routes.draw do # devise not support omniauth with locale devise_for :user, only: [:omniauth_callbacks], controllers: { omniauth_callbacks: 'user/omniauth_callbacks' } scope '(:locale)', locale: /#{I18n.available_locales.map(&:to_s).join('|')}/ do root 'videos#index' devise_for :user, skip: [:sessions, :omniauth_callbacks], controllers: { passwords: 'user/passwords', registrations: 'user/registrations', confirmations: 'user/confirmations', } devise_scope :user do get 'login', to: 'user/sessions#new', as: :new_user_session post 'login', to: 'user/sessions#create', as: :user_session delete 'logout', to: 'user/sessions#destroy', as: :destroy_user_session get 'terms_of_service', to: 'user/registrations#terms_of_service', as: :terms_of_service get 'privacy', to: 'user/registrations#privacy', as: :privacy end resources :users, only: [:update, :destroy] resource :user do resources :videos get '/support', to: 'user#support' get '/mypage', to: 'user#edit' end devise_for :admin, controllers: { sessions: 'admin/sessions' } resource :admin do resources :videos end resources :videos, only: [:show, :create, :destroy] do resource :good, only: [:create, :destroy] resource :bad, only: [:create, :destroy] resources :comments, only: [:create, :destroy, :update] # videos.idを伴わないパスを生成 collection do resources :categories, only: [:index] get '/search', to: 'videos#search', as: :search end end resource :opinion, only: [:create] mount FileUploader::UploadEndpoint => "/images" # s3 direct upload mount Sidekiq::Web => '/sidekiq' Sidekiq::Web.use Rack::Auth::Basic do |username, password| username == 'xxx' && password == 'xxx' end unless Rails.env.development? mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development? end end
初見の体験がよろしくないですね。
リソースごとにファイルを切り分け、ルーティングを管理することはできないでしょうか?
解決策
Rubyのinstance_eval
メソッドを用います。
instance_eval
については以下の記事が参考になりました。
Rubyのinstance_evalメソッドについて分かってきたような気もするが疑問も増えた
insance_eval
を用いると以下のように書くことができます。
require 'sidekiq/web' Rails.application.routes.draw do def draw(resource_name) instance_eval(File.read(Rails.root.join("config/routes/#{resource_name}.rb"))) end # devise not support omniauth with locale devise_for :user, only: [:omniauth_callbacks], controllers: { omniauth_callbacks: 'user/omniauth_callbacks' } scope '(:locale)', locale: /#{I18n.available_locales.map(&:to_s).join('|')}/ do root 'videos#index' draw :user draw :admin draw :video draw :third_party resource :opinion, only: [:create] end end
# => 追加
config/routes/user.rb
config/routes/admin.rb
config/routes/video.rb
config/routes/third_party.rb
上記の例を用いてconfig/routes/videos.rb
に移したものは以下です。
# config/routes/video.rb resources :videos, only: [:show, :create, :destroy] do resource :good, only: [:create, :destroy] resource :bad, only: [:create, :destroy] resources :comments, only: [:create, :destroy, :update] # videos.idを伴わないパスを生成 collection do resources :categories, only: [:index] get '/search', to: 'videos#search', as: :search end end
元々config/routes.rb
に書いていたものをそのまま移せばOKです。
リソースを新規追加することになった際にはdraw: resource_name
を追加し、config/routes/resource_name.rb
のファイルを作成してそちらにルーティングを書いていきます。
これにより、本体のconfig/routes.rb
が肥大化していくことを抑えることができます。
ちなみにRails.application.draw
rails/actionpack/lib/action_dispatch/routing/route_set.rb
Railsで普段使っているようにパスとアプリのマッピングをしてくれています。
require 'action_dispatch' | |
header = {'Content-Type' => 'text/html'} | |
routes = ActionDispatch::Routing::RouteSet.new | |
routes.draw do | |
get 'hello', to: -> env { [200, header, ['hello']] } | |
get 'goodbye', to: -> env { [200, header, ['goobye']] } | |
root to: -> env { [200, header, ['Hiroshima Okayama rb']]} | |
end | |
Rack::Handler.default.run routes, Port: 3000 |
参考: http://blog.eiel.info/blog/2014/03/30/action-dispatch/
所感
Railsにおけるルーティングのリファクタリング方法を書きました。
あまり話題に上がらないテーマですが、皆さんはどのように管理しているのでしょうか。