Movatterモバイル変換


[0]ホーム

URL:


give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

Everyday Railsチャリティセールの経過報告と集計用Railsアプリの紹介

はじめに

これまでこのブログで何度かお知らせしていますが、熊本地震の災害支援のために2016年4月25日から2016年5月31日まで「Everyday Rails - RSpecによるRailsテスト入門」のチャリティセールを実施しています。

今回のエントリでは、昨日(2016年5月15日)時点での収益(=募金額)と、収益を集計するRailsアプリの紹介をします。

開始から3週間で899.55ドル(約9万7000円)に到達!

ありがたいことに、昨日の時点で募金額はなんと899.55ドル(約9万7000円)になりました。

f:id:JunichiIto:20160516043825p:plain

上のグラフを見てもらえれば分かるとおり、日によって多少の違いはあるものの、今のところ毎日売上げが発生しています。
購入してくださったみなさん、どうもありがとうございました。

この調子でいくと当初の目標額だった500ドルの2倍、1000ドル(約10万8000円)も夢じゃないかもしれません。
みなさん、引き続きご協力よろしくお願いします!

Leanpub用・収益集計Railsアプリの紹介

ところで、上のグラフは僕が作ったRailsアプリケーションで表示したものです。
「お金の話だけじゃなくて技術的なネタも読みたい!」と思っているエンジニアさん向けに、このRailsアプリの仕組みを説明します。

処理の流れ

このRailsアプリはこんな感じで処理しています。

  1. LeapubのAPIを叩いて売上データを取得し、データベースに保存する
  2. Web画面を開くとデータベース内のデータを集計し、グラフに表示する

まあ特に変わったところはない、非常にオーソドックスな流れですね。

売上データをAPIからデータベースに保存する処理

LeanpubのAPIからは次のようなJSONデータが返ってきます。
author_royaltiesの1.5(ドル)が収益です。

{"cause_royalty_percentage":"0.0","author_royalty_percentage":"5.0","author_royalties":"1.5","cause_royalties":"0.0","author_paid_out_at":null,"cause_paid_out_at":null,"created_at":"2016-04-21T15:58:45.000Z","royalty_days_hold":45,"publisher_royalties":"0.0","publisher_paid_out_at":null,"author_username":"foo_bar","publisher_slug":"","user_email":"","purchase_uuid":"HlKg4GLtj-fVwM4UqcRj2","invoice_id":"DimXToR5rhc2z_g8A34LM","date_purchased":"2016-04-21T15:58:45.000Z"}

上のJSONは1レコード分なので、実際は複数レコードが配列になって返ってきます。

あとはRails側でAPIを呼び出して、配列をぐるぐるループさせながらJSONデータ(Ruby内ではハッシュとして扱う)を保存していけば終わりです。
実際のコードとは若干異なりますが、イメージ的には以下のようなコードになります。

slug ='everydayrailsrspec-jp'api_key ='(api key)'url ="https://leanpub.com/#{slug}/individual_purchases.json?api_key=#{api_key}"# APIを叩いて売上データを取得hash_array =JSON.load(open(url))hash_array.eachdo |hash|# 1件ずつ売上データをデータベースに保存  purchase_uuid, author_username = hash.values_at('purchase_uuid','author_username')  sale =Sale.find_or_initialize_by(purchase_uuid: purchase_uuid,author_username: author_username)  hash.eachdo |key,value|    sale.send("#{key}=", value)end  sale.save!end
データを保存する処理のテストコード

JSONをパースしてデータベースに保存するところはRSpecでテストも書いています。
(Everyday RailsはそもそもRSpecの本ですからね!)

RSpec.describeSale,type::modeldo  describe'::save_records_from_hash_array!'do    let(:json_text)do      <<-'JSON'[  {     "cause_royalty_percentage":"0.0",     "author_royalty_percentage":"5.0",     "author_royalties":"1.5",     "cause_royalties":"0.0",     "author_paid_out_at":null,     "cause_paid_out_at":null,     "created_at":"2016-04-21T15:58:45.000Z",     "royalty_days_hold":45,     "publisher_royalties":"0.0",     "publisher_paid_out_at":null,     "author_username":"foo_bar",     "publisher_slug":"",     "user_email":"",     "purchase_uuid":"HlKg4GLtj-fVwM4UqcRj2",     "invoice_id":"DimXToR5rhc2z_g8A34LM",     "date_purchased":"2016-04-21T15:58:45.000Z"  }]JSONend    it'creates record'do# メソッドを呼び出すとレコードが増えることを確認      expect {Sale.save_records_from_hash_array!(JSON.parse(json_text))      }.to change(Sale,:count).from(0).to(1)# JSONの各値がちゃんと保存されていることを確認      sale =Sale.first      expect(sale).to have_attributes(cause_royalty_percentage: BigDecimal('0.0'),author_royalty_percentage: BigDecimal('5.0'),author_royalties: BigDecimal('1.5'),cause_royalties: BigDecimal('0.0'),author_paid_out_at:nil,cause_paid_out_at:nil,royalty_days_hold:45,publisher_royalties: BigDecimal('0.0'),publisher_paid_out_at:nil,author_username:'foo_bar',publisher_slug:'',user_email:'',purchase_uuid:'HlKg4GLtj-fVwM4UqcRj2',invoice_id:'DimXToR5rhc2z_g8A34LM',date_purchased:'2016-04-21T15:58:45.000Z'.to_time      )endendend
データを集計する処理

データの集計はがっつりSQLを書いて集計しています。

classSale <ActiveRecord::Basedefself.sum_by_date    sql = <<-SQLWITH jst_sales AS (    SELECT      *,      date_purchased + INTERVAL '9 hours' AS jst_date_purchased    FROM sales),    sum_by_date AS (      SELECT        to_char(jst_date_purchased, 'YYYY/MM/DD') AS purchased_on,        count(DISTINCT purchase_uuid)             AS purchase_count,        sum(author_royalties)                     AS royalties      FROM jst_sales      GROUP BY purchased_on  )SELECT  *,  SUM(royalties)  OVER (    ORDER BY purchased_on) AS cum_royaltiesFROM sum_by_dateORDER BY purchased_on DESCSQL    find_by_sql(sql)endend

"WITH jst_sales AS ( )"の部分はCTE(共通テーブル式)というPostgreSQLの機能を使っています。
CTEはSQL内だけで有効なビュー(Railsではなく、データベースのVIEW)を作り出す機能と考えてもらえばOKです。
まずここで購入日時を日本時間に変換しています。

"sum_by_date AS ( )"の部分もCTEです。
ここは普通に日付単位で収益を集計しています。

一番最後に出てくるSELECT文が最終的に返却するデータのSELECT文になります。
ポイントは "SUM( ) OVER ( ORDER BY )" の部分です。
ここはPostgreSQLのWindow関数という機能を使って、収益の累積値を計算しています。

このSQLを実行すると以下のような結果が返ってきます。

日付購入数収益累計額
2016/05/155$55.31$899.55
2016/05/141$7.60$844.24
............
2016/04/283$22.80$427.29
2016/04/2716$151.94$404.49
2016/04/2617$167.93$252.55
2016/04/259$84.62$84.62
データをグラフとして表示する処理

グラフの表示はFlot というjQuery用ライブラリを使っています。
RailsからJavaScriptには直接データを渡せないので、divタグのdata属性にFlot用に加工したデータを埋め込みます。

<divdata-cum-royalties="[  [1463238000000, 899.55],  [1463151600000, 844.24],  ... ,  [1461510000000, 84.62]]" ...id="placeholder"></div>

"1463238000000"は日付を表すタイムスタンプで、"899.55"が値(ここでは収益の累積値)です。

最後にJavaScript側でデータを読み取って Flot に渡せばおしまいです。

$(function (){// 売上冊数var purchaseCount = $('#placeholder').data().purchaseCount;// 収益の累積値var cumRoyalties = $('#placeholder').data().cumRoyalties;// 目標値var goalData = $('#placeholder').data().goalData;var data =[{    data: purchaseCount},{    data: cumRoyalties},{    data: goalData}];var options ={    legend:{position:"ne"},    colors:["#eb941f","#55a868","#c60c30"],    grid:{      hoverable:true}};// グラフを表示  $.plot("#placeholder", data, options);});
このRailsアプリのソースコード

このRailsアプリのソースコードはGitHubに置いています。
上で説明したコードはブログ用に簡略化してる部分も多いので、処理の詳細を知りたい方はGitHubのコードを参照してください。
(自分用に作ったRailsアプリなので、公開するには恥ずかしい部分もいろいろあります。悪しからず・・・)

まとめ

というわけで、このエントリではEveryday Railsチャリティセールの経過報告と集計用Railsアプリの紹介をしました。
Everyday Railsのチャリティセールは5月31日まで続きます。
引き続きみなさんのご協力をお願いします!

Everyday Rails - RSpecによるRailsテスト入門
f:id:JunichiIto:20160424081240p:plain

チャリティセールの詳細や購入方法についてはこちらのエントリをご覧ください。

Entry Search
Popular Entries
Popular Qiita
Latest Qiita
Readings

引用をストックしました

引用するにはまずログインしてください

引用をストックできませんでした。再度お試しください

限定公開記事のため引用できません。

読者です読者をやめる読者になる読者になる

[8]ページ先頭

©2009-2025 Movatter.jp