るびー めも

Ruby の学習メモを記す

Rails で作る、POSシステム(その4)

とりあえず、scaffold で実装してみました。

ソースは、以下に置いてます。

https://github.com/yuji-shimoda/POS

実装した機能は、以下

  • Menu モデルに、商品名と価格を入力する
  • Customer モデルに、卓番(席番号)を入力する
  • Order モデルに、卓番と、商品名、注文数を入力する
  • Customer モデルの各卓番を詳細表示すると、注文したメニュー一覧と合計金額が表示される

すごくシンプルに実装しましたが、基本的な機能は包含されてるはず

Rails アプリ初心者としては、リレーションのあたりで苦戦しました。
#いまいちピンとこないので、分かり難かった。

とりあえず趣味でこんなん作ってみましたーと、夢やのオーナーに連絡してみると
以下の要望をいただきました。

  1. 売り上げ管理機能(商品の出数、来店客数、客単価の算出)が欲しい
  2. メニュー数は、食べ物/飲み物あわせて70品目程度→メニューの種別分けが必要そう

最初のモデルでは、対応出来なさそうなので
再度、モデルで追加すべきプロパティについて検討しなくちゃ

Android タブレットに Rails 環境の構築

普段、おもちゃにしている Android のタブレットがあります。

これ↓です。

最近は、ほとんど遊んでなかったので有効に活用出来ないかと思案しておりました。

最近だと、ユーザーランドで Linux 環境を構築するアプリがある模様。

Complete Linux Installer

Linux が動くなら、Ruby もビルド出来るんじゃない?ということで
テストしてみました。ちなみに動作させる Linuxdebian です。

その結果が↓これ

f:id:yuji_shimoda:20130620173027j:plain

見事に Ruby 2.0 + Rails 3.2 環境が構築出来ました。

ビルドしたソフトウェアは、以下のとおり

  • git
  • Ruby 2.0.0p195

パッケージマネージャーでインストールしたソフトウェアは、以下のとおり

  • nodejs
  • nginx
  • vim
  • sqlite3

当初、therubyracer の gem インストールしようとしてて
v8 が make できずはまりましたが、RC 版の nodejs パッケージ
Package: nodejs (0.10.11~dfsg1-1 and others)を無理矢理入れて、なんとかなりました。

見事に、rails g scaffold でひな形が作成されたので
WEBrick を起動してみました。

f:id:yuji_shimoda:20130620172522j:plain

ブラウザは、Android 側のブラウザが使えるので
内部ディスクに余裕があれば Chrome とかも使えます。

最終的には、nginx と rails を連携させて動かしたりしたいんですが
それは、また今度ということで。

ちなみに nginx は、既に動作済みです。

f:id:yuji_shimoda:20130620142905j:plain

今のところ、不満点は以下です。(ほとんど、タブレットのハードウェア的な問題)

  • 内蔵ディスクの空き領域(700MB)しか、ディスクが使えない(のこり40MB程度)
  • Bluetooth が搭載されていないので、有線キーボードしか使えない(そのうち、Bluetooth モジュールをビルドして、組み込みたい)
  • メモリに余裕がない(空きメモリは、10MB程度)のでブラウザのレスポンスが少し悪い

最近の Nexus7 とかだと、サクサク RailsChrome をいったりきたり出来るんですかね?

Nexus7 欲しーなー。

Rails で作る、POSシステム(その3)

Rails アプリとして、POS システムを作るにあたり
まずは設計が必要ですね。

現実的なモデルを参考に、設計していきます。

前職でお世話になった POS(Point Of Sales)システムは
以下の機器で構成されています。

  • POS レジ (Rails アプリで代替予定)
  • ハンディターミナル (スマホで代替予定)
  • ハンディターミナル用のアクセスポイント(Wifi AP で代替)

POS レジがもっている情報は、以下のとおり

  • お客さんのテーブル番号
  • お客さんの人数
  • 注文された商品名と個数
  • 売り上げ

ハンディで出来ることは、以下です

  • 新規テーブル番号の立ち上げ
  • お客さんからの注文入力(取り消し、変更を含む)
  • 各テーブルの清算金額を参照

とりあえず、上記と同様な機能を含む Rails POS アプリを作成したいので
以下のようなモデルを scaffold 機能で作ればよいかと思います。

  • Menu モデル

+ 商品の作成、削除、修正、表示
 Menu model が持つデータは、以下
 - 商品名
 - 商品個数
 - 商品金額

  • Order モデル

+ 注文の作成、削除、修正、表示
 Order model が持つデータは、以下
 - テーブル番号
 - 注文された商品名と個数

  • Customer モデル

+ 各テーブル番号の作成、削除、修正、表示
 Customer model が持つデータは、以下
 - テーブル番号
 - トータルの注文金額

Menu モデルのデータは、開店前にあらかじめ情報を作成しておき
Order モデルは、スマホで注文データを入力します。
Customer モデルは、PC で精算時に参照すれば良いかなぁという設計。

#ほんとにこんな設計で良いのだろうかぁ、、、

Rails で作る、POSシステム(その2)

前回は、Rails で POS システムを作ろうと思った経緯を書きました。
今回は、POS システムを作るにあたって必要そうな用件を書きます。

夢やにご飯を食べに行った時間は遅く、晩の8時頃でした。
ちょうどピークの時間帯だったのか、店内は満席状態で
店員さんも人数が多く、5〜6名程度で営業していました。

つまり、多くても5、6人にハンディーターミナルが必要です。
Rails で POS システムを作成するのでクライアント(ハンディ)に要求される仕様は、
フルブラウザが動作するスマートフォンiPod touch 可)という判断。

最近の人たちなら、デフォルトでスマートフォンを所有しているはずなので
多分、大丈夫でしょう。(笑

後は、Wifi の AP が余っているのでこれを使います。
PC も少し古いVAIO が遊んでいるので Linux でも入れて
ローカルネットワークを構築すれば、環境は整うと考えてます。
#現時点でのコストは、0円です。(笑

物理的な環境構成は、ほぼコスト0で用意出来そうなのでよしとします。
#スマホがなければ、iPod touch を手配するしかないですかね?
#1台、23000円ほどの出費になりますが、、、

次は設計のおはなし

Rails で作る、POSシステム(その1)

釣りっぽいタイトルですみません。

6/1 に、姫路駅に立ち飲みやへ奥さんと行ってきたときのお話です。

前職の上司や先輩が、店のオーナーや店長をしている
17席程度のカウンターしかないちっちゃなお店が
姫路にできました。お店の名前は確か、「夢や」だったと思います。
6/1 はオープン初日で、子供が出来てから初めて奥さんと二人で外食。

ちなみに、店の雰囲気もご飯の味も折り紙付きですので
お近くにお越しの際は是非。

ただ、席数が少ないため当分の間は込み合っているかもしれませんので
あしからずご了承ください。

色々と、楽しく過ごさせて頂いたのですが
1点だけ残念な点があります。

それは、レジがないこと
別にレジがないのはいいのですが、
店員さんがお客さんから注文を受けると
紙と鉛筆でメモし、御会計時に電卓はじいていました。

つまり、POSシステムが導入されていないのです。

御会計時に、その影響がありました。
お愛想して貰おうと思い、「お愛想」と店員さんに伝えてから
会計が終わるまでに5分くらい掛かってました。

我々の清算時だけではなく、ほとんどのお客さんの清算でも同様です。

POS が導入されていたら、この会計時の時間も短縮されるし
CS(顧客満足度)の向上と売り上げ向上につながると思います。

そんな事を帰路につきながら考えてました。

ふと気になったので、POS システムの導入費用について
調べてみたところ、、、

、、、

えー、ハンディターミナル(よく居酒屋の店員が使ってる機器の名称)が1台、
15万円なんて、、、(汗

小さな個人店だと、原価消却するのに何年掛かるねん、、、

というわけで、オープンソースで POS システムとかないんかいなーと
軽く調べてみましたが、見つかりませんでした。

本格的な POS システムではないけれど
有償のサービス(iPad/iPod touch)使うサービスは見つかりました。
ただし、そこそこの金額(初期費用6万円、月額4千円、機器代金含まず)でした。
参考情報:スマレジ

せっかくの機会ということで、Rails で作ってみようかなぁと考えた次第。

とある Ruby ソースのリファクタリングについて

前回の記事の続きです。

とあるプルリクエストで採用されたコードは、無駄がいっぱいあったのと
メンテナンス性が悪そうだったので、リファクタリングしてみました。

まず、以下の問題がありました。
10000 回ほど、連続して実行させると結果が少し偏る

$ ./roulette.rb M2
ふじい: 2937
すがわら: 2023
おくつ: 2662
なかじま: 2378
$ ./roulette.rb M2
ふじい: 3026
なかじま: 2366
すがわら: 2035
おくつ: 2573
$ ./roulette.rb M2
ふじい: 3089
すがわら: 2074
なかじま: 2183
おくつ: 2654

上記は、rand(10) を rand(100)にすることで結果が(少し)均等になりました。

$ ./roulette.rb M2
ふじい: 2521
すがわら: 2379
なかじま: 2526
おくつ: 2574
$ ./roulette.rb M2
おくつ: 2527
ふじい: 2545
なかじま: 2396
すがわら: 2532
$ ./roulette.rb M2
すがわら: 2398
おくつ: 2474
なかじま: 2470
ふじい: 2658

次に、使用者の目線で考えると Roulette クラスは複数のインスタンス
生成したりすることはなさそうなので、new する必要もないだろうということで
start/speak は、クラスメソッド化しました。

おかげさまで、Roulette.new(arg).start ではなくて
Roulette.start(arg) という風に自然な感じの使い方が出来そうです。

最後に、やたら出てくるコマンドのパスがメンテナンス性を下げていたため
定数に変更し、メンテナンス性を高めました。

以下は、再度プルリクエスト中のソースコードです。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

SAY    = "/usr/bin/say"
PLAYER = "/usr/bin/afplay"
BGM    = "./gaki.mp3"

group  = { "M2" => %w(おくつ すがわら なかじま ふじい),
           "M1" => %w(たけみち こまつ ふるだて),
           "B4" => %w(おくとも かとう いのうえ あべ なかむら ひらが まいた かどわき ほし やぎぬま) }

class Roulette 
  def self.start members
    @result = Hash.new
    @members = members
    @members.each do | member |
      @result[member] = rand(100) + 1
    end
    @result = @result.sort{|a, b| b[1] <=> a[1]}
    if File.exist?("#{SAY}")
      speak
    end
    return @result
  end
  private
  def self.speak 
    result = @result.dup
    result.each_with_index do | elem, index |
      3.times { print "."; sleep(0.5) }
      puts message = "#{elem.join(", ")}点。"
      `#{SAY} #{message}`
      if index == result.length - 1
        threads = []
        puts message = "#{elem[0]}" + "ーーー。アウトーーー。"
        threads.push(Thread.new{`#{PLAYER} #{BGM}`})
        sleep(4)
        threads.push(Thread.new{`#{SAY} #{message}`})
        threads.each{|thread| thread.join}
      end
    end
  end
end

attr = ARGV[0] 
if group.include?(attr)
  Roulette.start(group[attr])
else
  Roulette.start($*)
end

とりあえず、現状これがせいいっぱいなんですが
もっとリファクタリングできるよ!ってな人はコメントお願いします。

とある Ruby ソースのクラス化について

とあるブログの記事で、以下のようなページを見つけました。

ソーシャルコーディング

プルリクエスト Come on てな具合です。

もともとあったソースコードは、以下のとおりでした。

class Array
  def get_point
    number = 0
    number = rand(10) + 1
  end

  def roulette
    return self if self.size <= 1

    master = Hash.new
    master = self.map do |p|
      {
        :name => p,
        :point => get_point 
      }
    end

    master.each do |p|
      3.times do
        print '.'
        sleep 0.5
      end
      puts kyoko = "#{p[:name].to_s.capitalize},  #{p[:point].to_s} 点。"
      system("say #{kyoko}")
    end
    outman = master.sort{|a,b| a[:point] <=> b[:point]}
    puts kyoko = "#{outman.first[:name].to_s.capitalize}ーーー。アウトーーー。"
    system("afplay gaki.mp3")
    system("say #{kyoko}")
  end 
end

case ARGV[0] 
  when "M2"
    year = %w(おくつ すがわら なかじま ふじい)
  when "M1"
    year = %w(たけみち こまつ ふるだて)
  when "B4"
    year = %w(おくとも かとう いのうえ あべ なかむら ひらが まいた かどわき ほし やぎぬま)
  else
    year = %w()
end

year.roulette if ARGV[0]

とある大学生が、とある研究室のゼミで司会者を決めるためにちゃちゃっと作った
ルーレットプログラム。

git の勉強もかねて、プルリクエストしてみるべというのが事の始まり。

まずは、クラス化に着手するため基本的な動作について検討しました。

ルーレットクラスは、引数にメンバーの配列を貰う。
そのメンバー配列をHash 化して rand() でランダムに選出された点数を挿入し
返すだけで良さそう。その他の機能としては、OSX の say(1)でメンバーの名前と点数を
しゃべらせて、最後にBGM と共に最下位メンバーをアウトにする。という動作

最初に行ったクラス化のソースコードは、以下です。

class Roulette 
  def initialize members
    @result  = Hash.new
    @message = Hash.new
    @members = Array.new(members)
  end
  def start
    @members.each do | member |
      @result[member] = rand(10) + 1
    end
    @result = @result.sort{|a, b| b[1] <=> a[1]}
    self.speak
  end
  protected
  def speak 
    result = @result.dup
    result.each_with_index do | elem, index |
      3.times do 
        print "."
        sleep(0.5)
      end
      if index == result.length - 1
        threads = []
        puts  "#{elem.join(", ")}点。"
        puts message = "#{elem[0]}" + "ーーー。アウトーーー。"
        threads.push(Thread.new{`/usr/bin/afplay ./gaki.mp3`})
        sleep(2)
        threads.push(Thread.new{`/usr/bin/say #{message}`})
        threads.each{|thread| thread.join}
      else
        puts message = "#{elem.join(", ")}点。"
        `/usr/bin/say #{message}`
      end
    end
  end
end

Roulette Class のインスタンスメソッドとして、start と speak を定義しました。
また、当初のソースコードで問題だったのが BGM を再生したあとで say(1)を実行しているところ

YouTube とかで、とある番組を確認してみたんですが BGM と平行して
ナレーションされている感じ。そのため、Thread で並列に処理しました。

上記のコードで、人生初めての github Pull Request を実施。
しかも、採用されマージされてしまいました。

ただ、マージされた後で、リファクタリングした方が良いなと思い立ち
第2段のプルリクエストを実施しました。
#実のところは、speak メソッドを何故か、protected にしてしまっていたので
#修正しないといけないというのが、リファクタリングのきっかけかも

リファクタリングについては、次の記事に続きます、、、