電書部技術班、電子書籍配信サーバーに挑む

第8回「電書サーバー 秋」開発と、これから

文学フリマでは一箇所で配信、電書フリマでは複数の場所で同じ電書を配信しました。となると次の目標は「異なる場所で、異なる電書を」です。つまり、販売サイトを複数運用して、それぞれ異なる電書を配信します。個々の販売サイトは独立した電書ショップになります。電書の品揃えも売上管理も独立して行う必要があるでしょう。このバージョンは「電書サーバー 秋バージョン」と呼んでいます。

最終回となる今回は、この電書サーバー 秋バージョンと、今後の展望を説明していきます。

マルチアカウント

販売サイトは電書サーバーのアカウントがあれば作成できます。これまでは、アカウントが異なっても同じ電書を配信していました。アカウントごとに異なる電書の品揃えを持つために、秋バージョンではアカウントのモデルを導入して、電書のリストや売上情報などをアカウント毎に持てるようにします。

WardenPlugin

これまではsinatra-authorizationによるBASIC認証を使っていました。idとパスワードをパラメータで受け取って真偽値を返すメソッドがあればいいのでお手軽なのですが、ユーザの新規追加、パスワードのハッシュ化などの機能はありません。BASIC認証なのでログアウトもできません。そこで、もう少し高機能な認証ライブラリとして、sinatra_more付属のWardenPluginを使うことにしました。

WardenPluginを使うと、アカウントの新規作成、パスワードの確認、パスワードをハッシュ化して保存などが簡単にできるようになります。

バグ

ただし、WardenPluginはバグがあります。idからレコードをひくコードでfindメソッドを使っているのです。これはActiveRecordでは問題ありませんが、DataMapperではfindではなくgetを使います。SinatraMore::WardenPlugin::PasswordStrategyの中にあるので、そこは書き換えるなりパッチを当てるなりしてください。

ユーザーモデル

ユーザー用のモデルとしてDenshoUserを新たに作りました。sinatra_moreにはアプリのひながたを生成するsinatra_gemコマンドが付属しています。これを使ってアプリを作るとWardenPluginで利用可能なUserクラス「user.rb」が生成されます。DenshoUserは、それを改造したものです。UserではなくDenshoUserを使うようにするには以下のようにします。

DenshoUserのモデル定義
class DenshoUser
  include DataMapper::Resource
  has 1, :book_set
  has n, :customers

  property :id,       Serial
  property :name,     String, :default=>'電書部売店''電書部売店'
  property :username, String
  property :email,    String
  property :crypted_password, String, :length=>128128
  # これがfalseのうちはログインできない
  property :ok,       Boolean, :default=>falsefalse
  # okをtrueにできる権限
  property :admin,    Boolean, :default=>falsefalse
  # 割引機能を使うか?
  property :enable_discount, Boolean, :default=>truetrue

  attr_accessor :password, :password_confirmation
  
  before :save, :encrypt_password
  
  validates_presence_of :username, :password, :password_confirmation
  validates_confirmation_of :password
  validates_uniqueness_of :username

  def self.authenticate(username, password)
    user = DenshoUser.first(:username => username) username)
    user && user.has_password?(password) && user.ok > user : nil
  end
  
  def encrypt_password
    self.crypted_password = BCrypt::Password.create(password)
  end
  
  def has_password?(password)
    BCrypt::Password.new(crypted_password) == password
  end
end
Userではなく自前のDenshoUserクラスを使うように設定
Dzz::register SinatraMore::WardenPlugin #まずプラグインを登録する
SinatraMore::WardenPlugin::PasswordStrategy.user_class = DenshoUser #DenshoUserクラスを使うように設定

「validates_presence_of :username, :password, :password_confirmation」とバリデーションが指定されているので、passwordとpassword_confirmationの値が一致していないとsaveが失敗します。コンソールからDenshoUserに値をセットするとき、これだと常にpasswordとpassword_confirmationの値が必要なので使いにくいことがあります。たとえばname属性だけを変更したいときなどです。そういう場合にはsaveではなくsave!を使うとバリデーションを回避できます。

ブックセット

アカウント毎に異なる電書を販売するために、ブックセットを導入しました。アカウントで販売する電書のセットがブックセットです。現在の仕様ではひとつのアカウントにつき、ブックセットは一つです。ブックセットには、登録されている電書はどれでも指定できます。

BookSetクラスのモデル定義
class BookSet
	include DataMapper::Resource
	belongs_to :densho_user
	has n, :book_masters, :through => Resource Resource

	property :id,       Serial
	property :name,     String, :default=>'販売セット''販売セット'

	validates_presence_of :name
end

売り上げ系の画面

売り上げ系の画面(電書別売上、売上詳細、購入者別売上)はアカウント別に表示するようになりました。もちろん売上額もアカウント別に計算しています。

アカウント管理と電書の保護

どの電書を売るかは電書アカウントを持つ人間の判断に任されることになります。まったく知らない人が勝手に電書を売るという事態を避けるために、以下の機能を追加しました。基本的には善意に頼るシステムなので、将来的には変更が必要になるかもしれません。

  • DenshoUserは新規登録しただけでは利用できず、admin権限を持った人による承認が必要(具体的にはokフラグをtrueにする)
  • 誰でも見ることができる、電書別の売上画面を追加

その他の機能

今まで説明できなかった機能を紹介します。

カタログ

カタログは、電書のタイトルや内容の一覧を表示する機能です。これは電書の宣伝や告知のために使い、誰でもアクセスできる画面なので認証はありません。つまりアカウントで切り替えることはできないので、リクエストのパラメータに文字列を渡すようになっています。

各電書の解説文は、電書登録時に設定できるようになっています。

購入代行

購入者別の売上画面には「購入代行」機能があります。購入履歴がある方に、電書を再度配信する機能です。電書データが誤っていた場合や、サーバートラブルで購入したのに配信が行われなかった場合に使います。電書サーバーの唯一のトラブルシューティング機能です。

購入代行ボタン
画像

電書サーバーのこれから

電書変換の自動化

電書変換とS3へのアップロードは、今も人力の作業です。フリマなどのイベントの前になると変換作業が全体のボトルネックになっています。変換と登録の自動化は今後の大きな開発テーマです。

多様な販売形式への対応

電書サーバーは、電書の販売形式に応じて徐々に進歩してきました。販売と一言でいっても、バリエーションは無限にあります。文学フリマバージョンにあった「まとめ買い」も、パーセント割引、何円引き、タイムセール、特典付き、早期予約割引、⁠二冊買ったら一冊おまけ」などがあります。この割引ロジックを入れ替え可能にするなどの工夫が考えられます。

オンライン決済の対応も検討中です。PayPalのような小額決済なら電書販売にマッチするかもしれません。ただ「電子書籍を対面販売する」という電書部のコンセプトとどう折り合いをつけるのか考えないといけませんね。

トラブルシューティング

販売した電書がトラブルでダウンロードできない、ダウンロードできてもデータに誤りがある、間違った電書がダウンロードされる等の問題が起こり得ます。こうした問題を効率よくサポートする機能が購入代行しかありません。より柔軟なサポート機能が必要だと考えています。

Rails 3

前回も述べたようにsinatra_moreはすでにサポートされていません。秋バージョンの開発時にはherokuのgem管理ツールbundlerが1.0にバージョンアップしていたため、sinatra_moreの起動がうまくいかなくなりました。秋バージョンでは、起動プロセスを書き換えています。今後はRails 3などのプラットフォームに乗り換える必要が出てくるでしょう。

テスト

電書サーバーはもともと「部活」として始まった小規模なプロジェクトで、ここまで展開するとは考えてもみませんでした。そのためテストコードが不足しています。これはRails 3に移行するようなタイミングで考え直したいと思います。

最後に

電書サーバーは、電書部技術斑だけではなく、電書部のみなさん、初代部長の米光一成さん、電書を提供してくださった作者の方々、Skypeチャットでアドバイスをくれた人たち、ほんとうにたくさんの方のサポートでなりたっています。そして今も進化が続いています。

11月14日に電書フリマZが新宿ロフトで開催され、秋バージョンのお披露目となりました(サーバートラブルでご迷惑をおかけしました…⁠⁠。11月20日と21日、漫画化のとだ勝之さんには「Make: Tokyo Meeting 06」のイベントで「ホームセンターてんこEX.」を販売していただきました。歌人の佐々木あららさんは「電書行商」と称して、12月2日~11日の10日間、東京から西に電書を売り歩くそうです

そして秋バージョンの最大のターゲットである第十一回文学フリマが、12月5日に東京の蒲田にある大田区産業プラザPiOで開催されます。我々は「電書部(A-20⁠⁠」として参加しています。この連載も時間があれば一冊の電書として販売する予定です。また電書部以外にも複数の電書販売ブースが、この秋バージョンをサーバーとして使います。当日は朝からドキドキものです。お時間があれば、ぜひお越しください。

この電書サーバーで、みなさんが、いつか、面白かったと思える本を入手されることがあれば、開発者として、本好きとして、これほどうれしいことはありません。

おすすめ記事

記事・ニュース一覧