Redis + React + SinatraでちんまりとしたSPAをつくったメモ

Redis + React + SinatraでちんまりとしたSPAをつくったメモ

RubyでRedisを使うのに慣れましょうというのが今回のテーマでした。

Redisの本を読んだ結果、データ規模がおさまるのであればメインで使っていけるやつじゃんみたいなことになったので、使っていくためには慣れなきゃいけないということで使いました。

どんなアプリケーション

メモをためておいて、それをまとめたメールを投げます。(一人用)

翌日に後悔するようなことを酔った勢いでツイートしてしまうのをやめたい一方、それでもネット上のどこかに雑にメモりたい欲求(なぜかローカルではダメ)に抗いがたいのでつくりました。

Heroku Redis + Heroku Schedulerでrake taskを呼び、メモをまとめたメールははてなブログの下書き登録用アドレスに投げるようにして使用しています。同時にRedisを完全にまっさらにするので、25MBを使い果たす心配がほぼありません。

ソース mmmpa/short_message_store

画面 sms.png

メールで登録した様子 メモまとめ - ンンンパ

今気づいたけど時間の設定してなかった。

Redis

RubyでRedisを使う場合のgem選択肢として、redis-rbredis-objectsがあります。

redis-rbはRuby上からRedisの生コマンド名を持ったメソッドでRedisにアクセスすることができます。Rubyではなく、Redisの一般的な使い方を学びたい場合はこちらを使う。

# redis-rbでSorted Set
redis = Redis.new
redis.zadd('name_of_sorted_set', 35, 'me')
redis.zadd('name_of_sorted_set', 26, 'her')
redis.zrange('name_of_sorted_set', 0, -1)   # => ["her", "me"]
redis.zrem('name_of_sorted_set', 'her')
redis.zrange('name_of_sorted_set', 0, -1)   # => ["me"]

redis-objectsはredis-rbの上に、Rubyっぽいインターフェースをかぶせて、作成、削除、編集をRubyのオブジェクトに対して行うような感じにします。

# redis-objectsでSorted Set
sorted = Redis::SortedSet.new('name_of_sorted_set')
sorted['me'] = 35
sorted['her'] = 26
sorted.members        # => ["her", "me"]
sorted.delete('her')
sorted.members        # => ["me"]

今回はredis-objectsを使用しましたが、機能の把握にはRedisリファレンスの生コマンドに目を通すのがいいと思います。

Model Class Include

また、インターフェースの提供のほかに、Redis上の値をインスタンスの属性値であるかのように扱えるようになる機能があります。

これはredis-objectsをクラスにincludeして型と名前を設定することで使用できます。

include Redis::Objects
hash_key :foo

設定した名前に対応するインスタンスメソッドfooはRedisオブジェクトを返します(この場合Redis::HashKey)。foo.clearfoo[]=というメソッドはRedisへダイレクトに作用します。

ORMとはちがう、とわざわざ書いてあるのはこのせいかもしれません。

最小構成

どのインスタンスの値として保存するか、どのインスタンスの値を取得するかを判別するために、必ずidが必要となります(ActiveRecord::Baseが継承されている用例がおおいのですが、idさえあれば大丈夫です)。

class Message
  include Redis::Objects

  hash_key :message_set
  value :date

  def id
    # 一意になるなにか
  end
end

message = Message.new
message.message_set[:message] = 'やぁ'
message.date = Date.today

message.message_set       # => #<Redis::HashKey:0x007f692339ed40>
message.message_set.to_h  # => {"message"=>"やぁ"}

message.date              # => #<Redis::Value "2015-10-18">
message.date.value        # => "2015-10-18"

今回はid用のデータを一つRedis上に用意して使用しました。

def id_generator
  Redis::Counter.new('messages_id')
end

def generate_id!
  id_generator.increment.to_s
end

いつものARモデル的につかう

このアプリケーションでは空メッセージはダメというinvalid条件が存在するので、redis-objectsが用意したメソッドでなんでもかんでもそのまま登録することはできない。

インスタンスに値を持たせる段と、値をRedisに登録する段にわけなくてはならない。つまりnewsaveを明確にわける必要があって、作成とか削除とか編集とかをペチペチ書いていると、ActiveRecord::Baseさんいつもありがとう……という気分になった。

ATTRIBUTES = [:message, :fail_message, :written_at, :reply_to]

attr_accessor *ATTRIBUTES
hash_key :message_set

def self_to_redis!
  ATTRIBUTES.each do |key|
    message_set[key] = send(key)
  end
end

保存時にself_to_redis!を呼んでRedis::HashKeyにぶちこむという雑な感じです。

つかいどころ

アプリケーション内で共通して使用される値ではRedis::Counter.new('messages_id')のように常に同じデータを見るようにします。

id用のほかに、メモの表示順用にidをmemberとしたSortedSet、表示順の評価基準となるscoreのためのCounterを一つずつ用意しています。

メモはぞれぞれがそれぞれの値を持つので、include Redis::Objectsidによるアクセスを使用します。

React

Reactは今回は生で使いましたが、ペライチのちんまいやつなら親父担当を一個用意する中央集権でやればそんなにとっちらからずに済みました。

最初は別のライブラリを使っていたのですが、ビューへの反映などを(乱暴に全部書きかえるとかじゃなく)ちゃんとやるのが大変すぎて、Reactで書きなおした。

どうもコードの行数を考えてウッと一瞬ひるむのですが、Reactがよしなにしてくれる量と質を考えると簡単におつりが出ると思いました。

親父コンポーネント中央集権

いわゆるsetStateをメインコンポーネントのみに担当させて、しもじもは基本的にpropsしか見ない形にすると具合がよかった(input系のonChangeは仕方がないとして)。

やはりビューの内容変更が楽

以前にどうやってたか既に思いだせないのですが、描画を変更するイベントを発行したり購読したりして色々手間だった気がする。

今は何らかの値準拠で書きかえますということを書いておけば、後は放置プレイでOKという素晴らしさがある。

Sinatra

Sinatra、最初はRailsで書きはじめていたのですが、こんなに分厚いのいらないでしょうということになってSinatraにすげかえたらajaxでペラペラッとつくるには具合がよかった。

あとはHerokuでの話です。

Procfileを忘れてRails扱いされる

Rack::MethodOverrideを使った関係でrackup config.ruを使っていたのでProcfileをすっかり忘れていました。

web: bundle exec rackup config.ru -p $PORT

これでOK。

Herokuよしなに

最初のpushで起動せず上記の忘れに気づいたのですが、ログをみたらbin/railsがありまてんという事態になっている。

Herokuは雑にpushしても勝手に判別してアプリを起動してくれる最高のパートナーなのですが、Gemfile.lockにrailtiesが含まれているとこちらの思惑などおかまいなしにRails扱いされます。

その他

Current streakが31 daysになった :sunny: