Awesome 某のスター付き一覧

Awesome Hoge のページをよく見ることがあり、特に iOS をよく見ていたのだが、スターの数をまとめて見たいと思い、主に自分用にスター付きの一覧を生成してみました。Awesome repositories の contributors と全てのレポジトリの developers の方々に感謝です。

URLはこれです。
Awesome repos with 🌟

GitHub API で取得した stargazers_count を使っています。 GitHub API、レポジトリの取得なら1時間に5,000回のLimitなので結構叩ける印象。

サーバは要らなそうだったので、AWSのサービスを組み合わせてサーバレスでページを生成しています。

  • Lambda でレポジトリ一覧を取得
  • まとめて GitHub API を叩くと Lambda がタイムアウトするので、一旦 SQS に JSON にしていれる。
  • CloudWatch Events で Lambda を定期的に起動して、 SQS のキューを受信しながら、 GitHub API を叩いて SimpleDB に結果を入れる
  • 定期的に、 SimpleDB のデータを Lambda で JSON にしてs3 に PUT する。

という風にしています。APIにも優しいように、1日かけて1ループするくらいの周期で処理をしています。

DynamoDB ではなく SimpleDB なのは、最後の処理で一括でデータを取得する必要があるのですが、DynamoDB はスループットの設定的に一括取得には向いていないので、課金的な意味で SimpleDB にしてます。無料枠を考えないと、ミニマムコストが SimpleDB はが$0で DynamoDB は$0.67なので。ただ、DynamoDBで読み書き1ずつにして、s3にPUTするときだけ scan しても、もしかしたら DynamoDB の方が安い、ということはあるかもしれません。この部分の比較は見たことがないというか、SimpleDBに関する情報をあまり見かけません。 Lambda からもデフォルトで使えるし、ちょっとした時に便利だと思うのですが、管理画面にも無いし、そのうちサービスが終わってしまうのではないかというのが心配です。

Lambda 開発環境は、最近知った node-lambda がシンプルで使いやすいです。手元での実行とデプロイが出来ます。

HTML側は、S3 + CloudFront です。最近 CloudFront が HTTP/2 に対応したので、個人的には静的ページの配信に HTTP/2 対応のサーバを立てる意味がほぼなくなりました。あと、React で描画してるので、対応してないブラウザでは見えないです。

JSON の URL はパブリックなので、直接叩いても良いです。見ればフォーマット何となくわかると思いますので、 jq とか使って絞り込めば、例えば 「Awesome iOSで、Buttonのカテゴリで、500スター以上付いているレポジトリを開く」とかもすぐ出来ます。

curl https://s3-ap-northeast-1.amazonaws.com/awesome-repositories/ios.json | gunzip | jq -r 'map(select(.category1 == "Button")) | map(select(.stargazers_count >= 500)) | "http://github.com/"+.[].path' | xargs open

※ 一気にブラウザ上でページが立ち上がるので、内容に注意して下さい!

最初 iOS だけ作ったら、他のものもほぼ同じコードで生成できたので、自分がよく使う言語だけですが、6個作りました。 Elixir 力を上げたい。

Awesome 某のスター付き一覧

Ruby1.9 でメール解析

 Ruby1.8 は、tmail で -> Rubyで受信メール解析(変なヤバいもんログ内) どうぞ。

 gem で mail をインストールします。

# gem install mail

メールのファイルを解析する方法です。

require 'mail'
mail = Mail.read("mail_dir/001.eml")
mail.from.first # 送信元 mail.from に配列で入ってる
mail.to         # 送信先 mail.to に配列で入ってる
mail.subject    # 件名
mail.body       # 本文

みたいな感じに取得できます。

Ruby1.9 でメール解析

unit test の fixtures で関連がないテーブルの primary key が取りたい

Rails の unit test を書いていて、関連がないテーブルの primary key が知りたい状況があった。

関連があるテーブルだと、

class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end

みたいなときに

test/fixtures/users.yml

chihaya:
name: 千早

test/fixtures/posts.yml

first:
user: chihaya
title: くっ…!
body: 先日フェスがあったのですが…

みたいな感じにすると関連が作れる。

今回は、単純なログみたいなもので、色んなテーブルの id を、target_id みたいなところにしまって、クラス名と合わせてユニークになるように管理してた。数が多かったのと関連付けて取る必要が無かったので、関連を作ってなかったんだけれども、fixture を書く時に、外部の id の取得方法に迷った。

つまり、先ほどの例で、has_many, belongs_to が無かった時にどうするかということです。
users.yml に、 id:1 とか書くというのも1つの方法です。

id を指定せずに rake fixtures:load ってすると、id が結構大きな数字になりますが、あれはラベルの文字列によって計算された値みたいですね。

ActiveRecord::Fixtures.identify(label) というメソッドで計算されていて、プラットフォーム依存で、同じ文字列を与えれば同じ値が返ってくる仕組みになっているようです。

そうすると、先ほどの例だと

test/fixtures/posts.yml

first:
user_id: <%= ActiveRecord::Fixtures.identify(:chihaya) %>
title: くっ…!
body: 先日フェスがあったのですが…

とすると、常に千早の id が取れるようになります。ActiveRecord::Fixtures.identify(:chihaya) の値は、見て分かるようにテーブルに依存することはなく、別テーブルで chihaya というラベルを使うと、同じ id になります。ActiveRecord::Fixtures が見つからないときは、 require ‘active_record/fixtures’ とします。

なんだろうなーと思ってた巨大な整数の id の計算方法がわかってすっきりしました。

unit test の fixtures で関連がないテーブルの primary key が取りたい

WebSocket でリアルタイムお絵かきチャット作った

 WebSocketを使って、@projecthl2先生といっしょにリアルタイム連動お絵かきチャットを作ってみました。

おえかきちゃっと♥

 WebSocket 速いです!リアルタイムで同期しているので、数人でやると、毎秒サーバと50回とかやりとりしてるのだけど (1回の通信はおそらく100bytesくらい)、サーバ側はほとんどCPU食ってないみたいですね。すごぃ。もし AJAX とか使うとすると、毎秒50回とかサーバにリクエスト飛ばそうとか絶対考えないよ。2窓開いてみると、遅延はほぼ0で通信できてることが分かります。

 WebSocket は、やってみたところでは文字列しか送れなさそうなので、今までAJAXでJSON扱ってたようなものは文字列にして送る必要がありました。

JSON.stringfy({x:100, y:100})

みたいにしてサーバに送って、サーバからJSONを文字列にしたものを送って

JSON.parse(event.data)

的な感じにJSONを得てみました。

 お絵かき部分と、チャット部分でWebSocketを使っています。サーバ側のWebSocket部分は em-websocket を使いました。EventMachine を使った WebSocket 実装みたいです。WebSocket はシンプルでした。EventMachine の使い方を調べるのに時間がかかった。。

 お絵かき部分は、canvas を使っています。ファイルをロードの部分は XMLHttpRequest Level2 を使っています。最初は、FileAPI を使って実装すればサーバ経由せずにファイルロードできてウマー!! と思ったけど、まだ Firefox でしか使えないみたいなので、あきらめました。Twitterに投稿っていう部分は、canvas から toDataURL() で canvas に描かれたデータが Base64エンコードされたものを取って、そいつをサーバ側に送って保存しています。今までの XMLHttpRequest ってファイルをサーバに送信出来なかったので、無理矢理 iframe 使ったりとか頑張っていたけど、ファイルが送れるようになってすごい便利そうです。使い方は今までのものと大して変わらないみたいですし。

 
 HTMLを生成する部分は sinatra ちゃんです。sinatra ちゃん可愛いよ。センスいいと思う。

 2日で作られたものなので、例外処理とかあんまりしてなぃ。。でも2日でこのくらいのもの出来るとは、高機能なHTMLですねー

 ソースコードを github で公開しています。 -> ecpplus / websocket_chat

WebSocket でリアルタイムお絵かきチャット作った

amazon Product Advertising API 署名

 amazon web services が署名が必要になって、名前も amazon Product Advertising API とかなってたので、コードを変更しなきゃと思ってサンプルページ見たけど、途中に描かれている値が間違っているみたいだ…。

サンプルリクエストに署名を行うためのステップ 【amazonヘルプ】 の、Version=2009-03-31 という文字列が、正しくは Version=2009-01-06 みたいです。

下記のサイトを参考にしました。下記のページだと、ヘルプページと最終出力は同じなのに、入力値が違う!って気づいたので、合わせてみたらちゃんと出来ました。

require 'hmac/sha2'
require 'base64'
 
def genreate_hash(original_url)
  url_string = original_url.split('&').sort.join('&')
  string = ['GET', 'webservices.amazon.com', '/onca/xml', url_string].join("\n")
  Base64.encode64(HMAC::SHA256.digest('1234567890', string))
end

上記で original_url を

"Service=AWSECommerceService&AWSAccessKeyId=00000000000000000000&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Version=2009-01-06&Timestamp=2009-01-01T12%3A00%3A00Z"

とすれば、

Nace+U3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg=\n

が出力されます。

なお、サンプル通りの文字列でやると、url_string が

"Service=AWSECommerceService&AWSAccessKeyId=00000000000000000000&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Version=2009-03-31&Timestamp=2009-01-01T12%3A00%3A00Z"

となって、

EDWJ1+VXQhAtPDKQ0f+wpaFQcBVDJyTIpDP7BZgxMiA=\n

が出力されます。

amazon Product Advertising API 署名

WindowsのRubyでThread内でプロセス作成すると止まっちゃう

Windows の Ruby で、Thread 内で新規プロセスを立ち上げようとすると、Thread 内で全体の動作を止めてしまうらしい。たとえば、

Thread.new do 
  `something.exe`
end

とやってみると、`something.exe` の処理が終わるのを待っているようだ。

Windows+Rubyで外部プロセスを立ち上げる時の注意 にまさにその情報が載っていた。Windows は、Windows::Process 等を使う必要があるみたいです。

require 'rubygems'
require 'win32/process'
require 'windows/synchronize'
require 'windows/process'
require 'windows/handle'
 
include Windows::Synchronize
include Windows::Process
include Windows::Handle
 
t = Thread.new do 
  Process.create('app_name' => 'something.exe')
end
puts "hello!"
t.join

のようにしたら、プロセス生成してもそこで止まることは無くなりました。

WindowsのRubyでThread内でプロセス作成すると止まっちゃう

ActiveRecord の例外処理

RailsのActiveRecordで、save! とかすると保存に失敗したとき例外を投げてくれる。例えば、User.create!(params[:user]) とかして、失敗すると、ActiveRecord::RecordInvalid を投げてくれるのだけど、エラーをどうやって取るのか調べたのでメモ。

よくある例で、ユーザが作れたら index へ遷移して、作れなかったら new で再入力させるという場合。

createを使う場合

def create
  if @user = User.create(params[:user])
    flash[:notice] = 'ユーザ作成しました'
    redirect_to :action => 'index'
  else
    # ここでは、@user.errors でエラーが取れる
    render :action => 'new'
  end
end

create!を使う場合

def create
  @user = User.create!(params[:user])
  flash[:notice] = 'ユーザ作成しました'
  redirect_to :action => 'index'
rescue ActiveRecord::RecordInvalid => e
  # @user は nil になっているので、 @user.errors でエラーが取れない。
  render :action => 'new'
end

としたとき、create! で例外が飛んでいるので、当然 @user は nil となってます。なので、rescue 句の中で @user.errors ではエラーが取れません。

調べたところ、e.record で、ActiveRecord の例外を発生させたインスタンスが取れるようです。

def create
  @user = User.create!(params[:user])
  flash[:notice] = 'ユーザ作成しました'
  redirect_to :action => 'index'
rescue ActiveRecord::RecordInvalid => e
  # e.record に Userクラスのインスタンスが入ってる。
  @user = e.record
  render :action => 'new'
end

たとえば、View内で

< %= error_messages_for :user %>

とかしたい場合は、@user = e.record として置けばエラーが楽に表示出来て良い。

ActiveRecord の例外処理