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

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

この記事を読むのに必要な時間:およそ 3 分

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

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

マルチアカウント

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

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にする)
  • 誰でも見ることができる,電書別の売上画面を追加

著者プロフィール

松永肇一(まつながけいいち)

株式会社ライフメディア 開発部マネージャ。東京都出身。"GNU for Towns"のためにリチャード・ストールマンに会いにいったり,ビル・アトキンソンとアンディ・ハーツフェルドの開発した"MagicCap"の日本語化を担当したり,よく分からない仕事を経て現在はWebアプリのエンジニア。Rubyon Rails(ときどきSinatra)を使って開発する毎日。スマートフォンアプリのレビューとランキングサイトであるスマートワールドでは電子書籍レビューも担当中。Twitterはma2

コメント

コメントの記入