redis-objectsを使ってRailsとRedisを組み合わせる

Ruby

redis-objectsについて

redis-objectsとはRedisのデータ型をRubyのオブジェクトにマッピングすることでRedisにRuby的なインタフェースを提供するgemです。

Redis::Objectsの利用用途は以下の2つです。

Redis::Objectsの利用用途
  • モデルとRedis::Objectsを紐付け、モデルに関連するデータをRedisに保存する
  • モデルとは紐付けず、Redis::Objects単体で利用する

今回はそれぞれの利用方法について紹介します。利用するredis-objectsのバージョンは1.5.1です。

下準備

RailsアプリケーションでRedis::Objectsを利用するための下準備について紹介します。

Redisのインストール

RailsアプリケーションでRedisを利用できるようにするため、まずはRedis本体をインストールします。

ローカルの場合

### redisのインストール
$ brew install redis

### redisサーバの起動
$ redis-server

Dockerの場合

Redis用のコンテナをdocker-compose.ymlに追加すればOKです。

version: '3'
services:
  app: # アプリケーションコンテナ
    (略)
    depends_on:
      - redis # redisコンテナを依存関係に追加
  redis: # Redisコンテナ
    image: redis:latest
    volumes:
      - redis-data:/data # Redisデータの永続化
volumes:
  redis-data:

Gemの追加

Gemfile

gem 'redis-objects'

Redisのイニシャライザを作成する

Redis::ObjectsがRedisに接続できるようにするためRedisの接続情報を設定します。
Redis::ObjectsはRedis::Objects.redisが設定されていない場合Redis.currentをデフォルトで利用できます。1

Railsアプリケーションの場合Redis.currentconfig/initializers/redis.rbで定義します。

config/initializers/redis.rb

# Docker環境のRedisを利用する場合、host名はRedisのコンテナ名にする
Redis.current = Redis.new(host: '127.0.0.1', port: 6379, db: 0)

利用用途1: モデルとRedis::Objectsを紐付け、モデルに関連するデータをRedisに保存する

UserモデルでRedis::Objectsを使用する例をもとに、モデルとRedis::Objectsを紐付けるケースの利用方法について紹介します。

モデルの修正

モデルにRedis::Objectsをインクルードします。

user.rb

class User < ApplicationRecord
+ include Redis::Objects
  counter :my_posts
end

Redis::ObjectsはRedisの各APIに対応したオブジェクトを用意しています。
ですので、以下の方法で利用するRedisオブジェクトのタイプとフィールド名を宣言します。

[Redisオブジェクトのタイプ] [フィールド名]

Redisオブジェクトのタイプの詳細についてはredis-objectsのREADMEをご参照ください。

たとえばmy_postsという名前のRedis::Counterをモデルに紐付ける場合は以下のようになります。なお、Redis::Counterはカウントに関するオブジェクトです。

user.rb

class User < ApplicationRecord
  include Redis::Objects
+ counter :my_posts
end

Redis::Objectsで生成されるデフォルトのキー名は"#{モデル名}:#{self.id}:#{フィールド名}"です。
ですので、上記の例であればuser:[インスタンスのid]:my_postsというキーでRedisにデータが保存されます。

動作確認

モデルとRedis::Objectsを紐づけたので、実際にデータが保存されているか確認します。

$ rails c

### モデルに紐づくRedis情報の確認。イニシャライザの設定が利用されている
> User.redis
=> #<Redis client v4.3.1 for redis://redis:6379/0>

### インスタンスの作成
> user = User.create(name: "Bob", active: true)

### フィールド名を参照するとRedisオブジェクトのタイプがわかる
> user.my_posts
=> #<Redis::Counter:0x000055f89ff358c0>

### Redis::Counterのincrementメソッドを実行
> user.my_posts.increment  # or incr
=> 1

### Redis::Counterのdecrementメソッドを実行
> user.my_posts.decrement # or decr
=> 0

### Redis::Counterのincrement(num)メソッドを実行
> user.my_posts.increment(3)
=> 3

### valueメソッドで値を確認できる
> user.my_posts.value
=> 3

### モデルに紐づくRedisで利用しているキーの一覧
>  User.redis.keys('user:*')
=> ["user:1:my_posts"]

redis-cliでRedisの中身を確認するとRedis::Counterのデータが反映されていることがわかります。

$ redis-cli

### DBの選択
> select 0
OK

### 保存されているキーの数
> dbsize
(integer) 1

### キーの一覧
> keys *
1) "user:1:my_posts"

### valueの種類の確認
> type user:1:my_posts
string

### valueの取得(Stringはgetで取得)
> get user:1:my_posts
"3"

補足事項について

モデルとRedis::Objectsを紐付ける際の補足事項について紹介します。

Redis::Objectsを利用するモデルにidがない場合はidメソッドを作成する

Redis::Objectsはユニークな値を返すidメソッドを提供するクラスで動作します。
ですので、idのない独自クラスでRedis::Objectsを利用する場合はidメソッドを実装する必要があります。

たとえば独自クラスfoo.rbでRedis::Objectsを利用する場合は以下のようになります。

lib/foo.rb

class Foo
  include Redis::Objects
  value :data

  def initialize(id)
    @id = id
  end

  # idメソッドは『attr_reader :id』でもOK
  def id
    @id
  end
end

モデルとRedis::Objectsを紐づけたので、実際にデータが保存されているか確認します。

$ rails c
> require 'foo'
> foo = Foo.new(1)

> foo.data
=> #<Redis::Value nil>

> foo.data = "example data"

> foo.data.value
=> "example data"

redis-cliでRedisの中身を確認すると、Fooモデルのデータが反映されていることがわかります。

$ redis-cli

### DBの選択
> select 0
OK

### 保存されているキーの数
> dbsize
(integer) 1

### キーの一覧
> keys *
1) "foo:1:data"

### valueの種類の確認
> type foo:1:data
string

### valueの取得(Stringはgetで取得)
> get foo:1:data
"example data"

キーを変更したい場合

キー名はkey:で任意の文字列に変更できます。具体例は以下の通りです。

user.rb

class User < ApplicationRecord
  include Redis::Objects
  counter :my_posts, key: ->(instance) { "it:is:new:key:#{instance.id}:good" }
end

なお、idを含めない形のキー名にした場合でもidメソッドの定義は必須です。もしidメソッドがないクラスでRedis::Objectsを利用しようとするとエラーが発生します。
以下はidメソッドがないクラスでRedis::Objectsを利用してエラーになる例です。

lib/foo.rb

class Foo
  include Redis::Objects
  value :data, key: ->(instance) { "foo:#{instance.code}:data" }

  def initialize(code)
    @code = code
  end

  attr_reader :code
end
$ rails c
> require 'foo'
> foo = Foo.new("abc")
> foo.data = "example data"
Traceback (most recent call last):
        2: from (irb):30
        1: from (irb):31:in `rescue in irb_binding'
NoMethodError (undefined method `id' for #<Foo:0x0000557dbeba3c90 @code="abc">)

Redisの接続情報を変更したい場合

[モデル名].redis = Redis.newで再定義をすると接続先のRedisを変更できます。

具体的には以下の通りです。

$ rails c

### イニシャライザで定義されたRedis.currentが利用される
> User.redis
=> #<Redis client v4.3.1 for redis://redis:6379/0>

### dbを0から10に変更する
> User.redis = Redis.new(host: 'redis', port: 6379, db: 10)

### 『redis://redis:6379/10』から分かるように、DBが10に変更されている
> User.redis
=> #<Redis client v4.3.1 for redis://redis:6379/10>

### DB番号10にデータを作成してみる
> user = User.create(name: "Bob", active: true)
> user.my_posts.increment  # or incr
=> 1

redis-cliでRedisの中身を確認すると、Redis::Counterのデータが反映されていることがわかります。

$ redis-cli

### DBの選択
> select 10
OK

### 保存されているキーの数
> dbsize
(integer) 1

### キーの一覧
> keys *
1) "user:1:my_posts"

利用用途2: モデルとは紐付けず、Redis::Objects単体で利用する

redis-objectsのREADMEでスタンドアローンと呼ばれている方式です。
Redisの操作をRedis APIを直接記述するのではなくRubyオブジェクトっぽく記述したい場合にスタンドアローン方式を利用します。

スタンドアローン方式の利用手順は以下の通りです。

スタンドアローン方式の利用手順
  1. 『Redis::[クラス名].new(key名)』でRedisインスタンスを作成する
  2. インスタンスメソッドを利用してインスタンスに保存されているデータを更新する

Redis::ObjectsはRedisの各APIに対応したオブジェクトを用意しています。今回は例としてRedis::Counterの利用方法について紹介します。

### Redisインスタンスの作成
@counter = Redis::Counter.new('counter_name')

### Redis::Counterのインスタンスメソッドを利用してデータ更新
@counter.increment  # or incr
=> 1
@counter.decrement # or decr
=> 0
@counter.increment(3)
=> 3

### valueメソッドで保存されているデータを確認
@counter.value
=> 3

redis-cliでRedisの中身を確認すると、スタンドアローン方式で生成したRedisのデータが反映されていることがわかります。

$ redis-cli

### DBの選択
> select 0
OK

### 保存されているキーの数
> dbsize
(integer) 1

### キーの一覧
> keys *
1) "counter_name"

### valueの種類の確認
> type counter_name
string

### valueの取得(Stringはgetで取得)
> get counter_name
"3"

まとめ

Redis::Objectsのまとめ
  • Redis::Objectsにはモデルと紐付ける方法とスタンドアローンの2種類の使い方がある
  • Railsの場合、Redis::ObjectsのRedisの設定はイニシャライザで定義する
  • モデルで利用する場合はRedis::Objectsのincludeと、Redisオブジェクトのタイプを定義する
  • Redis::Objectsを利用するモデルはidメソッドが必須

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考記事