2016 年の振り返り(仮面ライダー限定)
去年の大晦日、以下のようなエントリを書きました。需要があるかというとあんまりなさそうですが、今年の進捗を報告すべく、続編を書くことにしました。
今年の息子との遊び
ガンバライジングは 2 月くらいまではやっていました。やらなくなった理由は、息子が IC カードを紛失するという失態をおかしてしまったためです。
夏は流行りにのって、ポケモン GO をそれなりにやっていたようです。電動アシスト自転車で息子と出かけては、公園などを巡ったりしていました。
ブットバソウルとは
今年の 8 月から始まった新しいガシャポンゲームです。妖怪ウォッチをご存知の方であれば「おみくじ神社」「ドリームルーレット」と同じようなやつだと思っていただればと(そういう方がこのブログを読んでいるか謎ですが)。
- 遊ぶこと自体は無料
- お賽銭的にガシャポンで手に入れたメダルを登録すると一回遊べる、一度登録したメダルはもう使えない(ただし筐体単位)
- ゲームで勝つと特別なガシャポンを回せる権利が得られ、激レアメダルがゲットできる
ゲームとしては、それほど難しくなくて
- 敵の弱点に合わせたシリーズマーク・バトルタイプのメダルを 3 個選ぶ
- 敵の弱点に合わせたエナジーアイテムというメダルを 1 個選ぶ
- ボタン連打で敵と戦って勝利する
- チャンスタイムを見守る
という流れでわかりやすいので(去年より大きくなったというのもありますが)このゲームに関しては完全に息子一人で勝手にやっています。
今年の戦果
メダル大集合
ライダーパワー 4,000 以上のメダル達です。メダルといっても基本的には妖怪メダルと同様、プラスティック製なのですが、ゲームに勝利してゲットした黄金のレアメダルだけは金属製になっていてずっしりと重たいです。
はじめてのレアメダル
初めてのレアメダル。8 月の開始当初なので 00 弾です。
これは白銀のレアメダルでなのでプラスティック製です。
http://bs.gashapon.jp/products/sp_medal.php
映画特典 5 点セット
こちらはこれは映画の前売り特典で入手したものです。
12/10 に公開された仮面ライダーの映画前売り特典です。
www.toei.co.jp www.movie-taisen.com
そういえば封切りの日に映画観るの初めてな気がするが、それが仮面ライダーだとは昔は想像もしなかった。
— seratch(ja) (@seratch_ja) 2016年12月10日
エナジーアイテム
これは 01 弾から登場したエナジーアイテムと呼ばれているものです。出た当初、うちはこれを持っていなくてチャンスタイムまで到達できないことが多々ありました。
カードバインダー
オフィシャルカードバインダーを使っています。
http://bs.gashapon.jp/products/binder.php
ただ、ちょっと収納しづらいのとボロボロになりやすいのが難点です。そこまでお金かけるつもりがないので買わないですが、ケースの方がいいんでしょうね。
まとめ
ということで、これらのゲームについてご存知の方ならお分かりの通り、そこまで熱心にプレイしたわけでもないのですが、今年はポケモン GO とブットバソウルの年でした。
来年・・・そろそろ、友達と遊ぶことの方が多くなってくるだろうし、こういうのも来年までか、長くてもせいぜいあと 2 〜 3 年かもしれないですね。
macOS で「はがきデザインキット」を使った年賀状宛名印刷の手順
毎年、年賀状のためだけに Windows マシンを保全して年に一度 12 月だけ起動する *1ということをやっていたのですが、そのマシンもいよいよ退役となり、普段使っている MacBook Pro でスムーズに年賀状の宛名印刷をできないものかということで、今年やったことをメモしておきます。
使ったもの
使ったものは、これだけです。
- MacBook Pro 13-inch Early 2015 (macOS Sierra / 10.12.2)
- 日本郵便の「はがきデザインキット」インストール版
- Numbers(スプレッドシート)
- インクジェットプリンター Canon PIXUS iP2700
Windows 用の別ソフトで使っていた宛名一覧をダンプした CSV ファイルが存在しているという状態だったので、ここから移行していきたいという状況です。
注意すべき点
作業手順としてはスプレッドシートで宛名一覧を編集して CSV に出力したものを「はがきデザインキット」に「住所読込」してから、プリンターで印刷という流れです。いくつか注意点があるので、書いておきます。
- macOS の言語設定が日本語でないと「住所読込」「住所書出」が文字化けするので、普段、英語で使っていてもこの作業のときだけ OS の言語設定を日本語に戻す
- 「住所書出」で出力する CSV ファイルは UTF-8 で出力できないので、一度試しに出力した後はスプレッドシートに読み込んだものをマスターデータにして「住所読込」のみで作業した方がよい
- 「住所読込」で CSV ファイルの空行が無視されずに空の宛名として取り込まれてしまうので、CSV は空行がない状態で出力する
- 敬称で「くん」を指定すると「住所読込」でエラーにならず、単に無視されるので、男の子は「君」づけにする
- 「あて名面レイアウト」は事前に十分調整しないとダメ(郵便番号はデフォルトより少し下に下げる、他の項目も上下の幅を広げる、場所をずらすなどした方がよい)
- 好みの問題ではあるが、フォントはデフォルト選択の明朝体より楷書体の方が私は良い気がした
- 印刷時の「対象プリンタ」はデフォルト選択の「任意のプリンタ」ではなく、連携しているプリンタを選択する(そうしないと適切な「はがき」設定を選べない)
上記の点だけ注意すれば、あとは特にハマることはないかと思います。
手順
では、どのようにやったか手順をざっと紹介します。
はがきデザインキットをインストール
https://yu-bin.jp/create/design_kit/
この「インストール版」を選んで
「いますぐダウンロード」*2! Adobe AIR が必要です。
http://yubin-nenga.jp/design_kit/install/
「あて名面作成」を選択
起動したら「あて名面作成」をクリックします。今回の手順では、はがきの面はすでに印刷済という前提なので、この機能しか使いません。
CSV の雛形を入手する
CSV として出力するためには 1 件以上のあて名を指定する必要があります。そのために、以下のように適当な内容であて名を 1 件追加して保存します。
「すべて追加」を押した後、「CSV 書出」を押すと保存ダイアログが表示されます。
デフォルトは「address_list.csv」ですが、好きな名前で保存します。
この address_list.csv を Numbers で開くと以下のように表示されるはずです。これをベースにコピーしていくとよいかと思います。
あて名を編集する
基本的には行を増やしていくだけですが、いくつかやってみて気づいた点を。
- 「リスト表示用氏名」は「=A2 & " " & B2」としておくのが良さそう(人数が多いなら「連名1(姓名:自宅欄)」なども同様)
- 最初の注意点にも書いた通り CSV 出力する前に空行は除去しておく方がよい
Numbers で作業する場合は、この「address_list.numbers」をマスターファイルとして保管しておくとよさそうです。
CSV 出力して「はがきデザインキット」に取り込む
Numbers の出力機能で CSV をつくります。
これを取り込みます。「はがきデザインキット」の方で柔軟に重複チェックをしてくれるわけではないので、CSV ファイルをマスターとして、読み込み済のものは全削除してから取り込むという流れでやるのがわかりやすいと思います。つまり、以下のスクリーンショットで赤く囲まれている「すべて選択」を押して「削除」した上で「住所読込」をします。
「差出人登録・あて名面設定」
「差出人登録・あて名面設定」で差出人情報とあて名面のレイアウト情報を調整します。
差出人情報を保存したら、次は以下の画面でフォント・レイアウトを調整します。
私は楷書体の方が好みだったので変更したのと、はがきの上に表示されている各エリアは拡大縮小・移動が自由にできるので、よしなに調整しました。特に差出人欄はデフォルトだと郵便番号欄にかかってしまっていたり、差出人の氏名が小さかったりするので必ず調整が必要です。このプレビューは印刷時とほとんどズレませんが、郵便番号は少し下に移動しておく方が綺麗に収まるかと思います(プリンタによってはまた違うかもしれませんが)。変更例は以下の通り。
いよいよ印刷
「差出人登録・あて名面設定」から差出人欄を設定した後、「すべて選択」して「あて名面の印刷」を押します。
以下のようにプレビューが表示されます。この時点でレイアウトを手直ししたくなったら「あて名面レイアウト一括設定」で再度修正します。
「印刷」を押していよいよ印刷に進みます。ここでデフォルト選択の「任意のプリンタ」ではなく、連携しているプリンタを選択してください。
「はがき」を選んで「年賀はがき」をプリンタにセットしたらいよいよ印刷です。(年賀状印刷をやっている方は釈迦に説法ですが)まず何度かはがき以外の紙に試し刷りして「あて名面レイアウト一括設定」で微調整を繰り返してから、実際にハガキに刷ると良いかと思います。
まとめ
ということで、お世話になっている方々へきれいな宛名印刷で年賀状を届けることができそうです。
「はがきデザインキット」は使いやすいソフトウェアなので、来年も使うだろうと思います。*3
ということで、少し早いですが 2016 年も大変お世話になりました。2017 年も引き続きよろしくお願い申し上げます。
Skinny Framework Getting Started 日本語版
この記事は Scala福岡2016 - connpass でのハンズオン向けの入門記事です。
JDK (Java SE Development Kit) インストール
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Oracle 社の Web サイトにアクセスして、License Agreement に同意してから自分のプラットフォームにあったインストーラをダウンロードして実行してください。
インストール後はターミナルから java コマンドに PATH が通っていることを確認してください。java -version
でエラーにならず以下のような出力が表示されれば OK です。
$ java -version java version "1.8.0_71" Java(TM) SE Runtime Environment (build 1.8.0_71-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, mixed mode) $
skinny ダウンロード
Windows / Linux の場合
https://github.com/skinny-framework/skinny-framework/releases/download/2.1.1/skinny-blank-app-with-deps.zip をダウンロードして解凍したディレクトリで skinny スクリプトを使って以降の作業をします。
http://skinny-framework.org/ にダウンロードボタンがあります。
Mac OS X の場合
brew update brew install skinny
だけで OK です。Homebrew で始める場合は skinny new
コマンドが使えます。
skinny new hello-skinny cd hello-skinny
と実行してください。
Homebrew を使わない場合は Windows / Linux と同様、skinny-blank-app-with-deps.zip をダウンロードして解凍します。
これ以降の作業は全て共通です。
プロジェクトの動作確認
ここからは skinny スクリプトがあるディレクトリで作業します。Windows のコマンドプロンプトで作業する場合は ./skinny
を skinny
で読み替えてください。
$ ./skinny run [info] Loading project definition from /Users/kazuhirosera/tmp/hello-skinny/project [info] Set current project to skinny-blank-app-dev (in build file:/Users/kazuhirosera/tmp/hello-skinny/) 2016-05-23 21:45:31.340:INFO::pool-11-thread-3: Logging initialized @8585ms 2016-05-23 21:45:31.461:INFO:oejs.Server:pool-11-thread-3: jetty-9.2.17.v20160517 2016-05-23 21:45:31.858:INFO:oejw.StandardDescriptorProcessor:pool-11-thread-3: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet 2016-05-23 21:45:32.091 INFO --- [ool-11-thread-3] skinny.micro.SkinnyListener : The cycle class name from the config: Bootstrap 2016-05-23 21:45:32.260 DEBUG --- [ool-11-thread-3] skinny.micro.SkinnyListener : Loaded lifecycle class: class Bootstrap 2016-05-23 21:45:32.309 INFO --- [ool-11-thread-3] skinny.micro.SkinnyListener : Initializing life cycle class: Bootstrap 2016-05-23 21:45:32.739 DEBUG --- [ool-11-thread-3] scalikejdbc.ConnectionPool$ : Registered connection pool : ConnectionPool(url:jdbc:h2:file:./db/development;MODE=PostgreSQL;AUTO_SERVER=TRUE, user:sa) using factory : <default> 2016-05-23 21:45:33.288:INFO:oejsh.ContextHandler:pool-11-thread-3: Started o.e.j.w.WebAppContext@77261de5{/,[file:/Users/kazuhirosera/tmp/hello-skinny/src/main/webapp/],AVAILABLE} 2016-05-23 21:45:33.310:INFO:oejs.ServerConnector:pool-11-thread-3: Started ServerConnector@62b55b27{HTTP/1.1}{0.0.0.0:8080} 2016-05-23 21:45:33.311:INFO:oejs.Server:pool-11-thread-3: Started @10556ms [success] Total time: 2 s, completed May 23, 2016 9:45:33 PM 1. Waiting for source changes... (press enter to interrupt)
スタックトレースが出力されず 1. Waiting for source changes... (press enter to interrupt)
で止まれば OK です。
この状態で http://localhost:8080/ で Web サーバ(Jetty)が立ち上がっています。停止させたい場合は Enter や Ctrl + C を押すとアプリが停止します。
それでは IntelliJ IDEA を使いたい人向けの解説を続けます。セットアップの必要ないエディタを使う方は読み飛ばしてください。Eclipse は筆者が Scala では使っておらずお勧めもできないため省略させていただきます。
ファイルの説明
http://skinny-framework.org/documentation/getting-started.html#project-structure
. ├── README.md # 自動生成された README です、プロジェクトに合わせて書き換えてください ├── bin │ └── sbt-launch.jar # ./skinny や ./sbt が使う sbt の launcher です ├── build.sbt # これと project/Build.scala、project/plugins.sbt が sbt の設定ファイルです ├── project # sbt が使用するディレクトリです │ ├── Build.scala # メインのビルド設定ファイルです │ ├── build.properties # sbt 自体のバージョンを指定します、この記事時点で 0.13.11 が最新です │ └── plugins.sbt # sbt プラグインを追加する場合はここに追加します ├── sbt # ./skinny が使用する sbt 起動スクリプト ├── sbt.bat # 同上、Windows 向け ├── skinny # skinny スクリプト ├── skinny.bat # 同上、Windows 向け ├── src # Scala/Java のお作法的に src の下にソースコードや設定を置いていきます │ ├── main # こちらが実アプリのディレクトリ │ │ ├── resources # 設定ファイルなどを置く場所 │ │ │ ├── application.conf # アプリケーションの設定ファイルです │ │ │ ├── logback.xml # skinny が使用する logback というログライブラリの設定ファイルです │ │ │ └── messages.conf # 入力チェックエラーメッセージなどをここで指定します、i18n(国際化)対応 │ │ ├── scala # scala ソースコードの置き場所 │ │ │ ├── Bootstrap.scala # skinny アプリケーションが最初に呼び出すクラスです、名前は決め打ちです │ │ │ ├── controller # controller を置く場所です、自由に変更してもコンパイルが通るなら問題ありません │ │ │ │ ├── ApplicationController.scala # デフォルトの親 controller です、Ruby on Rails にならった命名ですが、リネームしても問題ありません │ │ │ │ ├── Controllers.scala # ルーティングはここで指定してください │ │ │ │ └── RootController.scala # http://localhost:8080/ はこの controller を呼び出します │ │ │ ├── lib # util クラスなど置き場に困るようなコードはここに置いてください │ │ │ ├── model # Rails でいう model としての置き場ですが、service や repository などが好みであれば変えても問題ありません │ │ │ │ └── package.scala # デフォルトでこの package 全体で共有したいものがあればここに書きます │ │ │ └── templates # Scalate というデフォルトのテンプレートエンジンが期待する package と class です │ │ │ └── ScalatePackage.scala # Scalate の設定 │ │ └── webapp # Servlet の規約で置かれているディレクトリ │ │ └── WEB-INF │ │ ├── assets │ │ │ ├── build.sbt # Scala.js 用の設定ファイルです、Scala.js を使わないなら不要です │ │ │ ├── coffee # CoffeeScript を使って開発したい場合はここに *.coffee を置きます │ │ │ ├── jsx # React を使って開発したい場合はここに *.jsx を置きます │ │ │ ├── less # LESS を使って開発したい場合はここに *.less を置きます │ │ │ ├── scala # Scala.js を使って開発したい場合はここに *.coffee を置きます │ │ │ └── scss # LESS を使って開発したい場合はここに *.sass/scss を置きます │ │ ├── layouts │ │ │ └── default.ssp # デフォルトのレイアウトテンプレートです │ │ ├── views │ │ │ ├── error # HTTP ステータス 40x/50x のときに表示されるエラーページです、デフォルトでは ssp が使われます │ │ │ │ ├── 403.html.jade │ │ │ │ ├── 403.html.mustache │ │ │ │ ├── 403.html.scaml │ │ │ │ ├── 403.html.ssp │ │ │ │ ├── 404.html.jade │ │ │ │ ├── 404.html.mustache │ │ │ │ ├── 404.html.scaml │ │ │ │ ├── 404.html.ssp │ │ │ │ ├── 406.html.jade │ │ │ │ ├── 406.html.mustache │ │ │ │ ├── 406.html.scaml │ │ │ │ ├── 406.html.ssp │ │ │ │ ├── 500.html.jade │ │ │ │ ├── 500.html.mustache │ │ │ │ ├── 500.html.scaml │ │ │ │ ├── 500.html.ssp │ │ │ │ ├── 503.html.jade │ │ │ │ ├── 503.html.mustache │ │ │ │ ├── 503.html.scaml │ │ │ │ └── 503.html.ssp │ │ │ └── root │ │ │ └── index.html.ssp # RootController が render("/root/index") を呼び出していますが、このファイルが読み込まれます │ │ └── web.xml # Servlet の設定ファイルです │ └── test # テストコード、テスト用の設定ファイルの置き場です │ ├── resources │ │ ├── factories.conf # FactoryGirl を使った fixture 用のファイルです │ │ └── logback.xml # テスト時に使用されるログ設定です │ └── scala # テストソースコードの置き場です │ ├── controller │ │ └── RootControllerSpec.scala # MockController を使った controller のテストです │ └── integrationtest │ └── RootController_IntegrationTestSpec.scala # Jetty を起動した HTTP リクエストによるインテグレーションテストです └── task └── src └── main └── scala └── TaskRunner.scala # db:migrate などのタスク実行設定がされているタスクランナーです
IntelliJ IDEA の設定
この説明は IntelliJ IDEA 2016.1.2 を前提としています。違うバージョンの場合、挙動が違う場合があるのでご注意ください。
まず Open を選んで、先ほど用意した skinny プロジェクトのディレクトリにアクセスします。
このようにディレクトリ自体が青色のアイコンになっていれば skinny プロジェクト(というより sbt プロジェクト)として認識されています。これ以降の手順を進めてください。
以下のスクリーンショットでは hello-skinny
となっていますが、zip を解凍した方は skinny-blank-app
となっていますので、読みかえてください。
もし普通のディレクトリのように肌色で表示されていたら、ターミナルから ./skinny idea
を実行してから IntelliJ IDEA の Open を試してください(一度 IDEA を再起動してから Open を試した方がいいかもしれません)。
このように sbt プロジェクトとして import する設定があらわれます。デフォルトで OK ですので、このまま進めてください。
このように処理が始まるのでしばらく待ちます。
このように 4 つのプロジェクトを読み込むかどうか聞かれますが、このまま OK を押してください。
おそらく No Scala SDK in module と表示されていて、Scala ソースコードもコンパイルエラー表示になっているかと思います。Setup Scala SDK
というリンクから設定してください。
このようなダイアログで OK を押します。Scala SDK が未設定の場合は洗濯して設定します。このスクリーンショットでは 2.11.7 になっていますが 2.11.8 が選べるならその方が望ましいですが 2.11.x ならどれでも大丈夫です。
しばらく待って src/main/scala/controller/RootController.scala などをクリックして開いてみて赤いコンパイルエラー表示がなくなっていればセットアップ完了です。
設定がおかしくなったら
- IntelliJ IDEA を終了させる
.idea
ディレクトリを削除する./skinny idea
コマンドを実行する- IntelliJ IDEA を起動して対象のディレクトリを Open して import を試みる
を試してみてください。
最初のコード生成
以下のページにならって最初のコードを自動生成してみましょう。
Getting Started - Skinny Framework
./skinny g scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate] ./skinny db:migrate ./skinny run
を実行するだけです。一つ一つのコマンドについて説明してきます。
$ ./skinny g scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate] [info] Running TaskRunner generate:scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate] *** Skinny Generator Task *** "src/main/scala/controller/ApplicationController.scala" skipped. "src/main/scala/controller/MembersController.scala" created. "src/main/scala/controller/Controllers.scala" modified. "src/test/scala/controller/MembersControllerSpec.scala" created. "src/test/scala/integrationtest/MembersController_IntegrationTestSpec.scala" created. "src/test/resources/factories.conf" modified. "src/main/scala/model/Member.scala" created. "src/test/scala/model/MemberSpec.scala" created. "src/main/webapp/WEB-INF/views/members/_form.html.ssp" created. "src/main/webapp/WEB-INF/views/members/new.html.ssp" created. "src/main/webapp/WEB-INF/views/members/edit.html.ssp" created. "src/main/webapp/WEB-INF/views/members/index.html.ssp" created. "src/main/webapp/WEB-INF/views/members/show.html.ssp" created. "src/main/resources/messages.conf" modified. "src/main/resources/db/migration/V20160523235117__Create_members_table.sql" created. [success] Total time: 8 s, completed May 23, 2016 11:51:17 PM
この時点で MVC のファイルが生成されて、ルーティング情報も設定済です。どのようになっているか skinny routes
で確認してみましょう。:id
は path パラメータで URL の一部が controller にパラメータとして渡されます。:id
の値は後述の members テーブルの id です。:ext
は json
か xml
でアクセスできます。
GET /? GET /assets/css/* GET /assets/js/* GET /members POST /members GET /members.:ext POST /members.:ext GET /members/ POST /members/ DELETE /members/:id GET /members/:id PATCH /members/:id POST /members/:id PUT /members/:id DELETE /members/:id.:ext GET /members/:id.:ext PATCH /members/:id.:ext POST /members/:id.:ext PUT /members/:id.:ext GET /members/:id/edit GET /members/new
手順に戻ります。./skinny db:migrate
でこのファイル DB に必要な members テーブルを作成します。
create table members ( id bigserial not null primary key, name varchar(512) not null, activated boolean not null, lucky_number bigint, birthday date, created_at timestamp not null, updated_at timestamp not null )
Skinny ではデフォルトでファイルベースのデータベースと連携するよう設定されていますが、MySQL などに変更も可能です。
$ ./skinny db:migrate [info] Running TaskRunner db:migrate 2016-05-23 23:51:34.150 DEBUG --- [ run-main-0] scalikejdbc.ConnectionPool$ : Registered connection pool : ConnectionPool(url:jdbc:h2:file:./db/development;MODE=PostgreSQL;AUTO_SERVER=TRUE, user:sa) using factory : <default> 2016-05-23 23:51:34.165 INFO --- [ run-main-0] o.f.core.internal.util.VersionPrinter : Flyway 4.0.1 by Boxfuse 2016-05-23 23:51:34.577 INFO --- [ run-main-0] o.f.c.i.dbsupport.DbSupportFactory : Database: jdbc:h2:file:./db/development (H2 1.4) 2016-05-23 23:51:34.722 INFO --- [ run-main-0] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.020s) 2016-05-23 23:51:34.742 INFO --- [ run-main-0] o.f.c.i.metadatatable.MetaDataTableImpl : Creating Metadata table: "PUBLIC"."schema_version" 2016-05-23 23:51:34.766 INFO --- [ run-main-0] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >> 2016-05-23 23:51:34.766 INFO --- [ run-main-0] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version 20160523235117 - Create members table 2016-05-23 23:51:34.792 INFO --- [ run-main-0] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.050s). [success] Total time: 9 s, completed May 23, 2016 11:51:34 PM $
ここまでできていれば ./skinny run
を実行して 1. Waiting for source changes... (press enter to interrupt)
が表示されたら、http://localhost:8080/members にアクセスしてみてください。このような CRUD 画面が自動生成されているはずです。
「New」ボタンを押すとこのように validation も設定済の入力画面が表示されます。
テストも自動生成されているので実行してみましょう。
./skinny db:migrate test ./skinny test
で、以下のように出力されます。
$ ./skinny test [info] RootControllerSpec: [info] RootController [info] - shows top page [info] MembersController_IntegrationTestSpec: [info] - should show members [info] - should show a member in detail [info] - should show new entry form [info] - should create a member [info] - should show the edit form [info] - should update a member [info] - should delete a member [info] RootController_IntegrationTestSpec: [info] - should show top page [info] MemberSpec: [info] MembersControllerSpec: [info] MembersController [info] shows members [info] - shows HTML response [info] - shows JSON response [info] shows a member [info] - shows HTML response [info] shows new resource input form [info] - shows HTML response [info] creates a member [info] - succeeds with valid parameters [info] - fails with invalid parameters [info] - shows a resource edit input form [info] - updates a member [info] - destroys a member [info] Run completed in 13 seconds, 149 milliseconds. [info] Total number of tests run: 18 [info] Suites: completed 5, aborted 0 [info] Tests: succeeded 18, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [success] Total time: 23 s, completed May 23, 2016 11:58:23 PM
生成されたコードを理解する
Controller
Controller のコードは差分のみが実装された非常にシンプルなものになっているのでどうすればいいか戸惑うのではないかと思います。
// $ cat src/main/scala/controller/MembersController.scala package controller import skinny._ import skinny.validator._ import _root_.controller._ import model.Member class MembersController extends SkinnyResource with ApplicationController { protectFromForgery() override def model = Member override def resourcesName = "members" override def resourceName = "member" override def resourcesBasePath = s"/${toSnakeCase(resourcesName)}" override def useSnakeCasedParamKeys = true override def viewsDirectoryPath = s"/${resourcesName}" override def createParams = Params(params).withDate("birthday") override def createForm = validation(createParams, paramKey("name") is required & maxLength(512), paramKey("lucky_number") is numeric & longValue, paramKey("birthday") is dateFormat ) override def createFormStrongParameters = Seq( "name" -> ParamType.String, "activated" -> ParamType.Boolean, "lucky_number" -> ParamType.Long, "birthday" -> ParamType.LocalDate ) override def updateParams = Params(params).withDate("birthday") override def updateForm = validation(updateParams, paramKey("name") is required & maxLength(512), paramKey("lucky_number") is numeric & longValue, paramKey("birthday") is dateFormat ) override def updateFormStrongParameters = Seq( "name" -> ParamType.String, "activated" -> ParamType.Boolean, "lucky_number" -> ParamType.Long, "birthday" -> ParamType.LocalDate ) }
ベタに書いた Controller や SkinnyResource の実装を見てみてわからない点があれば私やチューター係の人に聞いてみてください。
- https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/CompaniesController.scala
- https://github.com/skinny-framework/skinny-framework/blob/master/framework/src/main/scala/skinny/controller/SkinnyResourceActions.scala
ルーティング定義は src/main/scala/controller/Controllers.scala で有効になっています。ルーティング定義のサンプルはこの辺にあります。
- https://github.com/skinny-framework/skinny-framework/blob/master/example/src/main/scala/controller/Controllers.scala
- https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/Controllers.scala
Model
ここでいう Model は Ruby on Rails にならってデータベースアクセスが可能なモジュールというニュアンスです。実際に service レイヤーを設けて entity / DAO を分離して開発することもできますが Skinny のデフォルトのやり方は Ruby on Rails にならったこのスタイルです。データベースアクセスは以下のようにして REPL(Scala コードを実行する対話環境)で試してみましょう。
$ ./skinny console
REPL が起動したら、まずはデータが入っていない状態で全件取得してみましょう。
scala> Member.findAll() [SQL Execution] select m.id as i_on_m, m.name as n_on_m, m.activated as a_on_m, m.lucky_number as ln_on_m, m.birthday as b_on_m, m.created_at as ca_on_m, m.updated_at as ua_on_m from members m order by m.id; (0 ms) [Stack Trace] ... skinny.orm.feature.FinderFeatureWithId$class.findAll(FinderFeature.scala:57) model.Member$.findAll(Member.scala:17) ... res1: List[model.Member] = List()
レコードを insert してみましょう。
scala> Member.createWithAttributes('name -> "Alice", 'activated -> false) [SQL Execution] insert into members (name, activated, created_at, updated_at) values ('Alice', false, '2016-05-24 00:17:34.008', '2016-05-24 00:17:34.008'); (0 ms) [Stack Trace] ... skinny.orm.feature.CRUDFeatureWithId$class.createWithNamedValues(CRUDFeature.scala:213) model.Member$.skinny$orm$feature$TimestampsFeatureWithId$$super$createWithNamedValues(Member.scala:17) skinny.orm.feature.TimestampsFeatureWithId$class.createWithNamedValues(TimestampsFeature.scala:24) model.Member$.createWithNamedValues(Member.scala:17) skinny.orm.feature.NoIdCUDFeature$class.createWithAttributes(NoIdCUDFeature.scala:122) model.Member$.skinny$orm$feature$CRUDFeatureWithId$$super$createWithAttributes(Member.scala:17) skinny.orm.feature.CRUDFeatureWithId$class.createWithAttributes(CRUDFeature.scala:277) model.Member$.createWithAttributes(Member.scala:17) ... res3: Long = 2
この状態でレコード件数をカウントしてみます。1 件になっているはずです。
scala> Member.count() [SQL Execution] select count(1) from members; (4 ms) [Stack Trace] ... skinny.orm.feature.CalculationFeature$class.count(CalculationFeature.scala:30) model.Member$.count(Member.scala:17) ... res5: Long = 1
このように where 句を指定することもできます。
scala> Member.where('name -> "Alice").where('activated -> false).apply() [SQL Execution] select m.id as i_on_m, m.name as n_on_m, m.activated as a_on_m, m.lucky_number as ln_on_m, m.birthday as b_on_m, m.created_at as ca_on_m, m.updated_at as ua_on_m from members m where m.name = 'Alice' and m.activated = false; (0 ms) [Stack Trace] ... skinny.orm.feature.QueryingFeatureWithId$EntitiesSelectOperationBuilder.apply(QueryingFeature.scala:326) ... res8: List[model.Member] = List(Member(2,Alice,false,None,None,2016-05-24T00:17:34.008+09:00,2016-05-24T00:17:34.008+09:00))
より詳しい操作についてはドキュメントを参考にしてみてください。
View
表示する部分は上記のコマンドの場合、SSP (Scala Server Pages) で生成されています。JSP や Velocity に似たものでループや分岐など必要な処理を素直に書くことができます。
公式ドキュメントや Scalate のドキュメントを参照してください。
http://skinny-framework.org/documentation/view-templates.html
またこちらは Play Framework を使ったサンプルになりますが view template は認証ページなどのみにとどめてサーバから JSON のみを返して JavaScript で処理するようにしても良いでしょう。
https://github.com/skinny-framework/skinny-orm-in-play
やってみよう
TODO 管理アプリを作ってみる
task テーブルを設計して scaffold して TODO 管理アプリを scaffold してみましょう。JavaScript + JSON API の構成にして TodoMVC にしてもよいですね。
一応、こちらでサンプルをつくってみましたので、迷ったら参考にしてみてください。
GitHub - seratch/scala-fukuoka-hands-on-demo: Skinny Workshop at Scala 福岡 2016
Typetalk 連携のアプリを作ってみる
以下は今回の会場を提供してくださっているヌーラボ様の Typetalk の OAuth 2.0 と連携するサンプルアプリケーションです。
これをベースに Typetalk に投稿するアプリをつくったり、他のサービスでもログインできるようにしてみたりしてみてはどうでしょう?
see also: http://skinny-framework.org/documentation/oauth.htmlskinny-framework.org
Redmine のデータベースから reverse-scaffold してみる
以下は Redmine のデータベースから reverse scaffold してみた結果です。生成してから全く手を加えずにちゃんと動いています。
Skinny の reverse-scaffold は FK(外部キー)がない場合は勝手に association を生成しません。上記の model クラスに適切な belongsTo や hasMany を定義してみて ./skinny console
で join クエリの動作を確認してみるのも良いでしょう。また、何か他の既存データベースをつかって生成してみてもよいでしょう。
Scala.js を試してみる
Skinny では ./skinny scalajs:watch
を実行するだけで Scala.js を使った開発を始めることができます。Scala.js に興味がある方はこれを機にぜひ触ってみてください。
Assets Support - Skinny Framework
FAQ
java.net.BindException: Address already in use
というエラーが出たら、すでに別の terminal で./skinny run
していないか確認してくださいUnsupported major.minor version 51.0
のようなエラーは JDK のバージョンが古くないかjava -version
で確認してください
OSS Gateワークショップ 3/26 にメンターとして参加した #oss_gate
こちらのるびまを読んで、そういえばブログを書きましょうと話があったのをすっかり忘れて書いてなかったことに気づいたので先日の OSS Gate に参加したときのことを書いてみます。
Rubyist Magazine - Rubyist Hotlinks 【第 36 回】 須藤功平さん
私が参加したのは先日の 3/26 に開催されたOSS Gateワークショップの第二回です。半蔵門にあるコデアルさんのオフィスをお借りしての開催でした。
参加したきっかけは、知人の方が立ち上げから参加していて、興味を持ちそうな私を誘ってくれたことがきっかけです。最初に参加しようとしていた立ち上げミーティングには参加できなかったのですが、2 度目の正直ということで今回参加できました。
・・というところで、ちょっと色々と忙しくて、この記事を完成させる時間がなく、とりあえず Twitter でざっと発言しておいたのが以下。
OSS Gateに参加した人はそれについてブログなりに書いてインターネットに置いておくとOSS Gateの普及に役立つ(かもしれない)ので書いて欲しいな。。。 #oss_gate
— す (@ktou) 2016年4月4日
ごめんなさい、書こうと思ってたけど忘れてた... #oss_gate >RT
— seratch_ja (@seratch_ja) 2016年4月5日
#oss_gate はこのワークショップのことで https://t.co/QGpaqX7azw 作業ログを GitHub のイシューに残していく感じなので見るとどんなものかが大体わかります。私のメンターとしてのログはこれ。 https://t.co/b8EogWiIGo
— seratch_ja (@seratch_ja) 2016年4月5日
土日開催なので私は毎回は参加できないかなという感じですが、素晴らしい試みだしメンターの皆さんも親切なので GitHub とか OSS に不慣れな方は参加してみると得るもの多いのではないかと思います。メンター役も求められているので興味がある方はぜひ。
— seratch_ja (@seratch_ja) 2016年4月5日
私自身は色々と他にもやりたいこともあるので、深くコミットはできないかなと思っていますが、、できるだけ応援していきたいと思っています。これを読んで興味を持ってくれた方がいれば、ぜひ参加してみてください。
2015 年の振り返り(ガンバライジング限定)
このブログに書くか微妙なところですが、まあいいかと。
今年の 2 月くらいから 4 歳の息子とガンバライジングというアーケードゲームで遊んでいるので、それについて今年を総括してみます。 なお、本業については今年結構色々やっててまとめるの大変そうだし、特にやりませんw
ガンバライジングとの出会い
今年に入って Twitter の私の日本語アカウント(@seratch_ja)にちょくちょく仮面ライダーネタが出てくるようになりました。これでも自重してるんですが...
そもそも私と仮面ライダーについては以下のような感じ。
- 子供生まれるまで平成仮面ライダーについて全く知らなかった
- 子供とリアルタイムで見ているのは鎧武(ガイム)、ドライブ、ゴーストの 3 シリーズ
大人になってからもリアルタイムで仮面ライダーを追っている人は結構いますが、私は完全に息子の影響でした。
とはいえ、ガンバライジングについては最初は「アーケードゲームとかお金かかるし、カード集めてもねぇ」という感じだったのですが。
息子の仮面ライダーに対する異常なまでの情熱
うちの息子は 2 〜 3 歳の頃、数字などを教えてもどうにも興味を示さず、いつになったら数字をスムーズに言えるようになるの・・状態だったのですが、ふとあるとき、他のことだと数字に対するグダグダが嘘のように異常に覚えが良いことに気づきます。そう、それが仮面ライダーでした。
フリマで買ってきた昔の仮面ライダーの絵本や Hulu でたった一度見ただけのライダーも「これ何て書いてあるの?」といちいち聞いてきて、都度教えてあげるとそれをすぐに記憶しているようで、私が何度見ても覚えられないのを尻目に次からは「え、これはキバに決まってるじゃない」などと言ってきました。
そうこうしているうちに 3 歳も後半になってアンパンマンをそろそろ卒業かなというタイミングで、二人で出かけていたときに買ったマクドナルドのハッピーセットにガンバライジングカードが付いていたのです。
http://www.ganbarizing.com/event/mcdonald.htmlwww.ganbarizing.com
そういえば、このショッピングセンターに筐体があったなぁと思い、息子の仮面ライダー熱のこともあり、試しにやってみたのが始まりでした。そして、まんまと戦略に・・
ガンバライジングの遊び方
ゲームセンターに行くとポケモン、妖怪ウォッチ、ドラゴンボールなどカードバトル系のアーケードゲームで子供達がたくさん遊んでいるのを見かけることがあるかと思いますが、ガンバライジングはその仮面ライダー版です。
妖怪ウォッチやドラゴンボールほどは人気がないらしく、うちの近所だと基本的には並ばずに遊べる点が大人としてはありがたいです(アキヨドなどもっと人の多いところではそれなりに並んだりします)。
どういうものかはこの辺を見ると全部書いてあるのですが
私なりに前提知識がない人向けにざっくり書くと
- 100 円を入れると 1 回遊べる + ランダムでライダーカードが 1 枚出てくる
- ライダーカード 1 枚につき 1 人の仮面ライダーがゲーム内にロードされる
- 3 枚のカードをロードして 3 対 3 のストリートファイト的な戦いをする
- 戦いはスロットマシンで 〜100 までの攻撃力を 3 人分確定させて合計値(+エフェクト)で争う
- ゲージがたまるとライダーカードを裏返せるイベント(バースト)が発生し、そのライダーがパワーアップする
- IC カードを読み込ませて経験値やアビリティを蓄積してくとだんだん強くなっていく
- 仮面ライダーゴーストからは変身ベルトで使うゴーストアイコンというおもちゃとも連動するようになった(これでちょっとプレイする子供が増えた気がします)
という感じです。ルールは比較的シンプルなので 3 歳児にも遊びやすかったようです。ただ本来は 3 歳でやってる子はそんなにいなくて、おそらく文字がある程度読める幼稚園から小学生がターゲットかなという内容です。
ガンバライジングカードの見方
現在放映中の仮面ライダーは「仮面ライダーゴースト」ですが、ガンバライジングでは放映中のライダーだけでなく過去の仮面ライダーのカードも出てくるようになっています。
公式サイトのカード一覧を見ると、どう見ればいいのか一瞬戸惑うかもしれませんが、とりあえずは「レアリティ」を見ればよいです。
例えば「LR」は「Legend Rare(レジェンドレア)」の略で、現行バージョンではもっともレア度が高いカードです。基本的にレア度の高いカードはカードバドルの中でも強いカードとして機能します。
カードのレアリティはカードの右下に「LR」などの略称が記載されているので現物を見るとすぐにわかるようになっています。カードショップで買い物するときなどは、レアリティとそのカードがどのシリーズのものかを見るとよいでしょう。例えば、現行の「バッチリカイガン 2 弾」だと「K2」となっていて、その前の仮面ライダードライブ時代の「ナイスドライブ {1-6} 弾」シリーズでは「D6」のように表記されています。
今年のプレイを振り返る
今年、私は息子と 150 回程度ガンバライジングをプレイしました。
1 回につきカードは最大 5 枚まで買える仕組みになっていますが、新しいシリーズが始まったときはたまに多めに買ったりする程度にとどめ、基本的には 1 枚だけ買って 2 回プレイするという形でした。
また、ガンバライジングは一度のプレイ時間が長いのが特徴で、オンライン対戦*1だと 10 分を超えることも珍しくありません。
これらの理由から、たとえ大人がハマっても大金を使うことが非常に難しい点がよいと思います。 11 ヶ月で 150 回なので、課金した金額は月 2000 円満たない程度といってよいでしょう。
高いか安いかは人によって判断分かれそうですが、子供とだいぶ楽しく遊んだので高くはないかなと・・たぶん。
LR/CP/VR カード
誰得かつガチ勢の人が見たら鼻で笑われそうなカードリストですが、入手したカードを棚卸ししてみました。
カードショップでも多少は買いましたが、基本的には上記のプレイの中で入手したものです。レジェンドレアは真ん中の「仮面ライダーゴースト ムサシ魂(K1-007)」だけで、あとは CP(キャンペーン)、VR(バーサスレア)です。
バースト面はこんな感じ。
個人的には魔進チェイサーがフォトジェニックなので好きですね。
SR カード
ちょっと上記だけだと寂しかったので、その下のグレードである SR(スーパーレア)のカードから一部をピックアップしてみました。
この SR 程度のカードであればカードショップに行けば 100 円で買えたりしますので、子供とガンバライジング始めたけどいいカードがなかなか出ないなという方は一度カードショップに行ってみるとよいのではないでしょうか。
ちなみにカードショップは色々回ったわけではないですが、正直他のカードゲームほど在庫を持っているお店が多くないのが現状です。
私が行った中で一番良かったのは中野ブロードウェイの「まんだらけカード館」さんでした。ここにはかなり多くのカードがそろっています。
その他買ったもの
まずガンバライジングを始めるなら IC カードを買いましょう。いろいろ種類がありますが、私は緑色のやつを使っています。まだまだ 400 回のセーブ回数に達しないので、一枚目です。
http://www.ganbarizing.com/play/about-ic.php
ガンバライジングでは、プレイ中にカードをこすったり裏返したりするので、無防備な状態で扱っているとすぐにカードが擦り傷だらけになってしまいます。レアなカードだけでも保護フィルムに入れてあげましょう。
カードが増えてくると、すぐに取り出せるようにカードバインダーに整理して入れてあげる必要も出てきます。私はもっと安いやつにしていますが、純正のやつはカッコいいですね。
http://www.amazon.co.jp/gp/aw/d/B012CT49N0/
ちなみにカードバインダーの中のページは買い足せばどんどん増やしていけるのですが、4 穴式と 6 穴式という互換性のない二つの製品があるので買い間違えないように注意しましょう。私は最初に買ったとき、そんなトラップがあるとは思わず、まんまと間違えました。
よく間違える人がいるらしく、親切な店員さんであれば 4 穴だけど合ってる?と聞いてくれたりします。
まとめ
ということで、ガンバライジング限定で私の 2015 年を振り返ってみました。
もし興味がある人がいましたら、カンファレンスやミートアップイベントなどでお会いしたときにでもガンバライジング談義を持ちかけてくださいw
まあ来年も今年ほどやるかというと他のことにもっと時間使わないといけないなと思ってますが、、息子が飽きないうちはある程度は付き合ってあげようかなと*2。
Skinny Meetup Tokyo 2 を開催しました #skinnyjp
Skinny Framework 2.0 をリリースして一ヶ月弱、@yusuke さんのご厚意により、東池袋のサムライズムのオフィスをお借りして Skinny Meetup Tokyo の第 2 回を開催させていただきました。
当日使ったスライドはこちら。
www.slideshare.net
英語版はこちら。
www.slideshare.net
関連する tweet を Togetter にまとめました。
当日の内容
Meetup の流れは、まず、私の方から Skinny が 2.0 で変わったこと(とちょっとだけさだまさしコンパイラのデモ)をお話しました。次に、社内の情報共有ツールの実装に Skinny Framework を使ってくださっているという @roundrop さんから、その社内情報共有ツールについての紹介、初めての Scala 開発で Skinny Framework を使ってどうだったかという所感についてお話いただきました。具体的には以下のインタビューで触れられている Siita というツールです。
数年 Scala をやっている身からするとすっかり忘れかけていたような、初めて Scala をやったときに困った点などもバランスよくまとめていただいていたので、おそらくこれから Scala、Skinny をやってみようという方々に大変参考になったのではないかと思います。この発表のスライドは後日公開いただけるとのことでしたので、楽しみにしております。 (追記)公開していただいたので、以下に埋め込みました。
www.slideshare.net
サムライズム最高
当初は一人千円いただいてピザとドリンクを準備予定でしたが、サムライズム @yusuke さんのご厚意に甘えさせていただきまして、なんと無料で懇親会を行うことができました。再度、お礼申し上げる次第です。
tweet とこのブログで紹介させていただく程度のことしかできませんが、以下のようにサムライズムでは OSS コミュニティも応援されているそうです。また IntelliJ IDEA や JRebel の業務導入を検討されている企業様におかれましては、ぜひ日本の商慣行にも柔軟に対応しているサムライズム社でご購入いただければと思います!
今日は何から何までサムライズム .@yusuke さんのお世話になりっぱなしでした。ありがとうございました!IntelliJ IDEA と JRebel の購入はサムライズムさんで! #skinnyjp #さだまさしjp
— seratch_ja (@seratch_ja) 2015, 12月 22
サムライズムさんでは JVM を中心とした OSS コミュニティのイベント開催など応援されているそうですよ! #skinnyjp #さだまさしjp
— seratch_ja (@seratch_ja) 2015, 12月 22
そんな大きな箱ではありませんが、私が関われるようなコミュニティであれば弊社の場所お貸しできます!有償イベントはできません
— 山本裕介 Yusuke Yamamoto (@yusuke) 2015, 12月 22
Skinny Micro
今回、初めて Skinny Micro についてちゃんと説明させていただきましたが、Scalatra を fork してリファクタリング、機能追加したものでかなり手軽で使いやすいライブラリになったのではないかと思っています。
どれくらい手軽かというと、以下に示すこれだけのコードで Web アプリをつくれるくらい手軽です。この Web アプリは Jetty サーバで起動して curl -v 'localhost:4567/say-hello?name=Martin'
というリクエストに対して Hello, Martin!
というレスポンスボディを返します。
https://github.com/skinny-framework/skinny-micro/tree/master/scalas-samples
#!/usr/bin/env scalas // or ./scalas Hello.scala /*** scalaVersion := "2.11.7" libraryDependencies += "org.skinny-framework" %% "skinny-micro-server" % "1.0.0" */ import skinny.micro._ object HelloApp extends WebApp { get("/say-hello") { s"Hello, ${params.getOrElse("name", "Anonymous")}!\n" } } WebServer.mount(HelloApp).port(4567).start()
Heroku にすぐにデプロイできるサンプルや基本的な使い方を列挙したサンプルもありますので、ご参照いただければと思います。
https://github.com/skinny-framework/skinny-micro-usage-example
https://github.com/skinny-framework/skinny-micro-heroku-example
まだ公開して日が浅く GitHub スター数がちょっと少なめなので、気に入った方はぜひ stargazer になっていただけると嬉しいです。
Skinny の今後
Skinny 2 は大きな非互換を入れたわけでなく Scalatra からの移行という大きなイベントがあったのでメジャーバージョンを上げたという事情がありました。既に 1.3 でフレームワーク本体で備えるべき機能はそれなりに揃ってきているという認識で、大きくコードベースを変えていくことは考えていません。しかし、当然ながらソフトウェアに完成ということはありませんし、また利用者からするとメンテナンスされ続けていることは非常に重要なことですので、これからも手を入れ続け、マイナーバージョンでのリリースは続けていきます。
Skinny Framework の売りとしては、以下のような内容に共感いただける現場・用途には非常にマッチすると考えています。改めて明文化してみましたが、これは公式サイトにも明記しておくべきですね。
- 今後も Scala 界における Servlet での Web アプリケーション開発フレームワークのデファクトを目指し続けます
- 今後も(既にコモディティ化している)Rails のイディオムや基本的なコンセプトに近い立場を保つことで、その下地がある開発者にとって敷居の低いフレームワークであり続けます
- 既に 1.3 で当初予定していた最低限の機能セットは実現できたと考えており、安定フェーズとしてプロジェクトを運営していきます
- 外的要因(Scala 本体など)の大きな変化以外での非互換な変更は基本的に入れない方針で運営していきます
- Servlet に依存していないサブモジュール(ORM や Validator、HTTP クライアントなど)は今後も独立したライブラリとして利用可能であることを保証し続けます
- 日本語圏に閉じず、世界中のさらなるユーザ拡大に努めてガラパゴス化のリスクを避ける取り組みを今後も続けます
特に、運用に入ってからのメンテコストを抑えたい、新しいメンバのキャッチアップのしやすさを重視したいという目的の場合、期待に答えられるフレームワークだと思っておりますので、選択肢の一つとしてご検討いただければと思います。
まだ時期やプランの詳細は未定なのですが、来年は Skinny Framework を業務の現場で利用いただきやすくなるようなサポートにもチャレンジできないかと検討しているところですので、引き続きよろしくお願いいたします。
ScalikeJDBC / Skinny Framework グッズ 2015 春モデル
SUZURI 最高
去年から GMO ペパボさんの suzuri.jp を利用して Skinny Framework のロゴをプリントしたマグカップを販売しています。
これまでに 13 個お買い上げいただいております。ありがとうございます!*1
Skinny Framework 2015 Spring
さて、春ですし 2015 春モデルと称して新しく T シャツもつくってみました。
お子さんが生まれた方はぜひロンパースを。
暖かくなってきたので時期的にちょっと微妙ですが、スウェットもつくってみました。
ScalikeJDBC 2015 Spring
待望の? ScalikeJDBC グッズもつくりました。ScalikeJDBC を使って開発されている方もどうぞ。
みんなで買えば送料がお得
販売しているグッズ一覧はこちらで見ることができます。
複数口で買うと送料がお得になります。お買い上げいただける際はぜひ開発チーム、部署、会社単位でまとめてご注文ください!
*1:トリブンは 0 円設定なので、私の懐には 1 円も入れておりませんが