Redis + React + SinatraでちんまりとしたSPAをつくったメモ
RubyでRedisを使うのに慣れましょうというのが今回のテーマでした。
Redisの本を読んだ結果、データ規模がおさまるのであればメインで使っていけるやつじゃんみたいなことになったので、使っていくためには慣れなきゃいけないということで使いました。
どんなアプリケーション
メモをためておいて、それをまとめたメールを投げます。(一人用)
翌日に後悔するようなことを酔った勢いでツイートしてしまうのをやめたい一方、それでもネット上のどこかに雑にメモりたい欲求(なぜかローカルではダメ)に抗いがたいのでつくりました。
Heroku Redis + Heroku Schedulerでrake taskを呼び、メモをまとめたメールははてなブログの下書き登録用アドレスに投げるようにして使用しています。同時にRedisを完全にまっさらにするので、25MBを使い果たす心配がほぼありません。
画面
メールで登録した様子 メモまとめ - ンンンパ
今気づいたけど時間の設定してなかった。
Redis
RubyでRedisを使う場合のgem選択肢として、redis-rbとredis-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.clear
やfoo[]=
というメソッドは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に登録する段にわけなくてはならない。つまりnew
とsave
を明確にわける必要があって、作成とか削除とか編集とかをペチペチ書いていると、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::Objects
とid
によるアクセスを使用します。
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: