Aikの技術的な進捗部屋

技術的な進捗とか成果とかを細々と投稿するブログです。時々雑記も。

RubyonRails入門- その2

その0に当たる記事はこちら
その1はこちら

Railsに本格的に触れるよ!

前回のその1では、Railsの環境構築でてんやわんやしておりました。
でもそれは過ぎたお話!やっと実際に開発ができますよ…!

今回は、Railsの実践編として簡単なアプリを作って見たいと思います!

はじめに- プロジェクトを立てる

まず初めにターミナルから下記のコマンドを叩き、プロジェクトを作ります。
今回は「test1」という名前でプロジェクトを作ります。

rails new test1

次にコントローラーとビューを作成。
なお、1つのコントローラーに対してビューを複数個作成する事が可能なので…。
今回はwelcomeというコントローラーにビューを2つ(index,show)持たせることにしました。

rails generate controller welcome index show

そしてサーバーを起動!

rails server

それからブラウザを立ち上げてlocalhost:3000にアクセス!

f:id:aik0aaac:20180803155628p:plain

おお!ちゃんと動いてますね…!
試しに先ほど作ったビューへアクセスしてみます。

f:id:aik0aaac:20180803155645p:plain

ちゃんと出来てる…!感動!
この程度でって感じもありますが…あのRuby on Railsを使って今、1からアプリを作ってるんだと思うと感慨深いです(´;ω;`)ウッ…!

さて…お次は??

次に- 何したらいいか分からない

うん…何というか、何作ったらいいかわかんなくなりました…。
データベースを作れば色々できると思ったのですが、肝心のデータベースの扱い方がわからず…。

や、一応ちょっと作ってはみたのですよ。ブラウザからコマンドを実行できるやつ。

f:id:aik0aaac:20180803155721p:plain f:id:aik0aaac:20180803155746p:plain
こんな感じ

そして、ここからデータベースに格納されてるコマンドを呼び出して、ボタンポチったら実行…と言う風にしたかったのですがね。
データベースの扱い方が分からないのでは…うん、どうしようもない。

色々と入門サイトを見てると、モデルを作れば〜とかと書いてはありますが…。
これは、もう一度体系的に学ぶ必要がありそうです…。

今後について

とりあえずは、データベース周りの事にも触れられるRailsアプリケーションを、どこかの入門サイトを参考にしながら一から作りたいなと思ってます。

余談- 今回出て来た用語について

前回と同様に、今回出てきた初見の用語に関して、ちょっと調べてみました。
今回は記事内に出てきたものだけでない単語についてもまとめてあります。
Railsに関することについてもまとめましたが、インターネットの基礎技術的な部分についても触れています。

また、前回と同様に今回調べた際の参考記事のリンクを貼っておきます。
インターネット用語1分解説~ポート番号とは~ - JPNIC
デフォルトでrails serverを--bind=0.0.0.0付きで起動する方法 - @テク野路ジーロード
RailsでポートとIPアドレスを指定する方法
RackサーバーのPumaについて調べてみる - ゆーじのろぐ

第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社
Rackとは何か
Rack の話 | 初心者向け | DoRuby

localhostとは?

ローカルホストとは、通信ネットワークにおける位置関係を示す用語の一つで、利用者が現在操作している手元のコンピュータや端末のこと。通信相手のコンピュータは「リモートホスト」(remote host)という。
ネットワークにおける「自分自身」を意味するもので、IPアドレス(IPv4)では「127.0.0.1」が、IPv6アドレスでは「::1」が、ホスト名には慣用的に「localhost」が用いられる。
自らがネットワーク上に提供しているサーバ機能などに自分でアクセスする際などに使われる。

引用元: IT用語辞典「ローカルホストとは」

Railsでサーバーを立ち上げた際のアクセスURLの内、「localhost」の部分は自分自身の端末のIPアドレス、「:3000」はポート番号というわけですね。

ちなみにこのIPアドレスやポート番号は、rails sでの起動時にオプションを付けることで、いじれるみたいです。
IPアドレスを変えたい場合は--binding 任意のIPアドレス番号もしくは-b 任意のIPアドレス番号で、ポート番号を変えたい場合は-p 任意のポート番号で変更できるみたいです。

Rackとは?

rack | RubyGems.org | your community gem host

WSGIとは,PythonのためのWebサーバとWebアプリケーション/フレームワーク間の標準インターフェースを定める「仕様」です。
WSGIが提唱された背景には,当時既にPython製のWebアプリケーションフレームワークが百花繚乱…言い方を変えると乱立している状況だったことがあります。
フレームワークは多数存在していたものの,フレームワークの実装は特定のWebサーバに依存していることが多く,使用したいフレームワークの為に環境を制限される,あるいは使用したいWebサーバの為にフレームワークを縛られる,ということが往々にしてありました。
とはいえ,多数存在するサーバとアプリケーション/フレームワークがお互いに複数の環境に対応するためには,開発者にも結構な負担がかかります。
そのため,両者間の標準インターフェースが提唱され,WSGIに対応しているフレームワークと,WSGIを介して連携できるWebサーバとをユーザが好みの組み合わせで使えるようになったのです。

RackはこのWSGIに影響されて開発された,Rubyにおけるサーバとアプリケーション/フレームワーク間のインターフェースの役割を果たすライブラリです。
(中略)つまり,様々なアプリケーションサーバフレームワークが開発されても,双方がRackを使用してインターフェース部分を実装していさえすれば,既存のWebアプリケーションをサーバ側の構成を変えることなく新しいフレームワークでリプレイスしたり,あるフレームワークで実装されたアプリケーションを様々な環境に移したり,といったことが容易になります。
この「インターフェースが統一されていれば,サーバやフレームワークの組み合わせは自由である」ということが,選択肢の広がった最近のRubyにおけるWeb開発環境にとって重要な意味を持っているのです。

引用元: 第23回 Rackとは何か(1)Rackの生まれた背景

非常に分かりやすく説明されてあったので、ついつい長文引用してしまいました…。
簡単にまとめると、Rack=Rubyにおけるサーバとアプリケーション/フレームワーク間のインターフェースの役割を果たすライブラリということでしょうか。

なお、私たちがRailsのサーバー起動時に使うコマンドrails sは、内部では「Rack::ServerのサブクラスであるRails::Serverをstartする」という処理をしているそうです。
このサブクラス、Rails::Serverでは下記の様なRailsサーバー起動用の処理をしているとのこと。

  • opt_parserをオーバーライドしてrails s特有のオプションを定義
  • デフォルトポートを3000に
  • RAILS_ENVに応じた処理を定義

なお、rackupというコマンドを起動すればRails::ServerではなくRack::Serverをstart出来ますが、上記にある様な処理はされないとのことです。
試しにコマンドで叩いて見たらこんな感じのログが。

$ rackup
Puma starting in single mode...
* Version 3.11.4 (ruby 2.3.7-p456), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:9292
Use Ctrl-C to stop
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2018-08-03 14:56:52 +0900 ===
- Goodbye!

確かにパッと見た感じは一緒ですが…ポートが3000ではなく9292になってる等、細かな違いがありますね。
比較用に、普通にRailsサーバーを立ち上げた時のログも載っけておきます。

$ rails s
=> Booting Puma
=> Rails 5.2.0 application starting in development 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.4 (ruby 2.3.7-p456), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2018-08-03 14:58:48 +0900 ===
- Goodbye!
Exiting

pumaとは

GitHub - puma/puma: A Ruby/Rack web server built for concurrency

Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications in development and production.
(※和訳)Pumaは開発環境におけるRuby/Rackアプリケーションのための、シンプルで高速でスレッド化された、concurrent(並行処理)で高度なHTTP 1.1サーバーです。

引用元: Github-pumaのREADME.mdより

Railsのデフォルトサーバーになっているので、Railsが使えるということはpumaも使えるということです(ちょっと暴論かな?)。

Rackやpumaの知識を踏まえた上でRailsの内部処理を考慮すると、

  1. Rackの上にRuby on Railsが実装されている
  2. Railsがサーバーを起動させるときには、Rack経由でサーバーとしてpumaが起動

…と言った感じでしょうか。
Railsも一枚岩でないというか、モノリシックなフレームワークではないというか…(もちろんいい意味でですが)すごく今時だなぁと感じました。

メモ- 今回作ったRailsプロジェクトのソースコード

今回作成した「ブラウザからコマンドを実行できる」インタフェースのソースコードを、メモ代わりににこちらにおいておきます。
要所要所のソースコードのみを置いておく感じにしておきます…。

ファイル構造について

  • コントローラー: top
    • ビュー: index(トップページ)
    • ビュー: exe(コマンド実行後画面)
    • ビュー: new(コマンドの新規登録)
    • ビュー: create(コマンド追加完了画面)
  • モデル: command(コマンドの情報を入れたDB)

ソースコード

route.rb
  root 'top#index'
  get 'new' => 'top#new'
  post 'create' => 'top#create'

  post 'exe' => 'top#exe'
top_controller.rb
class TopController < ApplicationController
  def index
    @commands = Command.all
    @debug = Command.where(id: 0)
  end
  
  def new
  end
  
  def create
    @command = Command.new(params.require(:command).permit(:name,:argument,:info))
    if @command.save
      @status = "success"
      @command_name = params[:command][:name]
      @command_arg = params[:command][:argument]
      @command_info = params[:command][:info]
    else
      @status = "failed"
    end
  end

  def exe
    @command_str = params[:command][:name] +" "+ params[:command][:argument]
    @result = system(@command_str) # true/falseで返ってくる

    # コンソール上に出ている標準出力を出力させる
    # 参考記事: https://ikm.hatenablog.jp/entry/2014/11/12/003925
    @result_str = [];
    require "open3"
    Open3.popen3(@command_str) do |stdin, stdout, stderr, wait_thr|
      stdin.close_write # 標準入力を閉じる。
      begin
        # 標準出力、標準エラーの出力があるまで延々と待ち、随時1行ずつ書き出す
        loop do
          IO.select([stdout, stderr]).flatten.compact.each do |io|
            io.each do |line|
              next if line.nil? || line.empty?
              @result_str.push(line)
            end
          end
          # 標準出力、標準エラーでEOFが検知された、つまり外部コマンドの実行が終了したらループを抜ける
          break if stdout.eof? && stderr.eof?
        end
      rescue EOFError
      end
    end
  end
  
  def delete
#    @d_command = Command.find(params[:]id)
#    @d_command.destroy
#    @d_command = Command.find(params[:id])
#    @d_command.destroy
    redirect_to :action => "index"
  end
end
index.html.erb
<h1>Top#index</h1>
<h2>コマンド一覧</h2>
<p>
  <%= link_to 'コマンド追加ページへ', 'new' %>
</p>

<div>
  <h3>コマンド一覧</h3>
  <table>
    <thead>
      <tr>
        <th>コマンド名</th>
        <th>引数/オプション</th>
        <th>コマンドの説明</th>
      </tr>
    </thead>
    <tbody>
      <% @commands.each do |c| %>
        <tr>
          <td><%= c.name %></td>
          <td><%= c.argument %></td>
          <td><%= c.info %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

<div>
  <h3>コマンドの直打ち実行フォーム</h3>
  <% @command = Command.new unless @command %>
   <%= form_for(@command, :url => {:controller => :top, :action =>:exe}) do |f| %>
    <%= f.label :コマンド名 %>
    <%= f.text_field :name %><br>
    <%= f.label :引数、オプション %>
    <%= f.text_field :argument %><br>
    <%= f.submit "コマンドの実行" %>
  <% end %>
</div>

<div>
  <h3>デバッグ用</h3>
  <tbody>
    <% @debug.each do |c| %>
      <tr>
        <td><%= c.name %></td>
        <td><%= c.argument %></td>
        <td><%= c.info %></td>
      </tr>
    <% end %>
  </tbody>
</div>
exe.html.erb
<h1>Top#exe</h1>
<h2>コマンドの実行</h2>
<p>
  コマンド実行しました
</p>

<div>
  現在のカレントディレクトリは…: <%= @pwd %>
</div>
<p>
  実行されたコマンドは: <br>
  <%= @command_str%> <br>
  実行結果は: <br>
  <%= @result%> <br>
  実行結果詳細内容は: <br>
  <% @result_str.each do |c| %>
    <%= c %> <br>
  <% end %>
</p>

<p>
  <%= link_to 'コマンド一覧ページへ', '/' %> <br>
  <%= link_to 'コマンド追加ページへ', 'new' %>
</p>
new.html.erb
<h1>Top#new</h1>
<h2>コマンドの追加</h2>
<p>
  <%= link_to 'コマンド一覧ページへ', '/' %>
</p>

 <div>
  <% @command = Command.new unless @command %>
   <%= form_for(@command, :url => {:controller => :top, :action =>:create}) do |f| %>
    <%= f.label :コマンド名 %>
    <%= f.text_field :name %><br>
    <%= f.label :引数、オプション %>
    <%= f.text_field :argument %><br>
    <%= f.label :コマンドの説明 %>
    <%= f.text_field :info %><br>
    <%= f.submit "コマンドの追加" %>
  <% end %>
</div>
create.html.erb
<h1>Top#create</h1>
<h2>コマンドの追加完了</h2>
<p>
  コマンド追加が<%= @status %>しました
</p>

<p>
  追加されたコマンドは: <br>
  コマンド名: <%= @command_name%> <br>
  引数、オプション: <%= @command_arg%> <br>
  コマンドの説明: <%= @command_info%>
</p>

<p>
  <%= link_to 'コマンド一覧ページへ', '/' %> <br>
  <%= link_to 'コマンド追加ページへ', 'new' %>
</p>
top.scss
// Place all the styles related to the top controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

body{
  margin: 1em;
  font-family: "Mplus 1p";
  font-size: 16px;
}

h1,h2,h3,h4,h5,h6{
  font-size: 1.5rem;
}

div{
  margin: 1em;
  padding: 1em;
  background-color: #ddd;
  border-radius: 10px;  
}
マイグレーションファイル
class CreateCommands < ActiveRecord::Migration[5.2]
  def change
    create_table :commands do |t|
      t.string :name
      t.string :argument
      t.string :info

      t.timestamps
    end
  end
end