Railsでモデルクラスの名前をネームスペースとして流用してはいけません（warning: toplevel constantが発生します）

## 2019.8.21追記：Ruby 2.5からはこの問題が発生しなくなりました
Ruby 2.5からは定数探索のルールが変わったため、この記事で書いているような問題は発生しなくなりました。

参考：[サンプルコードでわかる！Ruby 2\.5の主な新機能と変更点 Part 1 \- Qiita](https://qiita.com/jnchito/items/f182b6f0093a6a3701a1#%E3%83%88%E3%83%83%E3%83%97%E3%83%AC%E3%83%99%E3%83%AB%E3%81%AE%E5%AE%9A%E6%95%B0%E6%8E%A2%E7%B4%A2%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F)

Ruby 2.5環境だと、この記事で使用したテストケースはどちらも正常に実行されます。

```console 
# Ruby 2.5環境 + Rails 5.2で全specを実行した場合
$ bundle exec rspec
blogs_controller_spec.rb loaded
BlogsController loaded
staff/blogs_controller_spec.rb loaded
Staff model loaded
Staff::BlogsController loaded

Randomized with seed 46529
....

Finished in 0.09461 seconds (files took 3.17 seconds to load)
4 examples, 0 failures

Randomized with seed 46529
```

```console 
# Ruby 2.5環境 + Rails 5.2で個別にspecを実行した場合
$ bundle exec rspec ./spec/controllers/staff/blogs_controller_spec.rb  
staff/blogs_controller_spec.rb loaded
Staff model loaded
Staff::BlogsController loaded

Randomized with seed 16532
..

Finished in 0.05565 seconds (files took 3.16 seconds to load)
2 examples, 0 failures

Randomized with seed 16532
```

参考用として、Ruby 2.5 + Rails 5.2で実行可能なサンプルコードを以下にアップしています。

- https://github.com/JunichiIto/namespaced-controller-sandbox/tree/rails-5-2

ついでに書いておくと、Rails 6はRuby 2.5以上が必須になるので、自ずとこの問題は発生しなくなります。
以下はRails 6.0版のサンプルコードです。

- https://github.com/JunichiIto/namespaced-controller-sandbox/tree/rails-6-0

（さらに言うと、Rails 6ではZeitwerkという新しいオートロードシステムが導入されたので、本記事の説明もRails 6では当てはまらない部分があります。Zeitwerk版のオートロードシステムに関する詳細は[こちらのRailsガイド](https://railsguides.jp/autoloading_and_reloading_constants.html)を参照してください）

というわけで、以下の本文は **Ruby 2.4以前の環境で発生する事象** として読んでください。

## TL;DR（最初に結論）

- モデルクラスの名前（`Admin` など）をネームスペースに流用する（たとえば `Admin::BlogsController`のようなクラスを作る）と、"warning: toplevel constant"という警告が発生し、エラーが起きる原因になります。
- もう少し厳密に言うと、 `BlogsController` のような **同名のクラスがトップレベル（ネームスペース無し）に存在している場合にこの警告が出ます。**
- 無用なトラブルの発生を防ぐため、ネームスペースは極力モデルクラスとかぶらない名前を付けてください（`Admins::BlogsController` など）

## トラブルが発生するケース

ここではサンプルアプリケーションとして架空のブログアプリケーションを考えます。
仕様は以下の通りです。

- ブログは下書きとして保存することができる
- 一般ユーザーは下書きのブログが見えない
- スタッフは下書きのブログも見える
- 一般ユーザーは `http://localhost:3000/blogs` のようなURLにアクセスする
- スタッフは `http://localhost:3000/staff/blogs` のようなURLにアクセスする

以下はこのアプリケーションのソースコードです。
（デバッグ用の puts を最初から仕込んであります）

### モデル

```ruby:app/models/blog.rb
class Blog < ActiveRecord::Base
  scope :without_draft, -> { where(draft: false) }
end
```

```ruby:app/models/staff.rb
class Staff < ActiveRecord::Base
  puts "Staff model loaded"
end
```

### ルーティング

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :blogs
  namespace :staff do
    resources :blogs
  end
  root to: 'blogs#index'
end
```

### コントローラ

ここではindexとshowのみを記載します。

```ruby:app/controllers/blogs_controller.rb
class BlogsController < ApplicationController
  puts "BlogsController loaded"
  
  before_action :set_blog, only: [:show, :edit, :update, :destroy]

  def index
    @blogs = Blog.without_draft.all
  end

  def show
  end

  private
    def set_blog
      @blog = Blog.without_draft.find(params[:id])
    end
end
```

```ruby:app/controllers/staff/blogs_controller.rb
class Staff::BlogsController < ApplicationController
  puts "Staff::BlogsController loaded"
  
  before_action :set_blog, only: [:show]

  def index
    @blogs = Blog.all
  end

  def show
  end

  private
    def set_blog
      @blog = Blog.find(params[:id])
    end
end
```

### テストコード（コントローラスペック）

前述の仕様を検証するため、次のようなテストコードを書きました。

```ruby:spec/factories/blogs.rb
FactoryGirl.define do
  factory :blog do
    title "MyString"
    content "MyText"
    draft false
    trait :draft do
      draft true
    end
  end
end
```

```ruby:spec/controllers/blogs_controller_spec.rb
require 'rails_helper'

puts "blogs_controller_spec.rb loaded"

RSpec.describe BlogsController, type: :controller do
  let!(:blog) { create :blog, :draft }
  describe 'GET #index' do
    it '下書きのブログは含まれない' do
      get :index
      expect(assigns(:blogs)).to eq []
    end
  end
  describe 'GET #show' do
    it '下書きのブログは表示できない' do
      expect { get :show, id: blog }.to raise_error ActiveRecord::RecordNotFound
    end
  end
end
```

```ruby:spec/controllers/staff/blogs_controller_spec.rb
require 'rails_helper'

puts "staff/blogs_controller_spec.rb loaded"

RSpec.describe Staff::BlogsController, type: :controller do
  let!(:blog) { create :blog, :draft }
  describe 'GET #index' do
    it '下書きのブログも含まれる' do
      get :index
      expect(assigns(:blogs)).to eq [blog]
    end
  end
  describe 'GET #show' do
    it '下書きのブログも表示できる' do
      get :show, id: blog
      expect(assigns(:blog)).to eq blog
    end
  end
end
```

### テストコードの実行結果（失敗）

上記のテストコードは問題なくパスするはずですが、なぜか以下のようなエラーが出て失敗しました。

```
$ bundle exec rspec
blogs_controller_spec.rb loaded
BlogsController loaded
staff/blogs_controller_spec.rb loaded
Staff model loaded
/(your-app)/spec/controllers/staff/blogs_controller_spec.rb:4: warning: toplevel constant BlogsController referenced by Staff::BlogsController

Randomized with seed 38796
FF..

Failures:

  1) BlogsController GET #index 下書きのブログも含まれる
     Failure/Error: expect(assigns(:blogs)).to eq [blog]
     
       expected: [#<Blog id: 1, title: "MyString", content: "MyText", created_at: "2016-02-11 00:14:31", updated_at: "2016-02-11 00:14:31", draft: true>]
            got: #<ActiveRecord::Relation []>
     
       (compared using ==)
     
       Diff:
       @@ -1,2 +1,2 @@
       -[#<Blog id: 1, title: "MyString", content: "MyText", created_at: "2016-02-11 00:14:31", updated_at: "2016-02-11 00:14:31", draft: true>]
       +[]
       
     # ./spec/controllers/staff/blogs_controller_spec.rb:9:in `block (3 levels) in <top (required)>'

  2) BlogsController GET #show 下書きのブログも表示できる
     Failure/Error: @blog = Blog.without_draft.find(params[:id])
     
     ActiveRecord::RecordNotFound:
       Couldn't find Blog with 'id'=1 [WHERE "blogs"."draft" = ?]
     # ./app/controllers/blogs_controller.rb:68:in `set_blog'
     # ./spec/controllers/staff/blogs_controller_spec.rb:14:in `block (3 levels) in <top (required)>'

Finished in 0.0915 seconds (files took 1.33 seconds to load)
4 examples, 2 failures

Failed examples:

rspec ./spec/controllers/staff/blogs_controller_spec.rb:7 # BlogsController GET #index 下書きのブログも含まれる
rspec ./spec/controllers/staff/blogs_controller_spec.rb:13 # BlogsController GET #show 下書きのブログも表示できる

Randomized with seed 38796	
```

実行結果をよく見てみると、どちらも `staff/blogs_controller_spec.rb` でエラーが発生しています。
そこで、このテストコードだけを単体で動かしてみることにしました。

```
$ bundle exec rspec ./spec/controllers/staff/blogs_controller_spec.rb  
staff/blogs_controller_spec.rb loaded
Staff model loaded
Staff::BlogsController loaded

Randomized with seed 38943
..

Finished in 0.07659 seconds (files took 1.43 seconds to load)
2 examples, 0 failures

Randomized with seed 38943
```

あれあれ？今度はなぜかパスしてしまいました。
これは一体なぜなんでしょうか？？

## 全体実行時にエラーが発生する原因

端的に言うと、スタッフ用のテストコード内で、`Staff::BlogsController` ではなく、 `BlogsController` （一般ユーザー用）が実行されてしまうためです。

ではなぜ、`Staff::BlogsController`が呼び出されないのかというと、クラスが呼び出される順番と、Rubyの定数（＝クラス名）探索の仕様を理解する必要があります。

まずデバッグ用に仕込んだ puts の出力結果を載せます。

```
blogs_controller_spec.rb loaded
BlogsController loaded
staff/blogs_controller_spec.rb loaded
Staff model loaded
/(your-app)/spec/controllers/staff/blogs_controller_spec.rb:4: warning: toplevel constant BlogsController referenced by Staff::BlogsController
```

上の出力結果を参考にしながら、RSpecの全体実行時に何が起きているのかを順に表すとこうなります。

1. RSpecがspecディレクトリ内のファイルを順に読み込む。今回であれば `blogs_controller_spec.rb`、`staff/blogs_controller_spec.rb` の順に読み込む。
2. `blogs_controller_spec.rb` 内には `RSpec.describe BlogsController, type: :controller do` の記述がある。このタイミングで `BlogsController` が読み込まれる。（ **blogs_controller_spec.rb loaded** 、 **BlogsController loaded** ）
3. 続いて、`staff/blogs_controller_spec.rb` の読み込みに移る。このファイルには `RSpec.describe Staff::BlogsController, type: :controller do` の記述がある。（ **staff/blogs_controller_spec.rb loaded** ）
4. ここでまず `Staff::BlogsController` の `Staff` クラスが読み込まれる。（ **Staff model loaded** ）
5. 次に、Rubyは `Staff::BlogsController` の読み込み（定数探索）を試みる。この時点ではまだ `Staff::BlogsController` は実行環境内では読み込まれていないため、Rubyは「`Staff` クラスは `BlogsController` という定数（クラス）を定義していない」と見なす。
6. `Staff` クラスには `BlogsController` が見つからなかったので、Rubyは `Staff` クラスの親クラス（正確には `Staff.ancestors` で返されるクラス）を順にたどっていき、それぞれのクラス内に `BlogsController` が定義されていないかチェックする。
7. 親クラスを順にたどっていくと、`Object` クラスにぶつかる。実はトップレベル（ネームスペース無し）のクラスは `Object` クラス内の定数として定義される。上記 2 で読み込まれた `BlogsController` も `Object` クラス内に定義されている。よって、ここで `BlogsController` が発見される。（ **ここが意図しない挙動！！** ）
8. `BlogsController` が見つかったので、Ruby は `Staff::BlogsController` がトップレベルの `BlogsController` を参照していると見なして、定数探索を終了する。ただし、本来意図しない読み込みである可能性もあるため、"warning: toplevel constant BlogsController referenced by Staff::BlogsController" という **警告を出力** する。
9. 結局、スタッフ用の `Staff::BlogsController` は読み込まれず、一般ユーザー用の `BlogsController` が使われるため、テストが失敗する。

上の処理フローで重要なポイントは以下の通りです。

1. 一般ユーザー用の `BlogsController` が先に読み込まれている。
2. `BlogsController` はトップレベルで定義されているため、`Object` クラスの定数（`Object::BlogsController`）として扱われる。
3. スタッフ用の `Staff::BlogsController` はまだ読み込まれていない。
4. 指定されたネームスペース（今回であれば`Staff`）内に対象となる定数（クラス）が見つからない場合、Rubyはネームスペースの親クラスを順に探索していく。その結果、一般ユーザー用の `BlogsController`（`Object::BlogsController`）が見つかる

特に2番目と4番目ののポイントが、多くのRubyプログラマの普段意識しないところだと思います。

## 単体実行時にパスする原因

一方、`staff/blogs_controller_spec.rb` を単体で実行すると、なぜかパスしてしまいました。
これはなぜなんでしょうか？

先ほどと同様、デバッグ用に仕込んだ puts の結果を載せます。

```
staff/blogs_controller_spec.rb loaded
Staff model loaded
Staff::BlogsController loaded
```

次に、RSpecの単体実行時に起きていることを説明します。

1. RSpecが `staff/blogs_controller_spec.rb` を読み込む。このファイルには `RSpec.describe Staff::BlogsController, type: :controller do` の記述がある。（ **staff/blogs_controller_spec.rb loaded** ）
2. ここでまず `Staff::BlogsController` の `Staff` クラスが読み込まれる。（ **Staff model loaded** ）
3. 次に、Rubyは `Staff::BlogsController` の読み込み（定数探索）を試みる。この時点ではまだ `Staff::BlogsController` は実行環境内では読み込まれていないため、Rubyは「`Staff` クラスは `BlogsController` という定数（クラス）を定義していない」と見なす。
4. `Staff` クラスには `BlogsController` が見つからなかったので、Rubyは `Staff` クラスの親クラス（正確には `Staff.ancestors` で返されるクラス）を順にたどっていき、 `BlogsController` が定義されていないかチェックする。
5. しかし、親クラスをたどっても `BlogsController` は見つからない。よって、Rubyはいったん定数探索に失敗する。
6. Rubyは `Staff` クラスの `const_missing` メソッドを呼び出す。厳密には `ActiveSupport::Dependencies` の `const_missing` メソッドが呼び出される。（ **ここがさっきと違うところ！！** ）
7. `const_missing` メソッド内では `autoload_paths` で定義された各ディレクトリ内を探索して、"staff/blogs_controller" という名前に合致するファイルがないか探す。ここで `app/controllers/staff/blogs_controller.rb` が見つかる。
8.  `app/controllers/staff/blogs_controller.rb` をrequireする。ここでスタッフ用の  `Staff::BlogsController` が読み込まれる。（ **Staff::BlogsController loaded** ）
9. `Staff::BlogsController` が無事に見つかったので、Rubyは定数探索を終了する。
10. スタッフ用の `Staff::BlogsController` が読み込まれているので、テストは正常にパスする。

上の処理フローで重要なポイントは以下の2点です。

- Rubyは `Staff::BlogsController` の探索にいったん失敗する。
- しかし、`const_missing` メソッドを利用して、Railsが `Staff::BlogsController` を自動的に見つけ出してくれる。

ちなみに、上記ステップ 2 の `Staff` クラスも `const_missing` メソッド経由で読み込まれています。

参考までにRubyMineを使って `Staff::BlogsController` が読み込まれるまでのコールスタックを表示してみました。
これを見ると、 `const_missing` メソッドが呼び出されているのがわかると思います。

![Screen Shot 2016-02-11 at 8.43.02.png](https://qiita-image-store.s3.amazonaws.com/0/7465/e7c69cc6-a03d-e4b1-aabf-4f661d0274fa.png "Screen Shot 2016-02-11 at 8.43.02.png")

さて、原因はわかりましたが、どうやって解決するのが良いでしょうか？

## 解決策１：モデル名とかぶらないようにネームスペースを変更する（オススメ）

若干修正の手間はかかりますが、テストコード以外の場所でも予期しないトラブルが発生したりしないよう、モデル名とは異なるネームスペースに変更することをオススメします。

たとえば、`staff` を `staffs` に変更するなどです。

### ルーティング

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :blogs
  namespace :staffs do
    resources :blogs, only: %i(index show)
  end
  root to: 'blogs#index'
end
```

### スタッフ用コントローラ

```ruby:app/controllers/staffs/blogs_controller.rb
class Staffs::BlogsController < ApplicationController
  puts "Staffs::BlogsController loaded"
  
  before_action :set_blog, only: [:show]

  def index
    @blogs = Blog.all
  end

  def show
  end

  private
    def set_blog
      @blog = Blog.find(params[:id])
    end
end
```

### テストコード（スタッフ用コントローラ）

```ruby:spec/controllers/staffs/blogs_controller._specrb
require 'rails_helper'

puts "staffs/blogs_controller_spec.rb loaded"

RSpec.describe Staffs::BlogsController, type: :controller do
  let!(:blog) { create :blog, :draft }
  describe 'GET #index' do
    it '下書きのブログも含まれる' do
      get :index
      expect(assigns(:blogs)).to eq [blog]
    end
  end
  describe 'GET #show' do
    it '下書きのブログも表示できる' do
      get :show, id: blog
      expect(assigns(:blog)).to eq blog
    end
  end
end
```

こうすると、全体実行してもテストはパスします。

```
$ bundle exec rspec
blogs_controller_spec.rb loaded
BlogsController loaded
staffs/blogs_controller_spec.rb loaded
Staffs::BlogsController loaded
Staff model loaded

Randomized with seed 160
....

Finished in 0.09497 seconds (files took 1.4 seconds to load)
4 examples, 0 failures

Randomized with seed 160
```

### ネームスペースを変えるとテストがパスする理由

エラーが起きない理由はこうです。

1. RSpecがspecディレクトリ内のファイルを順に読み込む。今回であれば `blogs_controller_spec.rb`、`staffs/blogs_controller_spec.rb` の順に読み込む。
2. `blogs_controller_spec.rb` 内には `RSpec.describe BlogsController, type: :controller do` の記述がある。このタイミングで `BlogsController` が読み込まれる。（ **blogs_controller_spec.rb loaded** 、 **BlogsController loaded** ）
3. 続いて、`staffs/blogs_controller_spec.rb` の読み込みに移る。このファイルには `RSpec.describe Staffs::BlogsController, type: :controller do` の記述がある。（ **staffs/blogs_controller_spec.rb loaded** ）
4. `Staffs` はどこにも定義されていないが、Railsの自動モジュール機能（[参照](https://railsguides.jp/autoloading_and_reloading_constants_classic_mode.html#%E8%87%AA%E5%8B%95%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB)）により、自動的に **モジュール** として定義される。
5. 次に、Rubyは `Staffs::BlogsController` の読み込み（定数探索）を試みる。この時点ではまだ `Staffs::BlogsController` は実行環境内では読み込まれていないため、Rubyは「`Staffs` モジュールは `BlogsController` という定数（クラス）を定義していない」と見なす。
6. また、`Staffs` モジュールは `Object` クラスを継承していない（正確には `Staffs.ancestors` に `Object` クラスが含まれない）ので、親クラスをたどっても `BlogsController` が見つからない。よって、Rubyはいったん定数探索に失敗する。（ **ここがさっきと違うところ！！** ）
7. Rubyは `Staffs` モジュールの `const_missing` メソッドを呼び出す。厳密には `ActiveSupport::Dependencies` の `const_missing` メソッドが呼び出される。
8. `const_missing` メソッド内では `autoload_paths` で定義された各ディレクトリ内を探索して、"staffs/blogs_controller" という名前に合致するファイルがないか探す。ここで `app/controllers/staffs/blogs_controller.rb` が見つかる。
9.  `app/controllers/staffs/blogs_controller.rb` をrequireする。ここでスタッフ用の  `Staffs::BlogsController` が読み込まれる。（ **Staffs::BlogsController loaded** ）
10. `Staffs::BlogsController` が無事に見つかったので、Rubyは定数探索を終了する。
11. スタッフ用の `Staffs::BlogsController` が読み込まれているので、テストは正常にパスする。

簡潔にまとめると、`Staff` はクラスだったので、`Object` クラスが探索対象になりましたが、`Staffs` はモジュールなので `Object` クラスが探索対象にならない、というのが大きな違いです。

## 解決策２：Staffクラスを読み込んだ際に、ネームスペース配下のクラスを強制的に読み込む（未検証）

解決策１はエラーは起きなくなりますが、ネームスペースを変更するため、これまで使っていた URL も変わります。
（`http://localhost:3000/staff/blogs` が `http://localhost:3000/staffs/blogs` に変わる）
すでにリリース済みのアプリケーションだと、URLが突然変わってしまうのはあまり望ましくありません。

その場合は、以下のようにネームスペース配下のクラスを強制的に読み込むようにすると良いかもしれません。

```ruby:app/models/staff.rb
class Staff < ActiveRecord::Base
  puts "Staff model loaded"
end
Dir[File.join(Rails.root, 'app/controllers/staff/*.rb')].each { |f| require_dependency f }
```

こうしておけば、`Staff::BlogsController` を参照したときに、以下のような動きになるのでエラーが起きなくなるはずです。

1. `Staff` クラスが読み込まれる
2. `Staff` クラスの読み込みが終わると、その直後に同一ネームスペース配下のクラス（主にコントローラ）が読み込まれる。
3. `BlogsController` という定数が `Staff` クラス内に見つかるので、定数探索がそこで終わる。


以下はテストの実行結果です。

```
$ bundle exec rspec
blogs_controller_spec.rb loaded
BlogsController loaded
staff/blogs_controller_spec.rb loaded
Staff model loaded
Staff::BlogsController loaded

Randomized with seed 31099
....

Finished in 0.09575 seconds (files took 1.44 seconds to load)
4 examples, 0 failures

Randomized with seed 31099
```

ただし、これはジャストアイデアであり、実務で開発しているアプリケーションで試したことはないので、もしかすると別の問題を引き起こしたりするかもしれません。
僕は普段、解決策１を使っています。

なお、上記のコードで使っている `require_dependency` については下記のドキュメントを参照してください。

https://railsguides.jp/autoloading_and_reloading_constants_classic_mode.html#require-dependency

## 解決策３：テスト実行前に強制的にクラスを読み込む（非推奨）

別のアイデアとして、`spec/rails_helper.rb` 内で強制的にネームスペースに属するクラスを読み込んでしまう（つまりテスト実行前に読み込んでしまう）、という案もあります。

```ruby:spec/rails_helper.rb
Dir[File.join(Rails.root, 'app/controllers/staff/*.rb')].each { |f| require_dependency f }

RSpec.configure do |config|
  # ...
```

ただ、これだと確かにテストはパスするようになりますが、開発環境や本番環境で動かしたときに、予期しないタイミングで "warning: toplevel constant" が発生するかもしれません。（発生しないかもしれませんが、僕にはハッキリわかりません）
なので、テスト環境だけに特殊な仕掛けを入れるのはあまり得策ではない気がします。

## サンプルコード

今回使用したアプリケーションのソースコードはGitHubに置いてあります。

[JunichiIto/namespaced-controller-sandbox](https://github.com/JunichiIto/namespaced-controller-sandbox)

- テストが失敗するバージョン => [masterブランチ](https://github.com/JunichiIto/namespaced-controller-sandbox)
- ネームスペースを変更したバージョン => [use-different-nameブランチ](https://github.com/JunichiIto/namespaced-controller-sandbox/tree/use-different-name)
- 強制的にクラスを読み込むバージョン => [force-requireブランチ](https://github.com/JunichiIto/namespaced-controller-sandbox/tree/force-require)

## まとめ

僕はもともと社内で「モデル名と同じネームスペースを付けるとトラブルの原因になるよ」と聞いていたので、いつも解決策１の方法でアプリケーションを作っていました。
しかし、そういった予備知識が無いと、どちらも同じ名前を付けてしまうことは結構起きそうな気がします。

もしこの話を知らなかった人がいたら、これからRailsアプリケーションを開発するときにモデル名とネームスペースの設計に十分注意してください。

### 参考文献

- [Rspecでwarning: toplevel constantが出る現象に対応する - Qiita](http://qiita.com/RyosukeKawamura/items/976cbc5c55c8ee1fe47c)
  - もともとはこの記事のエラー原因を書こうとしていました。
- [書籍「プログラミング言語 Ruby」](http://amzn.to/1Wfiyrf)
  - 「7.9 定数の探索」という章が参考になりました。
- [定数の自動読み込みと再読み込み | Rails ガイド](https://railsguides.jp/autoloading_and_reloading_constants_classic_mode.html)
  - Railsの自動読み込みや自動モジュールに関する仕様が詳しく説明されています。
- [Warning: Toplevel Constant XYZ Referenced Admin:XYZ – code.dblock.org | tech blog](http://code.dblock.org/2012/01/08/warning-toplevel-constant-xyz-referenced-adminxyz.html)
- [warning: toplevel constant SomeController referenced by Admin::SomeController - Google グループ](https://groups.google.com/forum/#!topic/rubyonrails-core/urXofuENLss)
  - 同じ原因による、同じエラーが発生しています。