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
とりあえず成功の模様。
/bin
って含めていいの……?
いきなりルーティングで詰まる。
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(¶m) do
...
end
とやるとブロックを引数に取れるメソッドを作れることがわかった。あと
resouces :user
を見て最初 Haskell 脳なので「関数適用か?」と思っていたけどだいたいあってた。
無事エンドポイントが生えてくる。
routes にデフォルトでいらんエンドポイントがベロベロ生えてくるので消したい。
/config/environments/production.rb
と /config/environments/development.rb
にある以下の行をコメントアウト。
#config.active_storage.service = :local
あと /config/application.rb
の require
をコメントアウト。
#require "active_storage/engine"
#require "action_mailbox/engine"
#require "action_text/engine"
これでスッキリ。こういうところをよしなにしてしちゃってくれるのが好きじゃないんだよな、フレームワーク。
元記事の意味が良くないためルーティング周りにまた時間を取ってしまった。上記のようにルーティングして controller を作っておくと /app/controllers/api/v1/user_controller.rb
中の index
、show
、create
、update
、delete
メソッドに紐付けられるらしい。ちゃんとやるには使うものだけ露出させるとかしなければいけないのだろうけど、プロトタイピングなのでどうでもいい。そしてこのルーティングからディレクトリ構造を自動で生み出してくれる方法はわからんかった。うーむ。とりあえずちゃんと動いて サーバーの Dockerize が済んだ。DB 動いとらんのでそのあたりの設定しなければいけないのが面倒。
DB をどうにかうっちゃり、Bearer トークンによる API 認証の作成までたどり着いた。ここでも問題を踏み抜いてる。API モードで開発してるんだけど、それだと ActionController::HttpAuthentication::Token::ControllerMethods
が抜けているらしく、authenticate_or_request_with_http_token
が使えない。
Rails APIのトークン認証おもったより楽だったので紹介 - Qiita
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
という形でアクセスできる。便利。どうよしなにやってくれているのか知らないまま使うのがいささか気にならないではないが、とりあえず乗っかっておいて先へ進む。
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.url
を config.ru
の run
する前に書いてあげる。そうすれば全コードの中で Docker 操り放題である。セキュリティ的に不味そうなのでなんとかしたほうがいいけどプロトタイプだし、プロトタイプ。Docker API を呼び出すためには unix ドメインソケットを通じて行う(TCP も使えるけど遅いし危ない)。今回は Docker の中から外の Docker をいじりたい。どうするか?単純な話でソケットファイル(わたしの環境では '/var/run/docker.sock')をバインドしてしまえばいい。
volumes:↲
- .:/app↲
- /images↲
- /var/run/docker.sock:/var/run/docker.sock↲
もちろんリスクは上がるので対策はしよう。
Ruby くん!Docker くん!load
にうっかり文字列じゃない物食わせたら無理やり文字列変換してそのまま応答がない原因に気づくまで 1 時間は費やしてしまった!そういうとこだぞ!!