PowerPointの資料生成をRubyで書きたい
資料作成自動化業務ってありますよね。 Googleスライドとか使えばいいのに、ビジネスサイドの理由とかでPowerPointじゃなきゃ嫌だって言われたり、 Railsアプリとして操作したいのにいい感じにPowerPointファイルを扱うgemがなかったりします。
今回はApache POIを使ったをさっと書く。
Apache POI
Apache POI(アパッチ・ポイまたはピーオーアイ)はApacheソフトウェア財団のプロジェクトで、WordやExcelといったMicrosoft Office形式のファイルを読み書きできる100% Javaライブラリとして提供されている。
そういうことなので、これを使えば詰むことはなさそう。
Apache POI - the Java API for Microsoft Documents サンプルコードとかたくさんある。
あとはJNIでrubyとJavaをブリッジしてしまえばよい。
JNI
Java Native Interface JVM上で動くコードでネイティブコードで連携するためのインタフェース仕様
このブリッジを実装したgemがあるので使う。
require 'rjb' out = Rjb::import('java.lang.System').out out.println('jarh')
こんな感じでかける。 これでRailsアプリのモデルや何かの資産を活用しつつ、Officeファイルを操作できる。
注意すべき点
RJBはメインスレッド以外のスレッドでの動作はサポートしていない。 うっかりSidekiqのworkerとかで処理しようとするとうまく動かなかったりする。 ジョブキューを使う場合はシングルスレッドで動かすとか、マルチプロセスモデルのものを使うとかしよう。
Firebase Functionsをゲームサーバーにしてみよう。
Firebase DatabaseとUnityでオンラインゲームを作るとして、 サーバーというか、GlobalManagerロールをどうしようってなったので Firebase Functionsでやってみようと思います。
今回やることは
1分ごとに、 - マップ上にランダムに木を生成する。 - ゲーム内時間を進める。
データ構造
{ "world": { "time": 1, "resources": { "wood": 0 }, "objects": [ { "type": "wood", "position": { "x":0, "y":0, "x":0, } }, { "type": "wood", "position": { "x":2, "y":0, "x":0, } } ] } }
こんな感じかな。 Functionsでobjectsにwoodを入れていく。
プレイヤーは木を切るとresources.woodを+1する。
やっていく
https://console.firebase.google.com まず適当にFirebaseプロジェクトを作る
npm install -g firebase-tools
firebaseのCLIをインストールして、 適当な空のディレクトリに移動してから
$ firebase init functions
生成されたindex.js
の中は
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); exports.tick = functions.https.onRequest((request, response) => { admin.database().ref('/world/time').once("value") .then(snapshot => { admin.database().ref('/world/time').set(1 + snapshot.val()); }) admin.database().ref("/world/objects").push({ type: 'tree', position: { x: Math.floor(Math.random() * 51), y: 1, z: Math.floor(Math.random() * 51) } }); response.status(200).end(); });
こんな感じ。
コードの記述が終わったらデプロイをする。
$ firebase deploy --only functions
これでHTTPSのエンドポイントURLが発行される。
定期的に実行する
cron-job.org - Free cronjobs - from minutely to once a year.
これとか使ってみます。 定期的にHTTPリクエストを送ってくれるサービス。 1分毎に叩くように設定してみた。
これで 「1分毎に新たに木が生え、ゲーム内時間が進む」 が実装されました。
時系列データを集めてシェアするサービスを作っている。
Statis αですが公開はしています。
こういう感じのサービスを作っています。
どういうサービス?
現段階で出来るのは以下のようなことです。
- APIを用いて時系列データをポストする
- 対象データのURL・HTMLのパスを指定し、定期的にスクレイピングをさせ、データを収集する
- シリーズ(時系列データの単位)に対するイベントトリガー(しきい値を超えたらメッセージ)
- シリーズをまとめて観る事ができる"ボード"
- ボードへコメントをつける
例えばツイッターの自分のページのフォロワー数を1時間に1度スクレイピングしていくような事ができます。 こんな感じに指定すると勝手にデータが入っていく。
あとは しきい値を超えると発火するようなイベントトリガー。 モニタリングツールとかによくあるやつ。
なんで作ってるの
ビジネスシーンでは、GoogleDataStudio,Redash,Supsersetなどなど、プロジェクトの状況を視覚化する為のBIツールと呼ばれるアプリケーションが増えてきてます。 システム面では、DatadogやStackdriver等のモニタリングツールというものもあり、これも最近増えてきているような気がします。
ただこういったサービスって基本的にプライベートで使う想定ではなく、閉じられた範囲でしか情報を共有しないものです。
一般公開されているデータや個人のデータを1箇所に集約し、横断的に観ることが出来たら、 面白い発見や新しい体験が得られるのではないかと思って作っています。
今後の方針
とりあえずはいろいろなデータを入れて行くことに専念して、 ある程度パターンが増えたらデータの表現方法を考えて、 インフォグラフィックっぽいものが生成される機能とかを作りたいです。
Angular+Firebaseでまずはフロントエンドだけ作っていくとすごく楽しい
タイトルのとおりです。 Angular+Firebaseで一旦それっぽくしちゃうと、モチベーションが下がらないのでおすすめです。
導入
$ npm install -g @angular/cli $ ng new my-app $ cd my-app $ ng serve
** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200 ** Hash: c27678c6699304bb4a70 Time: 7560ms chunk {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.bundle.js, main.bundle.js.map (main) 3.63 kB {3} [initial] [rendered] chunk {2} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {4} [initial] [rendered] chunk {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.4 MB [initial] [rendered] chunk {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered] webpack: Compiled successfully.
これでhttp://localhost:4200/
にアプリケーションがたちました。
それっぽいUIフレームワークをいれる
僕のおすすめは
この2つです。 clarityはcardのダサさにウッっとなるのですが、それ以外は無難であることと、デザインガイドラインが用意されているので良いです。 今回はclarityを入れてすすめます。
$ npm install clarity-icons @angular/animations @webcomponents/custom-elements@1.0.0-rc.3 mutationobserver-shim@0.3.2 clarity-ui clarity-angular --save
//.angular-cli.json "styles": [ "../node_modules/clarity-icons/clarity-icons.min.css", "../node_modules/clarity-ui/clarity-ui.min.css", ... ], "scripts": [ "../node_modules/mutationobserver-shim/dist/mutationobserver.min.js", "../node_modules/@webcomponents/custom-elements/custom-elements.min.js", "../node_modules/clarity-icons/clarity-icons.min.js", ... ], ...
//src/app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ClarityModule } from 'clarity-angular'; //追加 import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, ClarityModule.forRoot(), //追加 .... ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
これでng serve
すれば動きます。てきとうに
<!-- src/app/app/component.html --> <div class="main-container"> <header class="header header-6"> <div class="branding"> <a href="..." class="nav-link"> <clr-icon shape="vm-bug"></clr-icon> <span class="title">僕のWebアプリ</span> </a> </div> </header> <div class="content-container"> <div class="content-area"> ... </div> <nav class="sidenav"> <section class="sidenav-content"> <a href="..." class="nav-link active"> Nav Element 1 </a> <a href="..." class="nav-link"> Nav Element 2 </a> <section class="nav-group collapsible"> <input id="tabexample1" type="checkbox"> <label for="tabexample1">Collapsible Nav Element</label> <ul class="nav-list"> <li><a class="nav-link">Link 1</a></li> <li><a class="nav-link">Link 2</a></li> </ul> </section> <section class="nav-group"> <input id="tabexample2" type="checkbox"> <label for="tabexample2">Default Nav Element</label> <ul class="nav-list"> <li><a class="nav-link">Link 1</a></li> <li><a class="nav-link">Link 2</a></li> <li><a class="nav-link active">Link 3</a></li> <li><a class="nav-link">Link 4</a></li> <li><a class="nav-link">Link 5</a></li> <li><a class="nav-link">Link 6</a></li> </ul> </section> </section> </nav> </div> </div>
とか書いておきましょう。それっぽくなると思います。
ビルドとデプロイ
ビルドは
$ ng build --prod
これだけです。 何処かにデプロイしたいとおもったら、Firebaseが一番早いと思います
$ npm install -g firebase-tools $ firebase login $ firebase init
デプロイ先プロジェクトは、既にあればそこを指定し、特になければ[create a new project]
で新たに作ってください。
✔ Firebase initialization complete!
となれば成功。
//firebase.json { "hosting": { "public": "dist" } }
と書き加えてから
$ firebase deploy
とコマンドを叩くとデプロイされます。
=== Deploying to 'development-ef09c'... i deploying hosting i hosting: preparing dist directory for upload... ✔ hosting: 8 files uploaded successfully i starting release process (may take several minutes)... ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/development-ef09c/overview Hosting URL: https://development-ef09c.firebaseapp.com
ログインとかユーザ管理のしくみもFirebaseに乗っかるとすごい楽なんですが、 それはまた別の話。
RailsでGraphQLを使う
結構使いやすくなっていたので紹介
セットアップ
# Gemfile gem 'graphql'
rails generate graphql:install
まず導入 基本的な構成と、graphiql(GraphQLのクライアントをRailsにマウントするやつ)が入る
http://localhost:3000/graphiql
サンプルフィールドが用意されているのでクエリを叩くと、結果が得られる。
こんなかんじ。
ActiveRecordとつなげる
Railsで使うからにはGraphQLのObjectとRailsのモデルを繋げなければいけません。 つなぐために
GitHub - goco-inc/graphql-activerecord
このGemを使います。
rails g model Book name:string
こんなモデルを予め作っておいてください。
# Gemfile gem 'graphql-activerecord'
bundle install
レコード作成
まずはBookモデルに対応したBookTypeを作ります。
# app/graphql/types/book_type.rb Types::BookType = GraphQL::ObjectType.define do name "Book" backed_by_model :books do attr :name end end
次にBookを新たに作成するためのMutationを定義します。
# app/graphql/mutations/create_book.rb Mutations::CreateBook = GraphQL::Relay::Mutation.define do name "CreateBook" return_field :book, Types::BookType input_field :name, !types.String mutator_definition = GraphQL::Models.define_mutator(self, Book, null_behavior: :leave_unchanged) do attr :name end resolve ->(obj, args, ctx) { { book: Book.create(name:args[:name]) } } end
あとは、このMutationを
App名Schemaに認識させます。
# app/graphql/types/mutation_type.rb Types::MutationType = GraphQL::ObjectType.define do name 'Mutation' field :CreateBook, field: Mutations::CreateBook.field end
# app/graphql/アプリ名_schema.rb アプリ名Schema = GraphQL::Schema.define do query(Types::QueryType) mutation(Types::MutationType) # 追加 lazy_resolve(Promise, :sync) instrument(:query, GraphQL::Batch::Setup) instrument(:field, GraphQL::Models::Instrumentation.new) end
これで叩ける用にはなりました。
問題なければDBにレコードが作成されているはずです。
queryを使う。
先程BookTypeは作ってしまったので、QueryTypeを記述するだけです。
# app/graphql/types/query_type.rb Types::QueryType = GraphQL::ObjectType.define do name "Query" field :book, Types::BookType do description "An example field added by the generator" argument :id, !types.ID resolve ->(obj, args, ctx) { Book.find(args[:id]) } end end
関連レコードも引っ張ってくる
これだけだとつまらないので関連データもとってこれるようにしたいと思います。 デモ用モデルとして「登場人物」を新たに作ります。
rails g model Character name:string book:references
次に CharacterTypeを定義します。 ちなみに、
rails g graphql:object Character
で作ることも出来る。
# app/graphql/types/book_type.rb Types::BookType = GraphQL::ObjectType.define do name "Book" backed_by_model :books do attr :name has_many_array :characters # 追加 end end
今日の雑記はgraphql-activerecord - あのにのに
でも書いたけど Typeの定義の仕方によっては一手間必要・
できたできた。
今日の雑記はgraphql-activerecord
このgem使ってるんだけど、
RuntimeError (Could not locate GraphQL type for model Character)
“GraphQL::Models::CharacterTypeはなしんこなしなしだぞ”
って言われる。
graphql-activerecord/activerecord.rb at master · goco-inc/graphql-activerecord · GitHub
ここらへん。
Modelに対応するTypeをTypes::CharacterType
みたいに定義しちゃってるせい。
# config/initializers/graphql.rb GraphQL::Models.model_to_graphql_type = -> (model_class) { "Types::#{model_class.name}Type".safe_constantize }
で動いた。
Bash on Ubuntu on Windowsからexeファイルをコマンドとして叩いていくために
こっちの記事でdocker.exeをdockerコマンドとして使えるようにしました。 ただコマンドごとに.bashrcに記述していくのも面倒なので、指定ディレクトリ内のwindowsバイナリをまとめて処理するようにしようと思います。
/mnt/c/Users/anoChick/.winbin
ここに*.exeファイルを入れていきます。
ディレクトリをどこに配置するかは自由なのですが、
/usr/local/winbin
とかだとWindows側が読めないです。
export PATH=$PATH:/mnt/c/Users/anoChick/.winbin WIN_BIN_DIR='/mnt/c/Users/anoChick/.winbin' cd $WIN_BIN_DIR for file in `ls *.exe` do alias ${file%.*}=$file done cd
あとはsource .bashrc
するなりターミナルを再起動するなりすれば動くと思います。