Gemを初回インストールした時にメッセージを表示する post_install_message

自作のGemで初回インストール時にメッセージを表示したいケースがある場合に重宝する。 まだ自分でちゃんと運用しているわけではないけど、 社内Gemの場合はREADMEやMigrationしてねーみたいなメッセージを付けてあげるのが親切・便利かと思う。

spec.post_install_message = File.read('UPGRADING.md')

Specification Reference - RubyGems Guides

PaparClipでCSVをS3にアップロードした時にContent-Typeが"text/plain"になってしまう問題

概要

PaperClipで、Railsで生成したCSVファイルをS3にアップロードするときに、Content-Typeが"text/plain"でアップロードされてしまうので、 S3にアップロードしたときに.txtファイルになってしまう問題がある。

とりあえずの解決方法

とりあえずS3にアップロードするタイミングでヘッダーにContent-Typeに"text/csv"を設定すればとりあえずOK。

今回利用したかったケースはシステム内部で生成したCSVをS3にアップロードする要件なので。 特にバリデーション周りが雑なんだけどまぁこんな感じで。

  class Document < ActiveRecord::Base

    has_attached_file :document,
      storage: :s3,
      s3_credentials: "#{Rails.root}/config/private_s3.yml",
      s3_permissions: :private,
      path: "example.com/:id/:filename"
      
    validates_attachment :document, less_than: 512.megabytes
    do_not_validate_attachment_file_type :document

  end
  class DocumentCsv < Document
    has_attached_file :document,
      storage: :s3,
      s3_credentials: "#{Rails.root}/config/private_s3.yml",
      s3_permissions: :private,
      path: "example.com/:id/:filename",
      s3_headers: {"Content-Type" => "text/csv"}
  end

弱小Railsアプリケーションで1日毎のお手軽な集計方法のご提案

こちらのアンサーブログだと思ってもらえれば。

webuilder240.hatenablog.com

結論

結論からいうと、集計をするためのModelを作っておいてそこから集計を行う方式にして、 色々なModelの集計に対して対応できるようにした。

  create_table "aggregaters", force: :cascade do |t|
    t.date    "aggregate_date",  null: false
    t.string  "aggregate_type",  limit: 255
    t.float "aggregate_value", null: false
  end

ちょっと筋悪な感も否めないが、なにもしないよりは良いだろうと思っている。 タスクにして実行しないといけないのは当然なんだけど、そもそも弱小アプリケーションなんだから問題ないだろうと思ってる。

簡単な利用方法

バッチで結果を格納する側 (RailsのRakeタスク)

require 'active_record'
require 'active_support'

namespace :aggregate_record do
  desc 'コメント数の集計を行って、集計レコードを作成します。'
  task create_daily_comment: :environment do
     Aggregater.create(aggregate_date: Date.now, aggregate_type: 'comment_count', aggregate_value:  Comment.size)
  end
end

呼び出し方

 # なんかここはうまいことやるのは解るけど、あくまで簡単なサンプルで。
 daily_comment_count_obj = Aggregater.find_by(aggregate_date: Date.yesterday, aggregate_type: 'comment_count')
 daily_comment_count = daily_comment_count_obj.aggregate_value || 0

今後 

弱小な間はしばらくはこの方式でなんとかなりそう。 今後もTresureDataやBigQuery等を利用した集計を行う為の外部サービスのクエリ結果をDBにキャッシュする(格納してRailsが取り出しやすい用に)という用途でも利用できそうなので、便利な感じはする。 ただ筋悪なのは否めないので、もっといい方法があれば教えていただきたい所存です。

Dockerで1つのイメージ複数のコマンドを切り替える方法

これをやりたい理由

作るDockeイメージは1つにしたいのだけど、デプロイする先によってコンテナの役割(例えばCronやJobワーカーを動かすコンテナとWebサーバーを実際に動かすコンテナ等)を分けたいときに利用する。

例えばAzureのWebApps for Container (Web Apps on Linux)で、 Webサーバーはスケールアウトしたいけど、Jobワーカーサーバーはスケールアウトしたくない(もしくはJobワーカーサーバーは任意のタイミングにしたい)とか 管理するコンテナを分けたいとかそういうときに利用すれば便利だと思っている。

やりかた

init.shという起動時の環境変数によって実行するコマンドを変更するスクリプトをCMDで実行してあげればOK。 これで実行時に環境変数を渡してあげればOK。

下記例はRubyアプリケーションを動かす際の自分のDockerfileの雛形のようなものである。

# Dockerfile
---
FROM ruby:2.5

ENV LANG C.UTF-8
ENV RACK_ENV production
EXPOSE 3000

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY Gemfile /usr/src/app/
COPY Gemfile.lock /usr/src/app/
RUN bundle install -j4 --without development test

RUN mkdir tmp
COPY . /usr/src/app
RUN chmod u+x init.sh
CMD "./init.sh"
# init.sh
---
#!/bin/bash
set -e

if [[ "${RUN_WEBAPP}" != "" ]]
then
  echo "Starting uniron ..."
  bundle exec unicorn -c config/unicorn.rb
else [[ "${RUN_WEBJOB}" != "" ]]
  echo "Starting sidekiq ..."
  bundle exec sidekiq -r ./app.rb -C ./sidekiq.yml
fi

以上の内容でDockerイメージをBuildして、docker runするときに環境変数を渡せばOK。

サンプルでWebサーバーを実行する場合

 docker run -e RUN_WEBAPP=true xxx

サンプルでJobワーカーサーバーを実行する場合

 docker run -e RUN_WEBJOB=true xxx

おわりに

ちなみにAWSのElasticBeanstalkあたりだとマルチコンテナーをそもそもサポートしているので、そっちを使うほうがまぁ普通だと思っている。 もしくはCronを利用しないで、SidekiqやDelatedJobあたりでワーカーがいくらスケールアウトしても問題ないようにするとか、 WebサーバーとWorkerの同居を許容してしまうとか、そういう方法で解決したほうが至極真っ当な感じはする。

余談

AzureからAWSのDynamoDBって利用すると遅いのかな。 ぶっちゃけ用途があえばCosmosDBでもよかったんだけど、メッセージングアプリっぽいの作りたくて、 MongoベースのAPIでまともに要件を満たせるのか感があって、不安...

URLのOGP情報をスクレイピングするだけのWebAPIを作った

サイトのOGP情報をスクレイプするのが面倒なので、OGP情報のスクレイピングし、JSONに変換して情報を返すだけのマイクロサービスを作った。 マイクロサービスというか、用途的にはDockerが向いていると思ったので、Dockerイメージにもしてあります。

WebAPIから利用する

$ curl https://powerful-sierra-79664.herokuapp.com/?url=http://ogp.me | jq

{
  title: "Open Graph protocol",
  description: "The Open Graph protocol enables any web page to become a rich object in a social graph.",
  image: "http://ogp.me/logo.png",
  url: "http://ogp.me"
}

リポジトリにあるDockerComposeから利用する

同梱されているDockerComposeを利用して簡単に使い始めることも可能です。

$ docker-compose up

$ curl http://localhost:8080?url=http://ogp.me | jq

{
  title: "Open Graph protocol",
  description: "The Open Graph protocol enables any web page to become a rich object in a social graph.",
  image: "http://ogp.me/logo.png",
  url: "http://ogp.me"
}

Dockerイメージから利用する場合(Redisが必要です)

$ docker pull webuilder240/ogp_parse_api
$ docker run -p 8080:8080 webuilder240/ogp_parse_api

注意事項

  • Productionで利用する場合、サーバーサイドの前段でキャッシュを取ったりはしていないので、このMicroServiceの前にCDNを利用することを推奨しています。
  • バックエンドにRedisを使っているので、Redisを事前に用意してください。*1

その他の詳しい情報についてはGithubあたりに置いているREADMEをご参照頂ければと。

なんか特徴があるとすれば

  • Sinatra使ってる
  • 本体のコードは100行未満で、めっちゃ短い
    • ただ他のライブラリに依存していないとは言っていない。
  • バックエンドにRedisを利用している
    • OGPなんて消えてしまっても別に困らない。再度取得すればOK
    • キャッシュしたデータがもりもりになっても困るし、7日くらいで勝手に削除されるようにしてる。*2
  • 前段にNginxを噛ませてない
    • なるべくインストールするものを減らしたかったというのと、前段にCDNを置いて運用する前提で作っていたのでそもそも不要と思ったからでした。

作った背景

  • マイクロサービスの題材として最適だった。そのなかで色々な案件で使えそうな便利なものをと考えたらコレになった。
    • 機能単位でWebAPIとして切り出していくメリットはなんとなく掴めた。
  • サーバーサイドで利用するだけだったらGemだけでも良いけど、フロントエンドやRuby以外のサーバーサイドでも利用するのではないかと思うと、JSONでやり取りした方が良いのでは思ったから。
  • あとはリンクを解析して、色々な情報を解析して返してくれるSaasもあるけどなんかべらぼうに髙いように感じたのと、やけに高機能だったのでシンプルにして実装し直した。 というのが作ってみたモチベーション。

コレであれば使われなくても捨てれるサイズだし、何かしらアウトプットしとくか位の気持ちでやっている。*3

今後の展望

  • herokuボタン
  • Rubyではなく、JavaかGoにリプレースしてもいいのかと思っている。
  • これができるのがMicroServiceの強み

*1:開発用にDockerComponseも用意しているけど、それにはRedisのコンテナも定義してあるので。

*2:Dockerから利用する場合、環境変数から変更可能

*3:前に試しに作ってみたものをブログでアウトプットするネタのためにわざわざOSSにするという姑息な手を使ったというのは内緒...