Railsアプリを作る際は最初に rails new
を実行する。すると大量のファイルが作られ、その中には様々な設定が最初から書かれている。はっきり言ってどれが何をしているのかよく分からない。
そこで rails new
せずに、サーバー実行時のエラーを見ながら少しずつファイルや設定を加えてみた。 "Hello, world!\n" の14バイトのテキストを返すだけのアプリができるまでをまとめる。
- Gitリポジトリ
-
https://github.com/hmmnrst/rails_mini_app
- 本記事の内容はmasterブランチにある
- 派生ブランチを各種実験に使っている
-
https://github.com/hmmnrst/rails_mini_app
- 参考資料
TL;DR
必要なファイルまとめ
一般的なパス | パスの定義元 | 書くべき処理 |
---|---|---|
bin/rails |
railties/lib/rails/app_loader.rb |
定数 APP_PATH を定義した後、 rails/commands を読み込む |
config/application.rb |
ユーザー定義定数 APP_PATH
|
Rails::Application を継承したクラスを定義する |
config.ru |
rails server --config=XXX |
Railsアプリを初期化、実行する (今回はルーティングも書いた) |
app/controllers/xxx_controller.rb |
ルーティング・自動読み込み | リクエストに応じて処理する |
今回のアプリでは、上記ファイルは合計で20行しかない。
$ git ls-files | xargs wc --lines
5 .gitignore
7 Gemfile
135 Gemfile.lock
5 app/controllers/mini_controller.rb
3 bin/rails
8 config.ru
4 config/application.rb
167 total
前準備
Windows Subsystem for Linux上のUbuntu 16.04で試している。
Rubyのバージョン選択
既にrbenvが入っているとする。新しくRuby 2.6.4が出ていたのでインストールする。あまり他に影響を与えたくないので、バージョン指定はこのシェルでのみ有効なようにする。
(cd $(rbenv root)/plugins/ruby-build/ && git pull)
rbenv install --skip-existing 2.6.4
rbenv shell 2.6.4
Gitリポジトリの作成
適宜保存するために作成しておく。
mkdir --parents ~/projects/rails_mini_app
cd ~/projects/rails_mini_app
git init
git commit --allow-empty # 一応、空っぽの時点で保存しておく
Railsのインストール
bundle init
sed --in-place --expression='s/^# *gem/gem/' Gemfile
これで、gem rails
をインストールするGemfileが完成する。(2019/09/14時点の最新版は6.0.0)
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "rails"
不要になったら跡形もなく消せるよう、gemは作業ディレクトリ内にインストールする。git管理は不要なので .gitignore
で除外しておく。
bundle install --path=vendor/bundle
cat << EOF >> .gitignore
/.bundle
/vendor
EOF
Railsアプリ作成
初期状態で確認
早速railsサーバーを起動してみる。
bundle exec rails server
Usage:
rails new APP_PATH [options]
Options:
...
Example:
rails new ~/Code/Ruby/weblog
This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
rails new
のヘルプが表示された。暗にこれを実行しろと言われている。
bin/rails
の作成と、定数 APP_PATH
の定義
ヘルプからでは必要なファイルがわからないので、「Railsの初期化プロセス」を読む。gem以外で最初のファイルは bin/rails
であるらしい。
1.2
railties/lib/rails/app_loader.rb
exec_app
の主な目的は、Railsアプリケーションにあるbin/rails
を実行することです。カレントディレクトリにbin/rails
がない場合、bin/rails
が見つかるまでディレクトリを上に向って探索します。これにより、Railsアプリケーション内のどのディレクトリからでもrails
コマンドを実行できるようになります。
ただし試したところ、適当なコードだと rails new
のヘルプが表示されるままだった。ヘルプを回避するには APP_PATH
という定数を定義する必要があった。…と思ったら、文字列として含んでいれば十分だった。
#!/usr/bin/env ruby
# APP_PATH
この状態でサーバーを起動すると、ヘルプは表示されなくなるが他のことも起きない。
rails/commands
の読み込みと、 APP_PATH
の指すファイル準備
サーバー起動処理(railsコマンドの実行)へ進むには、 rails/commands
を読み込ませる必要がある。また、その中で定数 APP_PATH
のファイルも読み込まれるので、一般的なパスである config/application.rb
をとりあえず空ファイルで作っておく。
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require 'rails/commands'
これでサーバーを起動するとNoMethodErrorが発生する。
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/server/server_command.rb:142:in `block in perform': undefined method `root' for nil:NilClass (NoMethodError)
def perform
extract_environment_option_from_argument
set_application_directory!
prepare_restart
Rails::Server.new(server_options).tap do |server|
# Require application after server sets environment to propagate
# the --environment option.
require APP_PATH
Dir.chdir(Rails.application.root) # ここでエラー
if server.serveable?
print_boot_information(server.server, server.served_url)
after_stop_callback = -> { say "Exiting" unless options[:daemon] }
server.start(after_stop_callback)
else
say rack_server_suggestion(using)
end
end
end
Railsアプリの用意
Rails.application
が無いのが問題らしい。 config/application.rb
の中にRailsアプリを書く。
class MiniApp < Rails::Application
end
これでサーバーを起動すると、親切に次の作業のヒントを出してくれる。
=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
configuration config.ru not found
Exiting
なお、設定ファイルは --config
オプションで指定できるので、 config.ru
でなくてもいい。
config.ru
の用意
空ファイルを用意するだけだと、サーバー起動時にエラーになる。
=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
Exiting
Traceback (most recent call last):
...
1: from config.ru:2:in `<main>'
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:146:in `to_app': missing run or map statement (RuntimeError)
run
が必要らしいので、実際のアプリに存在する1行を加える。
run Rails.application
これでサーバーを起動すると、またNoMethodErrorが発生する。
=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
Exiting
Traceback (most recent call last):
...
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/server/server_command.rb:80:in `log_to_stdout': undefined method `formatter' for nil:NilClass (NoMethodError)
def log_to_stdout
wrapped_app # touch the app so the logger is set up
console = ActiveSupport::Logger.new(STDOUT)
console.formatter = Rails.logger.formatter # ここでエラー
console.level = Rails.logger.level
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
end
end
Railsアプリの初期化
loggerといえばいつも config/environments/<env>.rb
で何か設定している覚えがある。今回は config.ru
で require_relative 'config/environment'
をしていないため、抜けている処理がそこにあると考えられる。探したら怪しい1行があったので、直接 config.ru
に加える。
Rails.application.initialize!
run Rails.application
これでついにサーバーが起動する。 log/
と tmp/
が作られるので、 .gitignore
で除外しておくといい。
=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
* development - set it to false
* test - set it to false (unless you use a tool that preloads your test environment)
* production - set it to true
[%Y-%m-%d %H:%M:%S] INFO WEBrick 1.4.2
[%Y-%m-%d %H:%M:%S] INFO ruby 2.6.4 (2019-08-28) [x86_64-linux]
[%Y-%m-%d %H:%M:%S] INFO WEBrick::HTTPServer#start: pid=XXXXX port=3000
しかしクライアントからアクセスすると500エラーになる。
[%Y-%m-%d %H:%M:%S] ERROR NameError: uninitialized constant ActionController::Base
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/middleware/static.rb:78:in `ext'
...
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 500 325
- -> /
Rails関連のgemの読み込み
普通のアプリと比較して抜けている処理のうち、 ActionController
の読み込みに関わっていそうなものは、railsのgemの読み込みと考えられる。今回は機能が非常に少ないので rails/all
ではなく action_controller/railtie
のみを読み込む。
require 'action_controller/railtie'
class MiniApp < Rails::Application
end
これでサーバーを起動してクライアントからリクエストすると、RoutingErrorになる。あと一歩のところまで来た。
Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z
ActionController::RoutingError (No route matches [GET] "/"):
...
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 404 0
- -> /
ルーティングとcontrollerを定義
普通は config/routes.rb
に書く(初期化時の読み込み対象)が、ファイルを作らず他の場所に書けないか試した。その結果、Railsアプリ初期化後なら設定が反映された。
Rails.application.initialize!
Rails.application.routes.draw do
root to: 'mini#index', via: :all
match '*any', to: 'mini#index', via: :all
end
run Rails.application
もちろんcontrollerが無いとエラーになるので、ルーティングに対応するものを作成しておく。 app/
直下は自動読み込みの探索対象なので、ディレクトリ名は controllers
でなくても構わない。
class MiniController < ActionController::Base
def index
render plain: "Hello, world!\n"
end
end
これでついにサーバーが 200 OK を返す。
Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z
Processing by MiniController#index as */*
Rendering text template
Rendered text template (Duration: 0.1ms | Allocations: 3)
Completed 200 OK in 17ms (Views: 16.7ms | Allocations: 1269)
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 200 14
- -> /
$ wget --quiet --save-headers --output-document=/dev/stdout http://localhost:3000
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/plain; charset=utf-8
Etag: W/"d9014c4624844aa5bac314773d6b689a"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 211fdde9-cb2b-4d7f-83de-7f7b6bc6619c
X-Runtime: 0.040717
Server: WEBrick/1.4.2 (Ruby/2.6.4/2019-08-28)
Date: %a, %d %b %Y %H:%M:%S %Z
Content-Length: 14
Connection: Keep-Alive
Hello, world!
ルーティングをRails初期化前に書くと無効なようで、development環境ではRailsのwelcome画面が表示された。
Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z
Processing by Rails::WelcomeController#index as */*
Rendering vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/templates/rails/welcome/index.html.erb
Rendered vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/templates/rails/welcome/index.html.erb (Duration: 129.8ms | Allocations: 415)
Completed 200 OK in 183ms (Views: 154.5ms | Allocations: 2171)
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 200 399720
- -> /
これらのルーティングはこっそり加えられている。
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.prepend do
get "/rails/info/properties" => "rails/info#properties", internal: true
get "/rails/info/routes" => "rails/info#routes", internal: true
get "/rails/info" => "rails/info#index", internal: true
end
app.routes.append do
get "/" => "rails/welcome#index", internal: true
end
end
end
その他
動作しないこと
- Rakeタスク全般
- No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
- メッセージにある通り、
Rakefile
を用意すればいい
(例) https://github.com/hmmnrst/rails_mini_app/commit/fc9617c
-
bin/rails
による実行- cannot load such file -- rails/commands (LoadError)
-
bundle exec
を省くための設定が必要- 普通は
config/boot.rb
に記載して読み込み
- 普通は
-
bin/rails
以外のbinスクリプト実行-
require 'rails/commands'
が無い場合、railsのモジュールなどが用意されない - どこかで
config/application.rb
を明示的に読み込むのが楽
-
- ルーティング確認(
bundle exec rails routes
)- 次節を参照
rails routes
を有効化
このコマンドはサーバーを起動せず、設定の読み込みのみを実施する。そのため config.ru
に全て書く方法ではうまくいかない。
「設定の読み込み」は、具体的には config/environment.rb
を読み込むことで行う。そのため、最低でも以下のようにファイル分割する必要がある。
require_relative 'config/environment'
run Rails.application
Rails.application.initialize!
Rails.application.routes.draw do
root to: 'mini#index', via: :all
match '*any', to: 'mini#index', via: :all
end
これで動作する。
$ bundle exec rails routes 2>/dev/null
Prefix Verb URI Pattern Controller#Action
root / mini#index
/*any(.:format) mini#index
Gemの削減
gem rails
は action*
や active*
などに依存しているが、そのほとんどは今回使用していない。エラーに出てきたのは railties
なので、それに差し替える。
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "railties"
これでgemが43個から24個に減った。
ところで、railsの中心的なgemとしてよく "railties" という名前が出てきたが、この語句はどういう意味なのか気になった。 action_controller/railtie
があったので、単数形は "railtie" と思われる。
辞書などを調べてみたところ、どうやら**「枕木」(railroad tie)**らしい。 "tie" だけでも枕木の意味を持つ。レールを支えるもの、なるほど。
Herokuへのデプロイ
普通の手順でデプロイして問題なく動く。必要な環境変数 RAILS_ENV
と SECRET_KEY_BASE
はHerokuで勝手に設定してくれる。
heroku create
git push heroku master