動かざることバグの如し

近づきたいよ 君の理想に

Railsでmodelをフォルダ階層にしてきれいにする方法

環境

Railsのモデルディレクトリ荒れる問題

Railsはデフォルトだとapp/modelsにモデルファイルを生成する。

それはルールなので別にいいのだが、プロジェクトが成長するに従って10ならいいが50を超えてくるとかなりキツくなる。

そこでディレクトリをapp/models以下に作成して階層化したい。ディレクトリ別にグルーピングしたい

が、ググってもDB構造の階層化ばかりでフォルダを分ける記事が全然見当たらなかったのでメモ

サブディレクトリ化していない例

例えばこれは通常のPostモデル app/models/post.rb

class Post < ApplicationRecord
end

.model_name でそのモデルの命名規則などがわかる

irb(main):014:0> Post.model_name
=> 
<ActiveModel::Name:0x0000000114a6d2f8    
 @collection="posts",                     
 @element="post",                         
 @human="Post",                           
 @i18n_key=:post,                         
 @klass=Post (call 'Post.connection' to establish a connection),
 @name="Post",                            
 @param_key="post",                       
 @plural="posts",                         
 @route_key="posts",                      
 @singular="post",                        
 @singular_route_key="post",
 @uncountable=false>

まあそうなるわな

サブディレクトリ化する

まずは app/models/admin.rb を作成して以下

module Admin
end

で app/models/admin/post.rb を作成して以下

class Admin::Post < ApplicationRecord
end

これだけで後は Admin::Post でアクセス出来る

irb(main):021:0> Admin::Post.new
   (0.1ms)  SELECT sqlite_version(*)
=> #<Admin::Post:0x0000000115264830 id: nil, title: nil, date: nil, body: nil, created_at: nil, updated_at: nil>

だがこれだけではめでたしめでたしとはならない。form_withでフォームを作成するともれなくURLが想定ではなくなってバグるため。

もともとPostAdmin::Postディレクトリ作っただけなので、ルーティングやコントローラー周りは階層化するつもりはない。

しかし同様に .model_name すると route_key が「admin_posts」になってしまってるじゃないか。。。

irb(main):014:0> Post.model_name
=> 
<ActiveModel::Admin::Post:0x00000001141bd180    
@collection="admin/posts",                                            
@element="post",                                                      
@human="Post",                                                        
@i18n_key=:"admin/post",                                              
@klass=Admin::Post (call 'Admin::Post.connection' to establish a connection),
@name="Admin::Post",                                                  
@param_key="admin_post",                                              
@plural="admin_posts",                                                
@route_key="admin_posts",                                             
@singular="admin_post",                                               
@singular_route_key="admin_post",                                     
@uncountable=false>                                                   

解決方法

app/models/admin.rb にメソッドを追加する

module Admin
  def self.use_relative_model_naming?
    true
  end
end

Railsをリロードすると route_keyposts に戻っている!

irb(main):023:0> Admin::Post.model_name
=> 
<ActiveModel::Name:0x0000000117305ee0                                 
 @collection="admin/posts",                                            
 @element="post",                                                      
 @human="Post",                                                        
 @i18n_key=:"admin/post",                                              
 @klass=Admin::Post (call 'Admin::Post.connection' to establish a connection),
 @name="Admin::Post",                                                  
 @param_key="post",                                                    
 @plural="admin_posts",                                                
 @route_key="posts",                                                   
 @singular="admin_post",                                               
 @singular_route_key="post",                                           
 @uncountable=false,                                                   
 @unnamespaced="Post">                                                 

他にも色々見えないところで変わってるらしく、ただモデルの場所をフォルダ分け考えているなら全然あり

備考

ちなみにRailsのモデルを階層化しろって話はRails1.2時代から指摘されてたっぽい。ウケる

errtheblog.com