3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

`rails new` せずに小さなRailsアプリを作成

Last updated at Posted at 2019-09-15

Railsアプリを作る際は最初に rails new を実行する。すると大量のファイルが作られ、その中には様々な設定が最初から書かれている。はっきり言ってどれが何をしているのかよく分からない。

そこで rails new せずに、サーバー実行時のエラーを見ながら少しずつファイルや設定を加えてみた。 "Hello, world!\n" の14バイトのテキストを返すだけのアプリができるまでをまとめる。

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)

Gemfile
# 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 という定数を定義する必要があった。…と思ったら、文字列として含んでいれば十分だった。

bin/rails
#!/usr/bin/env ruby
# APP_PATH

この状態でサーバーを起動すると、ヘルプは表示されなくなるが他のことも起きない。

rails/commands の読み込みと、 APP_PATH の指すファイル準備

サーバー起動処理(railsコマンドの実行)へ進むには、 rails/commands を読み込ませる必要がある。また、その中で定数 APP_PATH のファイルも読み込まれるので、一般的なパスである config/application.rbとりあえず空ファイルで作っておく。

bin/rails
#!/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)
railties-6.0.0/lib/rails/commands/server/server_command.rb#L133-152
      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アプリを書く。

config/application.rb
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行を加える。

config.ru
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)
railties-6.0.0/lib/rails/commands/server/server_command.rb#L76-86
      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.rurequire_relative 'config/environment' をしていないため、抜けている処理がそこにあると考えられる。探したら怪しい1行があったので、直接 config.ru に加える。

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 のみを読み込む。

config/application.rb
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アプリ初期化後なら設定が反映された。

config.ru
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 でなくても構わない。

app/controllers/mini_controller.rb
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
- -> /

これらのルーティングはこっそり加えられている。

railties-6.0.0/lib/rails/application/finisher.rb#L74-86
      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タスク全般
  • 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 を読み込むことで行う。そのため、最低でも以下のようにファイル分割する必要がある。

config.ru
require_relative 'config/environment'
run Rails.application
config/environment.rb
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 railsaction*active* などに依存しているが、そのほとんどは今回使用していない。エラーに出てきたのは railties なので、それに差し替える。

Gemfile
# 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_ENVSECRET_KEY_BASE はHerokuで勝手に設定してくれる。

heroku create
git push heroku master
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?