Open15

Ruby 初心者が Ruby on Rails を使う

霧咲空人霧咲空人

Ruby 初心者が Ruby on Rails を使ってアプリを作るまでの軌跡(になる予定)。

霧咲空人霧咲空人

rbenv で仮想化、 bundle のインストール、Rails のインストールを終了。

霧咲空人霧咲空人

どうでもいいけど Ubuntu だと .profile が読まれて環境変数が設定され、コンソール開くとその設定が反映されるのだがシェルを zsh にしていたため環境変数のみが反映され alias が反映されないという知見を得た(X11 だとなのだろうか)。

霧咲空人霧咲空人

Railsで超簡単API - Qiita
この記事を参考にしていく。

$ rails g model User name:string mail:text fingerprint:text token:text
$ rails g controller user

みたいな感じでひと通りモデルとコントローラーを作り終わる。Docker イメージを投げつけるとサーバーが立つ、Heroku もどきのプロトタイプを作りたい。フロントは SPA にするので View は作らない(Cycle.js 使ってみようかな)。

app
├── channels
│   └── application_cable
│       ├── channel.rb
│       └── connection.rb
├── controllers
│   ├── application_controller.rb
│   ├── concerns
│   ├── container_controller.rb
│   ├── containers_controller.rb
│   ├── env_controller.rb
│   ├── image_controller.rb
│   ├── images_controller.rb
│   └── user_controller.rb
├── jobs
│   └── application_job.rb
├── mailers
│   └── application_mailer.rb
├── models
│   ├── application_record.rb
│   ├── concerns
│   ├── container.rb
│   ├── env.rb
│   ├── image.rb
│   ├── magicklink.rb
│   └── user.rb
└── views
    └── layouts
        ├── mailer.html.erb
        └── mailer.text.erb
$ rails db:create
$ rails db:migrate

とりあえず成功の模様。

霧咲空人霧咲空人

いきなりルーティングで詰まる。

/config/routes.rb
Rails.application.routes.draw do
  namespace 'api'do
    namespace 'v1' do
      resources :user
    end
  end
end

こうすればエンドポイントが生えてくるのはいいんだけど、構文がさっぱわからんて。:user は Symbol というものということがわかった。foo do ... endブロック付きメソッド呼び出しとわかった……のだが Rails.application.routes.drawソースコードを読みにいったら引数が一つなのにメソッドの呼び出しでは引数がない。2 時間くらいさまよった結果こっちが正しい draw だったといういい話。

def f(&param) do
  ...
end

とやるとブロックを引数に取れるメソッドを作れることがわかった。あと

      resouces :user

を見て最初 Haskell 脳なので「関数適用か?」と思っていたけどだいたいあってた。

無事エンドポイントが生えてくる。

霧咲空人霧咲空人

routes にデフォルトでいらんエンドポイントがベロベロ生えてくるので消したい。
/config/environments/production.rb/config/environments/development.rb にある以下の行をコメントアウト。

/config/environments/*.rb
  #config.active_storage.service = :local

あと /config/application.rbrequire をコメントアウト。

/config/application.rb
#require "active_storage/engine"
#require "action_mailbox/engine"
#require "action_text/engine"

これでスッキリ。こういうところをよしなにしてしちゃってくれるのが好きじゃないんだよな、フレームワーク。

霧咲空人霧咲空人

元記事の意味が良くないためルーティング周りにまた時間を取ってしまった。上記のようにルーティングして controller を作っておくと /app/controllers/api/v1/user_controller.rb 中の indexshowcreateupdatedelete メソッドに紐付けられるらしい。ちゃんとやるには使うものだけ露出させるとかしなければいけないのだろうけど、プロトタイピングなのでどうでもいい。そしてこのルーティングからディレクトリ構造を自動で生み出してくれる方法はわからんかった。うーむ。とりあえずちゃんと動いて サーバーの Dockerize が済んだ。DB 動いとらんのでそのあたりの設定しなければいけないのが面倒。

霧咲空人霧咲空人

Docker イメージファイルをアップロードする API を作成する際「ファイルの保存先、ソースコードにベタ書きしたくねーな」と思うのが当然じゃないですか。思わない人はソフトウェアエンジニアという職ともう一度よく見つめ合ってください。色々手段はあるけど Rails やしなんかあるやろと雑に調べたらあった。
rubyconfig/config: Easiest way to add multi-environment yaml settings to Rails, Sinatra, Pandrino and other Ruby projects.
yaml に書いておけばあとはよしなにやってくれて Settings.foo,bar という形でアクセスできる。便利。どうよしなにやってくれているのか知らないまま使うのがいささか気にならないではないが、とりあえず乗っかっておいて先へ進む。

/app/controller/api/v1/image_controller.rb
  module V1
    class ImageController < ApplicationController
      def create
        meta = JSON.parse params[:meta]
        image = params[:image]
        if !meta || !image
          render json: { error: 'Bad request' }, status: :bad_request
          return
        end

        timestamp = Time.new.strftime('%Y%m%d%H%M%S')
        uuid = SecureRandom.uuid
        filename = timestamp << '-' << uuid << '.tar'

        Image.new(filename: filename, user: @current_user)
        File.open Pathname.new(Settings.dir.image).join(filename), 'w+b' do |file|
          file.write image.read
        end

        render json: { message: 'success' }
      end
    end
  end
end

無事ファイルを multipart でアップロードする API が生えた。2021 年にもなって multipart かよという気はしないでもない。

霧咲空人霧咲空人

JSON の扱いに困っている。JSON 単独であれば application/json で送りつけるとハッシュに変換してくれるみたいなんだけど、multipart で送っているせいかそのへんをうまく扱ってくれない。誰だよ multipart でメタデータといっしょに送ったらいいんじゃねとか言い出したやつ。でも base64 で JSON にバイナリ載せて送ってるやつはソフトウェアエンジニアという職ともう一度よく見つめ合ってください。

霧咲空人霧咲空人

require でバリデーション利かすやつやりたかったけど、上記問題があるのでここは愚直にチェックするしかない。

霧咲空人霧咲空人

require でトップレベルのパラメーターぐらいバリデーション利かんものかと思って試したら利くことは利いたんだけど、エラーメッセージが出ねえという不親切設計だったのでやっぱ自分で書くっきゃねえという。このあたりは型で上手いことやってくれる Rust とか Haskell が恋しくなってくる。プロトタイプと割り切って気にせんほうが良いのか。テストも書いてねえのでソフトウェアエンジニアという職ともう一度よく見つめ合います……。

霧咲空人霧咲空人

いよいよ Docker API を Ruby から操り始める。Ruby のライブラリは以下のものがある。
swipely/docker-api: A lightweight Ruby client for the Docker Remote API

Docker.url = 'unix:///var/run/docker.sock'
Docker.info

これだけでもう使えてしまう。すごい楽。Docker.urlconfig.rurun する前に書いてあげる。そうすれば全コードの中で Docker 操り放題である。セキュリティ的に不味そうなのでなんとかしたほうがいいけどプロトタイプだし、プロトタイプ。Docker API を呼び出すためには unix ドメインソケットを通じて行う(TCP も使えるけど遅いし危ない)。今回は Docker の中から外の Docker をいじりたい。どうするか?単純な話でソケットファイル(わたしの環境では '/var/run/docker.sock')をバインドしてしまえばいい。

docker-compose.yaml
   volumes:- .:/app↲
      - /images↲
      - /var/run/docker.sock:/var/run/docker.sock↲

もちろんリスクは上がるので対策はしよう。

霧咲空人霧咲空人

Ruby くん!Docker くん!load にうっかり文字列じゃない物食わせたら無理やり文字列変換してそのまま応答がない原因に気づくまで 1 時間は費やしてしまった!そういうとこだぞ!!