ホーム ブログ ページ 31

姿勢の悪さからも目疲れはくる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、楽しいことはあっという間に終わってしまうなすちゃです。

はじめに

仕事や趣味などディスプレイを眺める環境が日常化している現代ですが、
長時間パソコン作業やスマホ操作をしているとどうしても目への負担が出てきます。

光による刺激や目の乾燥などありますが、使用中の環境も関係しています。
ブルーライトをカットする眼鏡や保湿の高い目薬など様々なアイテムがありますが
今回は自身で改善できることを調べてみました。

目への負担

なぜ姿勢が悪いと目へ影響がでるのか。
長時間不安定な姿勢でいると両目の視点のバランスが崩れ、片目だけに負担がかかり視力低下にも繋がるそうです。
目疲れが続くと頭痛・肩こりといった眼精疲労の原因にもなります。
眼精疲労が悪化すると頭痛・吐き気など身体にも悪影響を及ぼしてしまいます。

改善

座る際には椅子に深く腰掛け姿勢を正すことを意識します。
前かがみになり画面へ近すぎると目だけでなく首への負担もかかるため目線の移動範囲が広くならない距離に保ち、ディスプレイの高さは目線が真っ直ぐかまたはやや低い位置がいいそうです。

頻繁な視線移動で目の周りの筋肉へ負担がかからないようにしましょう。

最後に

忙しいと目疲れ程度でと思いがちですが、軽いストレッチで体の筋肉を解したり遠くを眺めたりと目を休ませてあげるのが大切だと思います。

コマンドラインから英和/和英辞書を引く

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


こんにちは。いくたです。

コードを書く上で気をつけるべき最も基礎的なこと…
それは「スペルミス」です。

一文字間違えると今まで動いていたスクリプトがうんともすんとも言わなくなる。
ミスに気付かないと永遠に時間が取られる。
そして何よりスペルミスは、めっちゃしょうもない

スペルミスしちゃうのは英単語のせい…

クラス名やメソッド名に使われている英単語の意味がわからないと、コードはただの英数字の羅列になってしまいます。
コードを書きながら英単語の意味をすぐに調べられたらいいですよね。

そこで、コマンドラインから英和・和英辞書を引けるようにしました。

概要

  1. rubyスクリプトでオンライン英語辞書の weblio http://ejje.weblio.jp から検索結果を取ってきます
  2. 実行するときに検索キーワードを引数に渡すことでコマンドラインから検索できるようにします
  3. bashのエイリアスを設定して、libコマンドを作ります

準備するもの

  • Ruby(ver 2.1.0)
  • gem: open-uri → URL先のデータを普通のファイルと同様に扱えます
  • gem: Nokogiri →  HTMLやXMLの構造を解析して特定の要素を抽出できます

open-uri や Nokogiri の使い方については私が前々回書いた記事でWebスクレイピングについて書いたので割愛します。
【前々回の記事】アニメ名探偵コナンの新一が出てる回だけを見たい

weblioの検索結果を取ってくる

weblioはhttp://ejje.weblio.jp/content/のURLの後ろに検索ワードをくっつけることで検索結果のURLとなります。

(例) rubyの検索結果
  → http://ejje.weblio.jp/content/ruby

このURLのページをnokogiriで分解して「主な意味」の項目をXpathで指定して取り出して表示します。

エラーに対処する

エラーが出ないようにコードを書くのは大切ですが、実行時に実行時に引数を入力しない・検索結果が存在しない等エラーの発生が予想されます。
そうなった時にNoMethodError等を出さないようにあらかじめ逃げ道を作っておきます。
エラーとなる原因をメッセージで表示することで、正しい使い方ができる手助けになります。

例えば、スペルミスをして検索結果が無かった場合、「一致する見出し語は見つかりませんでした。」と表示しておけばスペルミスに気づくことができます。
スペルが不安な単語を検索にかけることで、ミスに気づくことができますね。

実装スクリプト

引数をキーワードにweblioの検索結果を表示する

# weblio.rb
require 'open-uri'
require 'nokogiri'

LIB_URL='http://ejje.weblio.jp/content/'.freeze
XPATH="//*[@id=\"summary\"]/div[2]/table/tbody/tr/td[2]".freeze

# 引数を入れてなかったらメッセージを表示する
if AVGV.empty?
  puts '検索ワードを入れてください。' 
  return
end

# 引数ARGVの検索ワードからURLにする
# `shell script` など複数の単語を組み合わせることができるように'+'で結合しておく
# => 'http://ejje.weblio.jp/content/shell+script'
url = URI.encode(LIB_URL + ARGV.join('+'))

# URLから検索結果ページを取ってくる
html = open(url).read
doc = Nokogiri::HTML.parse(html)
# 検索結果の「主な意味」の項目を抜き出す
result = doc.xpath(XPATH).text

# resultが無かった場合メッセージを表示する
if result.empty?
  puts '一致する見出し語は見つかりませんでした。'
else
  # 結果を表示する
  puts result
end

bashのエイリアス設定

# ~/.bash_profile
alias lib='ruby ~/scripts/library_command/weblio.rb'

使ってみよう!

$ lib ルビー
ruby

$ lib apend
一致する見出し語は見つかりませんでした。

$ lib append
(…に)添える、付加する

$ lib
検索ワードを入れてください。

$ lib お腹すいた
I'm hungry.、hungry

これでスペルミスが少なくなりそうです!
写し間違えなどを気をつけるのが第一ですけどね。

それでは、今日はこの辺で。
読んでいただき、ありがとうございました。

アニメーションキーのスマートな打ち方

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

「とりあえずSキー」に心当たりがある方は今までよりもアニメーション作業が捗るかもしれません。 ※筆者作業環境:Maya2016

はじめに

独学で3DCGを学ぼうとすると、
実際は作業上あまり使われることのない知識が身についてしまうことが多々あります。

今回は独学で身につくであろうキーの打ち方に関するお話です。

『Sでキーを打つ』というやり方

Webでチュートリアルなどを探して何もわからない状態からアニメーションを学ぶときに高い確率で身につくであろう、この「Sでキーを打つ」というアクションは、
アニメーションさせたいオブジェクトをビュー上で動かしてSを押せばアニメーションキーを打ってくれるので、確かに便利ですし非常にシンプルで初心者にもわかりやすいです。
私も以前は、キャラクターを細かく動かしてはSキーを連打していました。

しかし、仕事上でアニメーション作業を教わってからは使うことがなくなりました

なぜ使わなくなったのでしょうか。
そして実際はどのようにキーを打つとスマートなのでしょうか。

使わない理由

無駄な項目にまでたくさんキーを打ってしまっている
これにつきます。

実際にバウンドする球体のアニメーションキーをSを用いて打ってみます。
すると画面右側、チャネルボックスの移動Xから可視性という項目までの数値が赤くなりました。赤くなった部分にキーが打たれたということになります。
Sキーでキーを打つと...

アニメーションの内容としては球体がZ軸方向にバウンドしていくというものなので、
移動Yと移動Zにだけキーが打たれていれば良いのですが、グラフエディタを開いてみるとこのようにたくさんの無駄なキーが打たれてしまっていることがわかります。
実際のアニメーションカーブ

例に挙げたモーションはZ軸移動とY軸移動に限定したものでしたが、
様々な方向、角度に動かすモーションを作っていく中で、いちいちこのやり方でキーを打ってしまうとグラフエディタを開いたときスパゲッティのように複雑なアニメーションカーブを目の当たりにすること間違いなしです。

先ほどのアニメーションの無駄をなくすとここまですっきりします。
これならタイミング調整などもしやすいですよね。
理想のアニメーションカーブ、キーもすっきりしていて編集しやすい

また、アニメーションカーブ自体も先ほどの画像のものと比べ曲線がきれいなことがわかるかと思います。
illustratorやphotoshop等の使用経験がある方ならイメージしやすいと思いますが、アニメーションカーブはベジェ曲線と原理は一緒なので頂点数が少ないほど、ソフト側できれいに補間してくれます。
アニメーション作業ではガタガタのカーブがそのまま見た目に現れてくるので綺麗なアニメーションカーブを作りたいところです。

ゲームなどに使われるモーションはできるだけ少ないキーでアニメーションを作り、一つ一つのデータサイズを小さくしていく必要があるためSキーによるキーの打ち方はゲームには不向きだと言えます。(特にコンシューマーゲームの場合はゲームソフトに入れられるデータ容量が決まっているので少ないキーフレームでモーションを作るスキルが必要と聞きます。)
単にアニメーション作品を作っているのであれば、最終的にはレンダリングで連番画像に書き出すだけなのでそこまでキーの削減に注力する必要はないのかもしれませんが、シーンサイズはできるだけ軽い方がストレスなく作業ができますよね。

ということで、出来るだけ動かしたい項目(移動軸、回転軸)だけにキーを打っていくようにしましょう。

キーの打ち方

私が普段使っている3通りのキーの打ち方を紹介します。

1.チャネルボックス上でのキーの打ち方

チャネルボックス上で特定の項目へキーを打つ場合は「選択項目のキー設定」からキーを打ちます。
方法はチャネルボックス上のキーを打ちたい項目の行にマウスカーソルを合わせ
右クリック→選択項目のキー設定
でキーを打つことが出来ます。
チャネルボックスからキーを打つ

2.グラフエディタ上での基本的なキーの打ち方
グラフエディタ上ではキーを挿入するといった方がわかりやすいです。あらかじめチャネルボックスなどで打ったキー(アニメーションカーブ)を選択して

Iキー+中マウスボタン

で任意のフレームにキーを挿入できます。
グラフエディタ上でキーを挿入する

3.自動キーフレームを用いたキーの打ち方
自動キーフレーム機能はオブジェクトをマニピュレーターで動かしたときに動かした軸にのみ自動でキーを打ってくれるもので、個人的にとても便利だと感じている機能です。
画面右下にあるこのボタンが自動キーフレームのON/OFFボタンです。このボタンが青く表示されていると、自動キーフレームがONの状態です。
自動キーフレーム機能を使ってキーを打つ

グラフエディタと同様、あらかじめキーが打たれていないとどんなに動かしてもキーを打つことができないので一つ目のキーはチャネルボックスで打ちます。
自動キーフレーム機能を使ってキーを打つ2

3通りのキーの打ち方について紹介してみました。
特定の項目に対してのみ任意のフレームにキーを打つという目的は一緒ですが、
場面によって使いやすさが変わってくるのでうまく使い分けていくと作業の単純化、効率化にもつながるかと思います。

終わりに

記事を書きながら、
「やる気があれば独学で3Dコンテンツくらい作れるだろう。」と高を括り、インターネットの力だけを頼って学んでいた頃を思い出して、よくSキーのみでアニメーション作れていたなと思いました。就活でそのアニメーションを見せて回っていたことに若干恥ずかしさを感じています。

最近はスカートや振袖、髪の毛等の「揺れ物」にアニメーションを付ける作業を始めました。体の動きとは違い、しなやかさや遅延表現など様々な要素を考慮して付ける一段難しい作業ですが、キャラクターのモーションがより華やかになる大切な作業なので非常に達成感が強いです。

考え事の場をつくる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

一日中ゲームをしていたら、いつの間にかお盆休みが終わっていました。
休みの間に旅行に行った方、帰省した方、仕事だったという方、私のようにゲーム三昧だった方……
色々な過ごし方をされてきた方がいらっしゃることでしょう。
そんな中でふとした瞬間、考え事をすることはありませんでしたか?
今回はその考え事について、少し書いていこうと思います。

発想の場といえば……

enter image description here

皆さんは日々何気なく考え事をしながら生活していると思いますが、自分にとってどこが最も考え事に適した場所なのか……そんなことを考えたことはありますか?
私の周囲では、「煮詰まったからファミレスに行って作業をする!」という話をよく耳にします。
お洒落で静かなカフェ、薄いノートPCを持ったイケてるサラリーマンたちと女子高生でごったがえすカフェ、近所のファミリーレストラン……
こんなところでキーボードをカタカタさせていたら、格好良いに違いありません。
少し憧れていましたが、残念なことに私はカフェなどで頭脳が活性化するタイプではありませんでした。

自分に合っている場所を見つける

enter image description here

私が集中して考え事をできる場所……それは、「お風呂」でした。
お風呂やトイレなどの、一人でいられる場所が最も集中できるという人はかなり多いと思います。
湯船にゆったりと浸かっている時も良いですが、私の場合は頭からシャワーを浴びている時が絶好調になります。
すぐにでもメモを取って忘れないようにしたいところですが、現実は非情です。
シャンプーを洗い流しているうちに半分くらい忘れています。
お風呂でアイデアが浮かんでも、メモを取れない!とお悩みの方は少なくないと思います。私もその一人です。
なんとかバスタイムを終えるまで、頭の中に残っているよう祈るしかないですね。

考え事をしやすい場所を見つける・気付くことができれば、より良いアイデアが生まれてくるかもしれません。

終わりに

最近では耐水ノートというとても便利なものが登場しています。
これがあれば、お風呂の中でも好きなだけメモを取れますね。
耐水といっても、お風呂に沈めると流石にまずいので、気を付けましょう。

ホワイトボードで代用するのも良いかもしれないです。

select2 で “No Results Found” などのメッセージを変更する

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

jquery を拡張し selectボックス(プルダウンメニュー、ドロップダウンリスト、リストボックス)に対してデザインを整えたりタグやプレースホルダテキストや検索の機能を加える select2 があります。検索が成功していないときに表示されるメッセージを変更したくて時間を費やしたので記載します。

参考

本体
https://select2.github.io/

使用例
https://select2.github.io/examples.html

基本的な使い方

jquery, select2 プラグインと関連のcssを読み込んでおく。
検索機能やスタイルを付与したいセレクトボックスをjqueryのセレクター ‘$()’ で指定して select メソッドを呼ぶだけです。select に引数(連想配列的な何か)を渡すことでカスタマイズできます。検索の時にちらっと表示されるメッセージもそこで上書きできます。

例 (coffeescript)

$(document).ready(->
  $('#select_box').select2()

こんなことをしました。

$(document).ready(->
  $('.select2').each(->
    $select = $(this)
    options = {}
    options.language = {}
    options.language.noResults = ->
      '該当するものがありません。' # 0件ヒットのときのメッセージ : No Results Found
    options.language.searching = ->
      '検索中' # 検索中のメッセージ :  Searching...
    options.language.errorLoading = ->
      '検索中' # 検索が失敗しているときのメッセージ: Error loading results
    $select.select2(options)

※ 文字列を返すのだけれど文字列を入れるのではなく文字列を返す関数を入れるところに気をつけましょう。

1台のWindowsで複数Firefoxを起動させる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

1台のWindowsで、複数Firefoxを起動させる(いわゆる多重起動)方法。

できると何が嬉しいか

  • cookieや閲覧履歴、保存パスワードなどが独立する
  • 複数のアカウントを分けてログインするというような事を Firefoxだけでできるようになる
  • それぞれ個別に設定する事ができる(設定違いのFirefox)
  • アップデートしたらアドオンや設定がおかしくなるかどうかを事前確認できるようになる

前提

Firefox自体はインストール済であるとする。(これを「メイン」とする。)

手順

  1. Firefox Portable をダウンロードし、任意の場所に展開する(これを「サブ」とする)
  2. 展開されたディレクトリ下の Other\Source\FirefoxPortable.ini を展開されたディレクトリ直下にコピーする
  3. コピーした FirefoxPortable.ini を以下の通り編集する
  • 変更前
AllowMultipleInstances=false
  • 変更後
AllowMultipleInstances=true

これでメインとサブを同時に干渉せず起動する事ができるようになる。

補足

FirefoxPortable は何個あっても問題ないので、この方法を使えば、サブ側はいくつでも起動可能となる。

オプション

任意で以下のような事もできる

FirefoxPortable の起動時にスプラッシュ画面を表示しない

FirefoxPortable.ini を以下の通り編集する

  • 変更前
DisableSplashScreen=false
  • 変更後
DisableSplashScreen=true

サブ側のアイコンを変える

拡張子が .ico である任意の画像ファイルを用意し、 App\Firefox\browser\chrome\icons\default\main-window.ico として上書き保存する

EXCEL(VBA)から、HTTP通信でファイルをアップロードしてみよう。

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

EXCEL(VBA)で、JSONデータをHTTP通信する方法に関しては、前回の記事を参考して下さい。
EXCELでファイルリストを管理し、PCに格納しているファイルもアップロード出来れば、作業効率が図れると思ったのがキッカケです。
それを実現する簡単な方法をご紹介します。

※実際は、そんなに簡単ではありませんでした(汗)

参考にさせて頂いたサイト
・WSH + VBScriptでのサンプルを参照して下さい。
・boundaryに関してのこちらの仕様を参照して下さい。

ファイルを送信するには

1.まずHTMLを使ったファイルを送信する処理を確認してみる

ファイルを送信するHTMLは、以下のようになります。
enctype="multipart/form-data" を付ける事でファイルを送信することが可能となるのです。

<form action="http://localhost:8080/" method="POST" enctype="multipart/form-data">
  <input type="text" name="id" /><br />
  <input type="text" name="description" /><br />
  <input type="file" name="file" /><br />
  <input type="submit" name="submit" value="send"/>
</form>

HTMLイメージ

HTMLイメージ

選択しているテキストファイルの内容

選択しているテキストファイルの内容
※使用しているサクラエディタは、こちら

2.HTTP通信の内容はどうなるの?

HTMLのフォームを使ったファイルアップロードの仕様はRFC1867を参照して下さい。
実際に送信し、proxyツールfiddler を使った、HTTP通信内容を確認した様子が以下の図となります。
※HTTPの構文に関しては、こちらのページが参考になります。

リクエスト内容

HTTPヘッダ部分の content-Type が、 multipart/form-data となっていますね。
ここでポイントとなるのは、 boundary= に設定されている”境界線”です。

3.multipart/form-data の構造

詳しくはこちらのサイトを参照して下さい。その中の一部を抜粋して説明します。

■HTTPヘッダ部

Content-Typeの内容

boundaryは、1文字以上70文字以下でなければなりません。
ブラウザによっては、40文字程度の場合もあります。

■ボディ部

ボディ部は、以下の3種類に分類されています。

フォームデータ(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundaryCRLF

テキストのパラメタ

ファイルデータ(バイナリデータも同様)(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundaryCRLF

ファイルを選択時のパラメタ

フッタ(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundary+後尾に--CRLF

フッタ内容

実際にEXCEL(VBA)からHTTP通信にて画像等のファイルを送信

実際簡単ではなかったポイントが、送信するファイルが、テキストではなく画像やEXCELファイル等のバイナリデータの場合です。
送信するmultipart/form-dataパラメタが、テキストであればString型の結合で対応できるのですが、画像やEXCELファイルはByte型になり、容易にはString型Byte型の結合ができませんでした。


解決方法として、上部でも記載さしている参考サイトと同様に、ファイル操作等に使用するADODB.Streamをパラメタ生成用の領域として使用することで、String型Byte型結合を実現しています。


ちなみに、今回もJSONを生成している部分は、こちらを使用させて頂いて居ます。

■サンプルプログラム

①メイン処理

Const adTypeBinary = 1
Const adTypeText = 2

Const adBTypeContent = 1
Const adBTypeBody = 2
Const adBTypeFooter = 3

Public Function UploadFile() As Boolean


    Dim FilePath As String: FilePath = "d:\証明写真サンプル.jpg"

    Dim strMethod As String: strMethod = "POST"
    Dim strUri As String: strUri = "http://localhost"
    Dim strResult As String

    '---------------------------------
    ' リクエストパラメタ用の領域を生成
    '---------------------------------
    Dim tempParamStream As Object
    Set tempParamStream = CreateObject("ADODB.Stream")
    tempParamStream.Open

    '---------------------------------
    ' リクエストパラメタ作成
    '---------------------------------
    Dim FileName As String
    FileName = Dir(FilePath)

    Dim JsonObject As Object
    Set JsonObject = New Dictionary

    JsonObject.Add "name", FileName
    JsonObject.Add "parent", New Dictionary
    JsonObject("parent").Add "id", 0

    If SetNomarlParameter(tempParamStream, "attributes", JsonConverter.ConvertToJson(JsonObject)) Then
    End If

    If SetFileParmater(tempParamStream, "file", FilePath, "application/octet-stream") Then
    End If

    If SetEndParameter(tempParamStream) Then
    End If

    '---------------------------------
    ' リクエストパラメタ取得
    '---------------------------------
    Dim snedParameter As Variant
    GetSendParameter snedParameter, tempParamStream

    '---------------------------------
    ' リクエスト
    '---------------------------------
    Dim objHTTP As Object
    Set objHTTP = CreateObject("msxml2.xmlhttp")
    objHTTP.Open strMethod, strUri, False
    objHTTP.setRequestHeader "Content-Type", "multipart/form-data; boundary=" + getBoundy(adBTypeContent)
    objHTTP.send snedParameter

    statusCode = objHTTP.status

    strResult = StrConv(objHTTP.responsebody, vbUnicode)
    Set objHTTP = Nothing

    UploadFile = True
End Function

②データフォームのパラメタ設定

Private Function SetNomarlParameter( _
                    ByRef tempParamStream As Object, _
                    ByVal fname As String, _
                    ByVal fvalue As String) As Boolean

    If fvalue <> "" Then

        ChangeStreamType tempParamStream, adTypeText

        Dim params As String
        params = ""
        params = params + getBoundy(adBTypeBody)
        params = params + "Content-Disposition: form-data; name=""" + fname + """" + vbCrLf
        params = params + vbCrLf
        params = params + fvalue + vbCrLf

        tempParamStream.WriteText params

    End If

    SetNomarlParameter = True
End Function

③ファイル(バイナリデータ)のパラメタ設定

Private Function SetFileParmater( _
                            ByRef tempParamStream As Object, _
                            ByVal fname As String, _
                            ByVal fvalue As String, _
                            ByVal fct As String) As Boolean

    '-------------------------------------
    ' テキストデータ
    '-------------------------------------
    ChangeStreamType tempParamStream, adTypeText

    Dim params As String
    params = ""
    params = params + getBoundy(adBTypeBody)
    params = params + "Content-Disposition: form-data; name=""" + fname + """; filename=""" + fvalue + """" + vbCrLf
    params = params + "Content-Type: " + fct + vbCrLf
    params = params + vbCrLf

    tempParamStream.WriteText params


    '-------------------------------------
    ' バイナリデータ
    '-------------------------------------
    ChangeStreamType tempParamStream, adTypeBinary

    Dim fileStream As Object
    Set fileStream = CreateObject("ADODB.Stream")
    fileStream.Type = adTypeBinary
    fileStream.Open
    fileStream.LoadFromFile fvalue

    tempParamStream.Write fileStream.Read()

    fileStream.Close
    Set fileStream = Nothing

    SetFileParmater = True
End Function

④フッタのパラメタ設定

Private Function SetEndParameter( _
                    ByRef tempParamStream As Object) As Boolean

    ChangeStreamType tempParamStream, adTypeText
    tempParamStream.WriteText getBoundy(adBTypeFooter)

    SetEndParameter = True
End Function

⑤送信するパラメタを取得

Private Function GetSendParameter( _
                    ByRef parameter As Variant, _
                    ByRef stream As Object) As Boolean

    ChangeStreamType stream, adTypeBinary
    stream.Position = 0
    parameter = stream.Read

    stream.Close
    Set stream = Nothing

    GetSendParameter = True
End Function

⑥パラメタ用の領域の状態を変更する

最初に、p = stream.Positionで現在のポジションを取得しているのは、状態を変更したことでポジションが変わってしまうためです。

Private Function ChangeStreamType( _
                    ByRef stream As Object, _
                    ByVal adType As Integer) As Boolean
    Dim p As Long
    p = stream.Position
    stream.Position = 0
    stream.Type = adType

    If adType = adTypeText Then
        stream.Charset = "UTF-8"
    End If

    stream.Position = p

    ChangeStreamType = True
End Function

⑦Boundy 情報取得

Boundyは、同じ文字列を使用することになるので、変数をstaticにすることで、1度生成した文字列を使って、用途に合わせたBoundyデータを復帰するようにしています。
各種参考させて頂いたサイトでは、Boundyデータは固定の文字列としていますが、折角なのでランダム文字列を生成年月日時分秒までを追加した文字列生成するようにしてみました。

Private Function getBoundy(ByVal adType As Integer) As String

    Static sBoundy As String

    If sBoundy = "" Then

        Dim multipartChars As String: multipartChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        Dim boundary As String: boundary = "--------------------"

        Dim i, point As Integer

        For i = 1 To 20
            Randomize
            point = Int(Len(multipartChars) * Rnd + 1)
            boundary = boundary + Mid(multipartChars, point, 1)
        Next

        sBoundy = boundary + Format(Now, "yyyymmddHHMMSS")

    End If

    Select Case adType
    Case adBTypeContent
        getBoundy = sBoundy
    Case adBTypeBody
        getBoundy = "--" + sBoundy + vbCrLf
    Case adBTypeFooter
        getBoundy = vbCrLf + "--" + sBoundy + "--" + vbCrLf
    End Select

End Function

送信パラメタの生成の経過

サンプルプログラムでは、"d:\証明写真サンプル.jpg" と画像ファイルを指定していますが
説明しやすいように、別のバイナリファイル(d:\test.bin)を用意して生成過程を確認してみます。

テキストデータを設定する場合には、tempParamStreamをテキスト状態に変更
ファイル(バイナリデータ)を結合する場合には、tempParamStreamをバイナリ状態に変更することで結合(パラメタを生成)を実現します。

test.bin の内容

テスト用バイナリデータ

テキストパラメタ設定時

    If SetNomarlParameter(tempParamStream, "attributes", JsonConverter.ConvertToJson(JsonObject)) Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り
※先頭の0xEF 0xBB 0xBF はBOM付き(UTF-8)の場合を表すコードとなります。

テキストパラメタまでの送信パラメタ

ファイルパラメタ設定時

    If SetFileParmater(tempParamStream, "file", FilePath, "application/octet-stream") Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り

バイナリデータ設定までの送信パラメタ

フッタまで設定時

    If SetEndParameter(tempParamStream) Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り

フッタを追加した時の送信パラメタ

まとめ

BOX API にて、もしもEXCEL(VBA)でファイルをアップロードさせるには今回の作成した処理を使って送信することが出来るはずです。
という事で、次回は、またBOX API に戻りPOST送信する処理を考えてみようと思っています。

英語弱者が送る翻訳サイトのススメ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

エンジニアに必要なもの。それは英語です。
コードを打つにも英語、リファレンスを読むにも英語、とにかく必要なのです。
いまどきはどんな人でも必要と言われているかもしれませんが特に我々エンジニアと英語は切っても切り離せない存在です。

ですが英語の勉強をしていなくても英語を訳すことができますよね。
そう、翻訳サイトです。わからない単語があったら翻訳サイトですぐに意味を調べることができます。
ある時僕が翻訳サイトを使用していて、イマイチしっくりくる翻訳が出来なかったので試しに他の翻訳サイトで訳してみるとニュアンスがかなり違って理解できた、なんてことがありました。


検証しよう!

そこで英語のことわざをいくつか翻訳サイトで訳してどれくらい違いがあるかを調べてみました。

その過程で「最後のわら一本がラクダの背中を折る」という、少しでも限度を超えると大変なことになる。といった意味の英語のことわざがそれぞれのサイトの特徴が出ていていい感じだったので、それを訳した文と僕が受けたそれぞれのサイトの特徴をまとめてみました。


・翻訳前の文
The last straw breaks the camel’s back.


Bing翻訳

最後のわらはラクダの背中を壊します。

いくつかの言葉を訳していて、一語一語の意味がわかりやすいと感じました。
「You」という単語が文の中に入っていても訳した文には「あなた」とは入れずに文を簡潔にまとめてくることが多かったです。わからない単語が多い時に使うといいかもしれません。


Google翻訳

最後のストローはラクダの背中を壊す。

ことわざをいくつか訳していた限りでは、あまり変な訳はでてきませんでした。さすがGoogle翻訳ですね。
ですが「kettle」をヤカン、釜ではなくケトルとしたり上記の例でも藁をストローと訳しています。
日本で通じる単語は何かに変換せずそのままカタカナにしてくれるのでわかりやすかったりそうじゃなかったりしました。


Weblio翻訳

我慢の限界は、後ろにラクダのものを壊します。

《諺》 ぎりぎりのところまで重荷を負ったラクダはその上わら 1 本でも積ませたら参ってしまう 《たとえわずかでも限度を越せば取り返しのつかない事になる》

日本語的には微妙な感じに翻訳されることが多かったのですが、ことわざが登録されていると直訳とは別にことわざの意味も表記してくれることがありました。
直訳は間違ってなさそうだけど意味はわからない!なんて時にはWeblio翻訳を使うといいのでは。


excyte翻訳

ぎりぎりの重荷を負ったラクダはわら1本でも積ませたら参ってしまう。

ことわざを訳していた限りではexcite翻訳が一番元の意味に近く翻訳してくれました。今回のように話し言葉のような形で翻訳してくれることも多かったので、ことわざや冗談を訳す時はexcyte翻訳に任せたいと思いました。


InfoSeekマルチ翻訳

我慢の限界は、ラクダの背中に怪我をします。

他のサイトとは単語の訳し方が違ったことが多かったです。
上記の例でも「怪我」という訳し方をしたのはここだけでした。
他のサイトで翻訳した文が微妙にしっくりこなかったりした時にはここに尋ねるといいでしょう。


おわりに

いかがでしたでしょうか。思ったより翻訳に差が出て調べている間結構楽しかったです。
よく考えたら翻訳が全く同じ内容なら翻訳サイトが複数あるなんてこと起こりませんよね。

ちなみに翻訳をお金を払ってやってもらうこともできます。
送った文章を機械じゃなく人力で翻訳してもらうことができたり、より精度の高い翻訳が有料だったりします。

……英語勉強してきます。

cURL(カール)ではなく、httpie を使用しBOX APIの確認をしてみる。

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

box api referenceでは、cURL(カールと読む)でのリクエスト方法が記載されていますが、
このcURLを使ったの確認の場合、JSONデータが整形されないため、使いにくい。
その事から、別のコマンドラインHTTPクライアントのhttpieを使う事にしてみます。

cURL と、httpie での比較

BOX のフォルダ情報取得処理(Get Folder Info)で比較してみます。

cURL の場合

 > curl https://api.box.com/2.0/folders/0 -H "Authorization: Bearer fAFzgyluzN5JUkjUFIF3jWjGQl48ucvx"
curl の実行イメージ

httpie の場合

 > http -v GET https://api.box.com/2.0/folders/0 "Authorization: Bearer fAFzgyluzN5JUkjUFIF3jWjGQl48ucvx"

シンタックスハイライトも付いているので、見やすい!!

httpie の実行イメージ

httpie をインストールしてみる。

会社からアテンドされているPCもwindow という事で、windows 環境にインストールしてみます。

1.python ダウンロード

httpieを使うには、python が必要です。

こちらのサイトから、python をダウンロードして下さい。

python のダウンロード画面

2.python インストール

インストール画面で、デフォルトのInstall Now を選択してもいいですが、
Customize installation を選択し、インストールする場所を指定する事をオススメしています。
この後説明する、環境設定のパスがOSのエディションなどにもよるが、ほとんどの場合はPATH環境変数の最大長は約2000文字以下に制限されているためです。

インストール時の選択画面1
インストール時の選択画面2

3.環境変数(path)の設定

Windows 10 で環境変数を設定するための流れは
Windowsキー → 設定 → システム → バージョン情報 → システム情報 → システムの詳細設定

仮に、python をインストールしたのが、c:\python の場合
システム境変数 の Path に以下の2つを追加して下さい。

  c:\python
  c:\Python\Scripts
システムプロパティ画面
環境変数設定の画面

4.python の確認

コマンドプロンプト画面を表示し、python とだけ実行すると以下の様に表示されます。

python のバージョン表示

5.httpie をインストール

インストールには、Python のパッケージ管理(pip)を使います。
以下のコマンドを実行して下さい。

 > pip install --upgrade pip setuptools

 > pip install --upgrade httpie

httpie の オプション紹介

一番使いそうな出力オプションのみをご紹介。
詳しくは、httpie使用方法を参照して下さい。

1.出力オプション

-h  応答ヘッダーのみが出力
-b  レスポンスBodyのみが出力
-v  HTTP交換全体(要求と応答)を出力
-p  HTTP交換の一部を選択

 http -v https://api.box.com/2.0/folders/0

1.1.HTTP交換のどの部分を出力するか指定

H  要求Headers
B  要求Body
h  応答Headers
b  応答Body

  http -p=hb https://api.box.com/2.0/folders/0

まとめ

BOXのAPIを簡単に確認出来るようになった気がします。

mapとpluck

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

 サービスの開発でよく使用するデータベース(DB)。DBテーブルの特定のデータを一度に取得する方法として、mapメソッドとpluckメソッドが存在する。どちらともデータを取得するという点では同じですが、いくつか異なる点が存在します。この記事ではこれらの違いとデータを取得する際の使い分けについて説明をします。

mapメソッド
 mapはrubyで使用することができるメソッドです。レシーバの要素の数だけブロックを実行し、ブロック内の結果を配列に格納して配列で返す機能があります。また、&(アンパサンド)を使用することでコードを簡略化することが可能。&には、ブロックを展開する意味がある。

["a", "b", "c"].map { |str| str.upcase } # => ["A", "B", "C"]
["a", "b", "c"].map(&:upcase) # => ["A", "B", "C"]

 mapメソッドは、配列だけでなくハッシュに対しても実行することができます。mapメソッドのレシーバがハッシュでも配列を返します。

{1 => "taro", 2 => "jiro"}.map { |key, value| key.upcase } # => ["TARO", "JIRO"]

 最後にmapメソッドを使用したDBデータ取得についてです。以下のユーザモデルが存在した場合、データの中から取得したいカラムを以下のように指定することで、そのカラムのデータを全て取得することができる。

#<ActiveRecord::Relation [
 #<User id: 1, name: "taro", created_at: "2017-06-01 01:00:00", updated_at: "2017-06-01 01:00:00">,
 #<User id: 2, name: "jiro", created_at: "2017-06-01 01:00:00", updated_at: "2017-06-01 01:00:00">
]>

User.all.map{ |user| user.id } # => [1, 2]
User.all.map{ |user| user.name } # => ["taro", "jiro"]
User.all.map(&:id) # => [1, 2]

pluckメソッド
 pluckメソッドは、引数に指定したカラムの配列を返すメソッドです。このメソッドはRailsで使用できるメソッドなので、Rubyのみでは使用することができません。また、pluckメソッドには、引数に&が必要無く、複数のカラムを指定することもできます。複数のカラムを指定した時は、2次元配列で返ります。

#<ActiveRecord::Relation [
 #<User id: 1, name: "taro", created_at: "2017-06-01 01:00:00", updated_at: "2017-06-01 01:00:00">,
 #<User id: 2, name: "jiro", created_at: "2017-06-01 01:00:00", updated_at: "2017-06-01 01:00:00">
]>

User.pluck(:id) # => [1, 2]
User.pluck(:id, :name) # => [[1, "taro"], [2, "jiro"]]

mapとpluckの違い
 mapメソッドとpluckメソッドの使い分けについて気になったので調べてみました。pluckメソッドは、mapメソッドよりも処理速度が速いと言われていますが、必ずそうとも言えないようです。pluckメソッドは、実行するたびにSQLを発行するので、データ数によっては、mapメソッドより遅くなる場合があります。mapとpluckの使い分けについてですが、参考記事によるとDBからデータを直接取得する場合はpluckメソッドを使用し、インスタンスからデータを取得する場合はmapメソッドを使用するとSQLの回数を抑えることができます。

User.pluck(:id)

user = User.where(“age > 20”)
user.map(&:name)

まとめ
 簡単にmap・pluckメソッドの説明を行いました。私は、データの取得にmapメソッドしか使用せずにパフォーマンスのことも考えてきませんでした。今回の記事で少しでも多くの方がパフォーマンスに気をつけることができるようになれば幸いです。

参考記事
http://qiita.com/metheglin/items/18064851a8f00dab67f8
http://yachibit.hateblo.jp/entry/2014/03/05/002844

beebole/pure.jsで動的にhtmlを表示する方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

pure.jsとは

json形式のデータを使って、ページの一部分を自動生成するテンプレートエンジンです。

使用例

たとえば↓のようなチュートリアル通りにHTMLを用意すると、

index.html

<script src="http://pure.github.io/pure/libs/pure.js"></script>

<!-- HTML template -->
<ul>
<li>
  <span></span>
</li>
</ul>

<script>
var data = {
  animals:[
    {name: 'mouse'},
    {name: 'cat'},
    {name: 'bird'},
  ]
};
var directive = {
  'li':{
    'animal<-animals':{
      'span': 'animal.name'
    }
  }
};
$p( 'ul' ).render( data, directive );
</script>

下のような形式で出力されます。

<ul>
  <li><span>mouse</span></li>
  <li><span>cat</span></li>
  <li><span>bird</span></li>
</ul>

※スクリプトの説明
data:表示したいデータ
directive:dataとhtmlの対応
$p( ‘ul’ ).render( data, directive ):表示処理

問題点

htmlを生成するときは
$p( ‘ul’ ).render( data, directive );
を実行するのですが、何回も実行してしまうと表示がおかしなことになってしまいます。

<script src="http://pure.github.io/pure/libs/pure.js"></script>

<ul>
<li>
  <span></span>
</li>
</ul>

<a href="javascript:addFox();">キツネを追加する</a>

<script>
var data = {
  animals:[
    {name: 'mouse'},
    {name: 'cat'},
    {name: 'bird'}
  ]
};
var directive = {
  'li':{
    'animal<-animals':{
      'span': 'animal.name'
    }
  }
};
$p( 'ul' ).render( data, directive );

function addFox(){
  data.animals.push({name: 'fox'});
  $p( 'ul' ).render( data, directive );
}
</script>

初期表示↓

enter image description here

「キツネを追加する」をクリックすると↓

\[押下後.png\]

このように大量に表示されてしまいます。

対処方法

これを防ぐためにはdirectiveに対して以下の1処理(コンパイル)が必要になります。

var compiled = $p( 'ul' ).compile( directive );

このコンパイルされた値を使ってレンダリングすることで大量に表示されてしまうのを防ぎます。
以下が修正後のスクリプトです。

<script>
var data = {
  animals:[
    {name: 'mouse'},
    {name: 'cat'},
    {name: 'bird'}
  ]
};
var directive = {
  'li':{
    'animal<-animals':{
      'span': 'animal.name'
    }
  }
};
var compiled = $p( 'ul' ).compile( directive );
$p( 'ul' ).render( data, compiled );

function addFox(){
  data.animals.push({name: 'fox'});
  $p( 'ul' ).render( data, compiled );
}
</script>

では、「キツネを追加する」をクリックしてみます。

enter image description here

はい、ちゃんとfoxが一つだけ追加されましたね。

感想

pure.jsは完成されたhtmlに埋め込みやすくなっているので割と便利です。
細かい処理にも融通が利くので割と好きです。
pureって名前もいいと思います。

参考

https://beebole.com/pure/

Slack Web API であそぼ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Slack Web APIで遊びます。 もともと難しくないと思いますが、rubyだと gemを使えばさらにかんたんです。

とりあえず今回使ってみるgem, slack-apiのリポジトリとドキュメントです。

slack-apiを使ってみよう

トークンの取得

Slack APIを使うために、まずトークンをとってきましょう。

https://api.slack.com/

の “Start Building” から適当な名前 (私は「あああああ」にしました) とAPIを使いたいチームを選ぶとAppができます。
OAuth & Permissionsから 適当な Permission Scopeを1つ以上 (私は emoji:read にしました) 選んで保存、ページ上の方の Install App to Team からアプリをインストール?するとトークンが発行されます。OAuth Access Token というのがそれです。

トークンがあると、API経由でトークンを発行したアカウントとして色々なことができてしまうので、これは基本的に人に見せてはいけないものです。
ソースコードをgitで管理するときは注意しましょう。私はよくうっかりコミットしています。

やってみよう(その1)

トークンを取得したらslack-apiを試してみましょう。
ドキュメントの http://www.rubydoc.info/gems/slack-api のコードの "YOUR_TOKEN" をさっきのトークンに置き換えて実行してみましょう。

require "slack"

Slack.configure do |config|
  config.token = "YOUR_TOKEN"
end

Slack.auth_test

コンソールでこれをそのまま実行すると、認証に成功しようが失敗しようが特に何も表示されません。

p なり何なりで Slack.auth_test の返り値を見てみると、認証に成功した場合はURLなどが、失敗した場合はエラーの内容がhashで返ってきます。
基本的にはSlack Web API https://api.slack.com/web のそのままです。slack-apiはトークンまわりやJSONのparseをしてくれるだけで、このあたりは自力です。DIYです。

# 成功した場合
{"ok"=>true, "url"=>"https://hoge.slack.com/", "team"=>"hoge", "user"=>"fuga", "team_id"=>"TXXXXXXXX", "user_id"=>"UXXXXXXXX"}
# 失敗した場合
{"ok"=>false, "error"=>"invalid_auth"}

やってみよう(その2)

もうちょっと意味のあるAPIを叩いてみたいですね。何かないでしょうか。

https://api.slack.com/methodshttp://www.rubydoc.info/gems/slack-api/Slack/Endpoint

あたりを見比べてやっていきます。
slack-apiのドキュメントにはどういったパラメータを指定すればよいかは書いていないので、そちらはslack apiのメソッド一覧を見てがんばります。

とりあえず役に立ちそうなものとして、ユーザーの一覧 を取得してみます。
https://api.slack.com/methods/users.list を見るに、必須パラメータはトークンだけですが、そのトークンはslack-apiがなんとかしてくれます。

permission がない場合はエラーになります。必要なものが何か返ってくるので、最初にしたように permission を追加してあげましょう。
emoji:read しかpermissionを与えていないと当然ですが駄目です。言われるがままに users:readを追加しましょう。

> Slack.users_list
=> {"ok"=>false, "error"=>"missing_scope", "needed"=>"users:read", "provided"=>"identify,emoji:read"}

permission を 追加すると何かメッセージが上に出てくると思いますが、reinstallする必要があります。
permission を追加すれば無事にできます。せっかくなので実行例を貼りますが、情報の大洪水です。ぜひ読み飛ばしてください。

> Slack.users_list
=> {"ok"=>true,
 "members"=>
  [{"id"=>"UXXXXXXXX",
    "team_id"=>"TXXXXXXXX",
    "name"=>"fugahoge",
    "deleted"=>false,
    "color"=>"4bbe2e",
    "real_name"=>"Hoge Fugefuga",
    "tz"=>"Asia/Tokyo",
    "tz_label"=>"Japan Standard Time",
    "tz_offset"=>32400,
    "profile"=>
     {"first_name"=>"Hoge",
      "last_name"=>"Fugafuge",
      "avatar_hash"=>"db6076802fe8",
      "image_24"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_24.png",
      "image_32"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_32.png",
      "image_48"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_48.png",
      "image_72"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_72.png",
      "image_192"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_192.png",
      "image_512"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_512.png",
      "image_1024"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_1024.png",
      "image_original"=>"https://avatars.slack-edge.com/2000-00-00/173314781776_db6076802fe8e541e650_original.png",
      "status_text"=>"あああああああああ",
      "status_emoji"=>":star:",
      "real_name"=>"Hoge Fugafuga",
      "real_name_normalized"=>"Hoge Fugafuga",
      "team"=>"TXXXXXXXX"},
    "is_admin"=>true,
    "is_owner"=>false,
    "is_primary_owner"=>false,
    "is_restricted"=>false,
    "is_ultra_restricted"=>false,
    "is_bot"=>false,
    "updated"=>1501299574,
    "is_app_user"=>false,
    "has_2fa"=>false},
   {"id"=>"USLACKBOT",
    "team_id"=>"TXXXXXXXX",
    "name"=>"slackbot",
    "deleted"=>false,
    "color"=>"757575",
    "real_name"=>"slackbot",
    "tz"=>nil,
    "tz_label"=>"Pacific Daylight Time",
    "tz_offset"=>-25200,
    "profile"=>
     {"first_name"=>"slackbot",
      "last_name"=>"",
      "image_24"=>"https://a.slack-edge.com/0180/img/slackbot_24.png",
      "image_32"=>"https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_32.png",
      "image_48"=>"https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_48.png",
      "image_72"=>"https://a.slack-edge.com/0180/img/slackbot_72.png",
      "image_192"=>"https://a.slack-edge.com/66f9/img/slackbot_192.png",
      "image_512"=>"https://a.slack-edge.com/1801/img/slackbot_512.png",
      "avatar_hash"=>"sv1444671949",
      "always_active"=>true,
      "real_name"=>"slackbot",
      "real_name_normalized"=>"slackbot",
      "fields"=>nil,
      "team"=>"TXXXXXXXX"},
    "is_admin"=>false,
    "is_owner"=>false,
    "is_primary_owner"=>false,
    "is_restricted"=>false,
    "is_ultra_restricted"=>false,
    "is_bot"=>false,
    "updated"=>0,
    "is_app_user"=>false}],
 "cache_ts"=>1501754086}

いろんな情報が返ってきますね。slackbot の is_bot が false なのが得心いきませんが、こういうもののようです。

idなる文字列 "UXXXXXXXX" がありますが、発言ログなどを取ったときやAPIで投稿するとき、 Slack API ではこれを使います。
チャンネルも同様に “CXXXXXXXX” などになります。idは変わらないので決め打ちでもよいのですが、そうもいかない場合は自力で照合してやりましょう。

permission に users:read.email を追加すると、メールアドレスも返ってくるようになります。

やってみよう(その3)

迷惑ユーザーなのでslackの同じチームのユーザー全員にdirect messegeを送りつけたくなりました(ジョークです)。

direct messageを送りつけるのはかんたんです。
https://api.slack.com/methods/chat.postMessage を見るに、宛先と本文(とトークン)さえあれば送れます。
手始めにslackbotに送ってみます。channelはユーザーid(Uから始まる方)、textは適当な文章でやってみます。
前のusers.listの結果を見るに slackbotのユーザーidは “USLACKBOT” です。

> Slack.chat_postMessage(channel: "USLACKBOT", text: "あああああ")
=> {"ok"=>true,
 "channel"=>"DXXXXXX",
 "ts"=>"1501756000.743447",
 "message"=>{"type"=>"message", "user"=>"UXXXXXXXX", "text"=>"あああああ", "bot_id"=>"BXXXXXXXX", "ts"=>"1501756000.743447"}}

slackbotには何言ってんの?という反応をいただきました。

enter image description here

実は Slack API 的には、UやCから始まる例のidでなくてもメッセージが送れます。

> Slack.chat_postMessage(channel: "@slackbot", text: "あああああ")
=> {"ok"=>true,
 "channel"=>"DXXXXXX",
 "ts"=>"1501756000.743447",
 "message"=>{"type"=>"message", "user"=>"UXXXXXXXX", "text"=>"あああああ", "bot_id"=>"BXXXXXXXX", "ts"=>"1501756000.743447"}}

さて、ここまでやったことを活かせば、無事に1行で迷惑ユーザーになれます。

Slack.users_list["members"].each {|member| Slack.chat_postMessage(channel: member["id"], text: "あああああ")}

間違いなく顰蹙を買うので実行するのはやめたほうがいいと思います。

まとめ

これでslackでなんでもできる気がする!!!!!!!

実際permissonさえ追加すればユーザーとしてできることはたいていできるはずです。

おまけ

派生?gemが存在して、slack-apiよりもこちらの方が若干APIのレスポンスを丁寧に返してくれます。
https://github.com/slack-ruby/slack-ruby-client

また、Real Time Messaging APIもgemから使えます。
Real Time Messaging API は botトークンでないと使えないので注意です。({"ok":false,"error":"not_authed"} ……)

Real Time Messaging API は 先駆者がいるので (名言botをslackに入れて「人生」とは何か考えよう )ここではあまり言及しません。

gemを使う場合もそこまで変わらず、違うのは

  • rtm.start からurlを受け取ったり Faye::WebSocket::Client.new(url) したりするくだりが全部まとめて Slack.realtime でいい
  • JSONもparseしたりしなくていい

くらいです。

Slack API を組み合わせて君だけの最高のbotを作ろう!

RealmでKotlinのモデルクラスを扱うときの注意点

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


概要

RealmというDBがやたら早いとかでモバイルでは流行りみたいです。
ここ数年はSQLiteからRealmに移行してる人が多いとか。
ということでRealm触ってみました。

サンプルコードの概要

冒頭のgif画像が動いている様子です。
右のボタンを押すと現在時刻をDBに突っ込んで表示します。
左のボタンを押すとDBのレコードを全て削除します。
fab使ってみたかったのでこんな感じにしました。
dataListを直接触らなくてもRealmを使うと簡単にRecyclerViewに反映されます。

以下コード

Activity

class MainActivity : AppCompatActivity() {
    lateinit var mRealm: Realm
    lateinit var mRecyclerView: RecyclerView
    lateinit var mAdapter: RecyclerViewAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_recycler_view)

        // Realmのセットアップ
        val realmConfig = RealmConfiguration.Builder(baseContext)
                .deleteRealmIfMigrationNeeded()
                .build()
        mRealm = Realm.getInstance(realmConfig)

        // Realmを読み込み
        val dateList: RealmResults<CurrentTimeModel> = mRealm.where(CurrentTimeModel::class.java).findAll()

        // RecyclerViewのセットアップ
        mRecyclerView = findViewById(R.id.recycler_view) as RecyclerView
        mAdapter = RecyclerViewAdapter(dateList)
        val layoutManager = LinearLayoutManager(applicationContext)
        mRecyclerView.layoutManager = layoutManager
        mRecyclerView.itemAnimator = DefaultItemAnimator()
        mRecyclerView.adapter = mAdapter
        mRecyclerView.addItemDecoration(DividerItemDecoration(this))

        // ボタンのセットアップ
        val fabAddCurrentDateTime = findViewById(R.id.fab_add_current_date_time)
        fabAddCurrentDateTime.setOnClickListener { addCurrentDateTime() }

        val fabDeleteAllRecords = findViewById(R.id.fab_delete_all_records)
        fabDeleteAllRecords.setOnClickListener { deleteAllRecords() }
    }

    override fun onDestroy() {
        super.onDestroy()

        mRealm.close()
    }

    fun addCurrentDateTime() {
        mRealm.executeTransaction {
            val currentDateTime = mRealm.createObject(CurrentTimeModel::class.java)
            // LocalDateTime.now()がMIN_APIで使えないので、KotlinMomentを使用
            currentDateTime.currentTime = Moment().toString()
            mRealm.copyToRealm(currentDateTime)
        }

        mAdapter.notifyDataSetChanged()
    }

    fun deleteAllRecords() {
        mRealm.executeTransaction {
            mRealm.where(CurrentTimeModel::class.java)
                    .findAll()
                    .deleteAllFromRealm()
        }

        mAdapter.notifyDataSetChanged()
    }
}

Model

open class CurrentTimeModel(
        open var currentTime: String = ""
): RealmObject() {}

エラーが出る

Realmを使っていて、エラーが出て詰まる場面が2つありました。
1. 新しくモデルクラスを追加したときに、class com.list_sample.realmkotlinsample.FooModel is not part of the schema for this Realm. というエラーが出る
2. 既に使っているモデルクラスをリネームしたり、パッケージ移動したりすると `Error:Execution failed for task ‘:app:transformClassesWithRealmTransformerForDebug’.

javassist.NotFoundException: com.list_sample.realmkotlinsample.Model.` というエラーが出る

エラー対策

JavaとKotlinで書いてみて、言語によって起こるエラーとそれ以外を切り分けます。

1. 新しくモデルクラスを作成した場合のエラー

まずはJavaで簡単なサンプルを動かす

#MainActivity
package com.list_sample.realmjavasample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;

public class MainActivity extends AppCompatActivity {
    private Realm mrealm;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Realmのセットアップ
        RealmConfiguration realmConfig = new RealmConfiguration.Builder(this)
                .deleteRealmIfMigrationNeeded()
                .build();
        mrealm = Realm.getInstance(realmConfig);

        writeRealm();

        RealmResults<HogeModel> mrealmResult = mrealm.where(HogeModel.class).findAll();
        Log.d("Hoge", "Realm result is " + mrealmResult.toString());
    }

    private void writeRealm() {
        mrealm.executeTransaction(new Realm.Transaction() {

            @Override
            public void execute(Realm realm) {
                HogeModel hoge = mrealm.createObject(HogeModel.class);
                hoge.setHoge("hoge");

            }
        });
    }
}
#HogeModel
public class HogeModel extends RealmObject{
    private String hoge;

    public String getHoge() {
        return hoge;
    }

    public void setHoge(String hoge) {
        this.hoge = hoge;
    }
}

当然正常に動きます。
これに新しくモデルクラスとして、FooModelを追加します。

# FooModel
public class FooModel extends RealmObject {
    public String getFoo() {
        return foo;
    }

    public void setFoo(String foo) {
        this.foo = foo;
    }

    private String foo;

}

# MainActivity
public class MainActivity extends AppCompatActivity {
    private Realm mrealm;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Realmのセットアップ
        RealmConfiguration realmConfig = new RealmConfiguration.Builder(this)
                .deleteRealmIfMigrationNeeded()
                .build();
        mrealm = Realm.getInstance(realmConfig);

        writeRealm();

        RealmResults<FooModel> mrealmResult = mrealm.where(FooModel.class).findAll();
        Log.d("Foo", "Realm result is " + mrealmResult.toString());
    }

    private void writeRealm() {
        mrealm.executeTransaction(new Realm.Transaction() {

            @Override
            public void execute(Realm realm) {
                FooModel foo = mrealm.createObject(FooModel.class);
                foo.setFoo("foo");
            }
        });
    }
}

Javaではエラーが出ません。
これをKotlinで書くと、

# FooModel
open class FooModel(
        open var foo: String = ""
): RealmObject(){}
# MainActivity
class MainActivity : AppCompatActivity() {
    lateinit var mrealm: Realm

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Realmのセットアップ
        val realmConfig = RealmConfiguration.Builder(this)
                .deleteRealmIfMigrationNeeded()
                .build()
        mrealm = Realm.getInstance(realmConfig)

        writeRealm()

        val mRealmResults: RealmResults<FooModel> = mrealm.where(FooModel::class.java).findAll()
        Log.d("Foo", "Realm results is $mRealmResults")

    }

    fun writeRealm() {
        mrealm.executeTransaction {
            val foo = mrealm.createObject(FooModel::class.java)
            foo.foo = "foo"
        }
    }
}

落ちます。
class com.list_sample.realmkotlinsample.FooModel is not part of the schema for this Realm.

原因と対策

これの原因はRealmのライブラリのバージョンが古かったことです。
今のRealmライブラリの最新バージョンが3.7.1で使っていたライブラリが1.1.0でした。

2. 既に使っているモデルクラスをリネームしたり、パッケージ移動したりすると `Error:Execution failed for task ‘:app:transformClassesWithRealmTransformerForDebug’.

javassist.NotFoundException: com.list_sample.realmkotlinsample.Model` というエラーが出る
ここでもJavaとKotlinを使って切り分けます。
不要になったHogeModelというクラスを削除してみます。

Javaの場合

削除しても正常に動きます。

Kotlinの場合
Error:Execution failed for task ':app:transformClassesWithRealmTransformerForDebug'.
> javassist.NotFoundException: com.list_sample.realmkotlinsample.HogeModel

落ちます。

原因

Realmはモデルクラスを作ってビルドするときに、Proxyクラスを生成します。
HogeModelの場合はHogeModelRealmProxyというファイルが生成されます。
これはJavaの場合もKotlinの場合も同じなんですが、Kotlinの場合RealmProxy クラスとモデルの名前やディレクトリが異なるとエラーが出ます。

対策

Rebuildすると直ります。一番手っ取り早いです。
あとはRealmProxyクラスを削除しても直ります。 -> android studio でcleanするとbuild以下のファイルは全て消えるため、手動で消すのは無駄だと指摘をうけました。
Javaでモデルクラスを作成した場合はそもそもエラーが出ないです。
Rebuildが一番良さそうな解決方法です。

感想

速度が求められるモバイル開発では今後もRealmを使う場面が増えてくると思います。
今回はざっくりした実装とエラー対策ではありましたが、これからも使い方を勉強してうまく使えるようになりたいですね。
あと、今回の記事が間違ってるよ!って方は教えていただけると助かります。
fabだったりRealmだったり、adb使った動画キャプチャのとり方だったり、画像の圧縮方式だったり、個人的には学びが多かったのでよかったです。
あとkotlinはやっぱりいいです。楽だし書きやすい。

スルメのススメ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

日々のデスクワークの中、集中して作業するのはとても疲れるもの。
適度な休憩や補給をすることで、かえっていい結果が残せたりするものです。
作業の合間にちょっとしたストレッチ等、やってみてはいかがでしょうか。

さて、今回紹介するのは間食にお薦めの食材、「スルメ」です。
お酒のツマミとかのイメージが多いかもですが、なんと間食に最適な食材だったのです!

ではまず「スルメとは」について紹介します。

「スルメ(鯣)は、イカの内臓を取り除いて素干しや機械乾燥などで乾燥させた加工食品。乾物の一種。古くから日本、朝鮮半島、中国南部および東南アジアにおいて用いられている食品で長期保存に向いている。日本では縁起物とされ結納品などにも用いられ寿留女と表記される。俗語としてアタリメとも言う。」(出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2017/05/17 02:23 UTC 版))

栄養成分(100gあたり):334kcal 
 タンパク質:69.2g
 脂質:4.3g
 炭水化物:0.4g
 ビタミンB群、Eに優れ、ミネラルの含有量も多い。また、疲労回復によいとされるタウリンも多く含まれる。(出典:カロリーslim http://calorie.slism.jp/110353/)

スルメを選ぶメリットとは?

 スルメという食品には以下の健康効果が秘められています。

1. 眠気に効く!

 眠気を感じた時、硬いものを嚙むことは有効です。咀嚼筋が動かされることで脳への刺激が伝わると、脳の機能が活発化し、眠くなりにくくなるのはもちろん、集中力が上がったりします。顎は筋肉の伸縮を感知する「筋紡錘(きんぼうすい)」が多く存在するため、筋肉を動かすと脳に刺激が伝わりやすく、眠気が起きにくくなるといいます。

2. ダイエット効果!

 噛むことは、ダイエットにも効果があります。噛む動作により、脳内にヒスタミンが発生し、満腹中枢が刺激されます。またタンパク質を多く含む食品のため、適度な運動を併せて行うことで、筋肉量を増加し太りにくい体を作ることができます。

3. 疲労回復効果!

 イカはタウリンを多く含みます。タウリンはアミノ酸の一種で、細胞の動きを正常にする作用があるため、肝機能の改善、風邪予防、疲労回復、といった効果があります。余分に取り過ぎた場合は体外に排出されるため、過剰摂取を心配する必要はありません。

スルメを摂取するうえで注意するべきことは?

1. 塩分の摂り過ぎ

 スルメは大体100gあたり3g程度の塩分を含みます。過剰摂取により発生した高い血中塩分濃度を補うため、細胞に水分を保持しようとする(→むくみ)、水分を多くとることで血流量が増大する(→高血圧)、といった現象が発生します。
 そのため、バナナなどに多く含まれるカリウムを摂取して、塩分の排出を促す必要があります。

2. 消化が悪い

 スルメはその硬さ故、消化に時間がかかります。それは反面「腹持ちがいい」ということでもあるのですが、消化が終わるまでの間、胃に負担をかけることになります。胃酸の分泌も促進されるので、注意が必要です。

3. 独特の匂い(スメル)

 イカを含めた海産魚介類には「トリメチルアミンオキシド」というエキス成分が含まれており、これが加熱などにより「トリメチルアミン」に変わります。この成分は量によっては独特な魚の腐敗臭を感じさせることもあり、周囲への配慮が必要です。

まとめ

このように、様々な健康効果を持つ食品であるスルメ。一方、過剰摂取には気を付けないといけない面もあります。摂取量をきをつけつつ、間食の一つとして採用してみてはいかがでしょうか。

名言botをslackに入れて「人生」とは何か考えよう

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、いくたです。

みなさん、人生について考えていますか?
毎日忙しいとなかなか考える暇がありませんよね。

そこで普段仕事中に使っているチャットツールである Slack に名言を教えてくれるbotを作ってみました。忙しい中でもふとした瞬間に人生について考えるきっかけになるかもしれませんね。

準備するもの

  • Ruby(ver 2.1.0)
  • gem: open-uri → URL先のデータを普通のファイルと同様に扱えます
  • gem: Nokogiri →  HTMLやXMLの構造を解析して特定の要素を抽出できます
  • gem: MeCab → 日本語の文章を品詞単位で解析してくれます
  • SlackBot → 導入方法は こちらの記事 がわかりやすかったです
  • 名言 → こちらの 偉人の名言100 から名言をとってきます

MeCab とは日本語の文章を品詞単位で解析してくれるオープンソース形態素解析エンジンです。

試しに二葉亭四迷の名言を解析してみました。
品詞ごとに分けて品詞の種類を教えてくれます。

$ mecab
いや、人生は気合だね。二葉亭四迷
いや  接続詞,*,*,*,*,*,いや,イヤ,イヤ
、 記号,読点,*,*,*,*,、,、,、
人生  名詞,一般,*,*,*,*,人生,ジンセイ,ジンセイ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
気合  名詞,一般,*,*,*,*,気合,キアイ,キアイ
だ 助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
ね 助詞,終助詞,*,*,*,*,ね,ネ,ネ
。 記号,句点,*,*,*,*,。,。,。
二葉亭四迷 名詞,固有名詞,人名,一般,*,*,二葉亭四迷,フタバテイシメイ,フタバテイシメイ
EOS

気合いですか…先生…

実装の概要

今回作るのはユーザーが投稿した内容に反応して名言を返してくれるbotです。
下の画像の saying_bot が今回作ったbotです。
直前の「ただ」という単語に反応してダグ・ハマーショルドさんの名言を投稿しています。

slack_bot.png
図1 投稿した内容の単語に反応して名言を返すsaying_bot

ざっくりとした仕組みとしては以下のような感じです。

  1. 名言データをサイトからとってくる
  2. MeCabで名言データを解析して”名詞”だけを抜き出す(名詞リスト)
  3. 投稿された内容に名詞リストと一致する部分があるか調べる
  4. 一致するものがあれば対応する名言を投稿する

1.については私が前回書いた記事でWebスクレイピングについて書いたので割愛します。open-uri や Nokogiri の使い方と合わせて書いてあるので、よろしければこちらも合わせてご覧ください。
【前回の記事】アニメ名探偵コナンの新一が出てる回だけを見たい

今回の記事では 2〜4 についてどうやって実装したか具体的に見ていきます。
また、裏テーマとして「クラスを役割ごとに分ける」ということも意識しているので、そのあたりも一緒に追っていきます。

MeCabで名詞だけを抽出

Stringに新たにparse_nounメソッドを追加します。
文字列から名詞だけを取り出して、配列に格納してくれます。

こちら↓を参考にして書きました。
Rubyの形態素解析「MeCab」で文章から名詞を抽出してみる

抜き出す名詞の種類を「一般名詞」と「形容動詞語幹」に絞っています。
名詞の全てを抜き出してしまうと「それ」「あれ」といった指示代名詞等も入ってしまうので細分化して絞り込みました。
一般名詞は「本」とか「人間」といった普通の名詞です。
形容動詞語幹とは「親切」とか「変」などのことをいいます。
形容動詞の「親切だ」「変だ」という語から変形して名詞のように使われています。

# mecab_string.rb
require 'MeCab'

# Stringクラスに名詞抽出メソッドを追加
class String
  def parse_noun
    model = MeCab::Model.new(ARGV.join(' '))
    tagger = model.createTagger
    node = tagger.parseToNode(self)

    nouns = []
    while node
      target_node = node.feature.force_encoding('UTF-8')
      # 名詞(一般・形容動詞語幹)を抽出する
      if /^名詞,一般/ =~ target_node || /^名詞,形容動詞語幹/ =~ target_node
        nouns.push(node.surface.force_encoding('UTF-8'))
      end
      node = node.next
    end
    nouns
  end
end

名言を保持するクラス

名言のデータを保持するクラスを考えます。
インスタンスからid(名言のID)、dialog(発言)とgreatman(偉人)というメソッドで名言のデータ取ってこれるように、attr_accessorをつかっています。

# saying.rb
# Sayingクラスは名言データを持つためのクラス
class Saying
  attr_accessor :id, :dialog, :greatman
  def initialize(id, dialog, greatman)
    @id = id
    @dialog = dialog
    @greatman = greatman
  end
end

名言データと名詞リストを扱うクラス

つぎにSayingDaoクラスを作成します。
DaoはData Access Objectの略でデータを扱うオブジェクトです。
今回はサイトからとってきた「名言データ」と名言から名詞を抽出した「名詞リスト」のふたつを同時に扱います。

SayingDaoクラスの役割

initialize

  1. 名言の内容をサイトから取得
  2. 使いやすいようにハッシュ化して「名言データ」をつくる
  3. 名言から名詞を抽出して「名詞リスト」をつくる

find_and_sample_saying メソッド

  1. 任意の文字列(ユーザーの投稿内容)を引数にとる
  2. initialize内で作成した名詞リストと照らし合わせる
  3. 一致したものがあれば名言データをひとつだけ返す
  4. 一致しなければnilを返す

繰り返しになりますが、Open-uriやNokogiriの使い方は前回の記事をどうぞ。

# saying_dao.rb
require 'open-uri'
require 'nokogiri'
require_relative 'saying.rb'
require_relative 'mecab_string.rb'

# 名言リストと名詞テストを作成する
class SayingDao
  attr_accessor :sayings_data, :nouns_list

  # 名言をとってくるURL
  SAYING_URL = 'http://atsume.goo.ne.jp/HxLFhNn4N7Zb'.freeze
  XPATH = "//*[@id=\"atsumeWrapper\"]/div[3]/div".freeze

  def initialize
    # 名言をとってくるURLからNokogiriとXPathでスクレイピング
    html = open(SAYING_URL).read
    doc = Nokogiri::HTML.parse(html)
    div = doc.xpath(XPATH)

    # 名言のデータはsayings_dataに格納する
    @sayings_data = []

    # 名言に含まれる名詞のデータはnouns_listに格納する
    @nouns_list = Hash.new { |hash, key| hash[key] = [] }

    # HTMLの解析結果をdiv要素ごとに処理
    div.each_with_index do |div, saying_id|
      dialog = div.xpath('./p').text
      greatman = div.xpath('./h2').text
      saying = Saying.new(saying_id, dialog, greatman)

      @sayings_data << saying

      # 名言から名詞を抽出
      saying_nouns = saying.dialog.parse_noun

      # 名詞データがないときには新しく作る
      # 既にkeyが一致するデータがあればindexの情報を追加
      saying_nouns.each { |noun| @nouns_list[noun].push(saying_id) }
    end
  end

  # 名詞リストと照合して一致した名詞と名言を返す
  def find_and_sample_saying(text)
    # 名詞リストのうち引数textに含まれるものを取ってくる
    # sayin_idは使わないので引数を捨てるためにアンダースコアをつける
    related_nonus = @nouns_list.select { |noun, _saying_ids| text.include?(noun) }

    # 名詞リストに引っかからなかったらnilを返す
    return if related_nonus.empty?

    # 複数の名詞リストに引っかかることがあるのでsampleで一つだけ取ってくる
    noun = related_nonus.keys.sample

    # 一つの名詞に複数の名言が紐付いていることがあるのでsampleで一つだけ取ってくる
    saying_id = related_nonus[noun].sample
    saying = @sayings_data.find { |saying| saying.id == saying_id }

    { noun: noun, saying: saying }
  end
end

ユーザーの投稿と名詞リストを照らしあわせる

次にslack側から渡されたユーザーの投稿に合わせて名言のデータを返すReplyMessageクラスを作ります。
ここで先ほどSayinDaoで生成した名言データおよび名詞リストをつかいます。
投稿内容に名詞リストと一致するものがあれば、該当するインデックス番号に紐付く名言データをひとつ取ってきます。

SayingReplyクラスの役割

initialize

SayingDaoをnewする

saying_replyメソッド

  1. ユーザーの投稿内容を受け取る
  2. 投稿内容と名言リストを突き合わせる(SayingDaoのfind_and_sample_sayingメソッド)
  3. find_and_sample_sayingの返り値(名言データor nil)に合わせて返信メッセージ or nilを返す
# reply_message.rb
require_relative 'saying_dao.rb'

# メッセージを成形
class ReplyMessage

  def initialize
    @saying_dao = SayingDao.new
  end

  # ユーザーの投稿内容にあった返信内容を返す
  def saying_reply(message)
    match_data = @saying_dao.find_and_sample_saying(message)

    # match_dataがnilだったらnilを返す
    return if match_data.nil?

    # match_dataに値があれば返信内容を返す
    <<~TEXT
    「#{match_data[:noun]}」と言えば、こちらの名言をご覧ください。

    >#{match_data[:saying].dialog}
    >
    >-  #{match_data[:saying].greatman}  -
    TEXT
  end
end

名言を投稿するSlackBot

最後に実際にslackの制御を行うbotを作成します。
基本の挙動は こちらの記事 を参考に書きました。

botの挙動

  1. slack上のアクション(誰がどんな投稿をしたか、誰が記入中ステータスか等)をリアルタイムで補足
  2. ユーザーが何かしら投稿した時にReplyMessageのsaying_replyメソッドに渡す
  3. saying_replyメソッドから返ってきた値をslackに投稿する
# bot.rb
require 'eventmachine'
require 'faye/websocket'
require 'http'
require 'json'
require_relative 'reply_message.rb'

SLACK_API_URL = 'https://slack.com/api/rtm.start'.freeze

response = HTTP.post(SLACK_API_URL, params: { token: ENV['SLACK_API_TOKEN'] })
rc = JSON.parse(response.body)
url = rc['url']

EM.run do
  # Web Socketインスタンスの立ち上げ
  ws = Faye::WebSocket::Client.new(url)

  # ReplyMessageのインスタンスを生成
  rm = ReplyMessage.new 

  #  接続が確立した時の処理
  ws.on :open do
    p [:open]
  end

  # RTM APIから情報を受け取った時の処理
  ws.on :message do |event|
    data = JSON.parse(event.data)
    p [:message, data]
    if data['type'] == 'message' && data['user']

      # ユーザーの投稿data['text']をReplyMessageのsaying_replyメソッドに渡す
      reply_message = rm.saying_reply(data['text'])

      if reply_message
        # 返信内容を投稿する
        ws.send({
          type: 'message',
          text: reply_message,
          channel: data['channel']
        }.to_json)
      end
    end
  end

  # 接続が切断した時の処理
  ws.on :close do
    p [:close]
    ws = nil
    EM.stop
  end
end

まとめ

今回このbotを作る上で一番大変だったのは、クラスを役割ごとに分ける作業でした。
普段だと、書きやすいコードからなんとなく書き始めてしまうことが多いので、作り始めてから「この機能は別のクラスにしよう…」とか「このクラス全く要らないのでは…」となってしまいます。
今度からは必要となるクラスや機能を挙げてから、実際のコードを書いていこうと思いました。

最後にお気に入りの名言をひとつ。

「それも、いいじゃないか」は、おもしろい人生のスローガン。
– メーソン・クーリー –

それでは、今日はこの辺で。
読んでいただき、ありがとうございました。

git log –graphでログを見やすくする

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

git logをコンソール上でグラフィカルに表示してくれるオプションが–graphです。

git log --graph

だけでもマージ状況が視覚的に把握できて便利ですが、
–onelineや、–decorate、 –formatオプションと
組み合わせると、さらに見やすくなります。

*   468ea0a [2017-07-01] Merge branch 'branch3' into 'master' @user1
|\
| * 34ed423 [2017-07-01] コミット5 @user3
* |   1d47e56 [2017-07-01] Merge branch 'branch2' into 'master' @user1
|\ \
| |/
|/|
| * 7ed5440 [2017-07-01] コミット4 @user2
| * 2a32810 [2017-07-01] コミット3 @user2
| * c4a27c5 [2017-06-30] コミット2 @user2
| * a8506c1 [2017-06-30] コミット1 @user2
* |   2925ee7 [2017-07-01] Merge branch 'branch1' into 'master' @user1

 
記事画像のような表示の例だと
こんな感じに指定しています。

git log --graph --oneline --decorate=full -20 --date=short --format="%C(yellow)%h%C(reset) %C(magenta)[%ad]%C(reset)%C(auto)%d%C(reset) %s %C(cyan)@%an%C(reset)"

コミットNo. :黄色
日付     :マゼンダ
ブランチ名  :自動
作業者    :シアン
 

【オプション】
–oneline
 一行表示
–decorate=(short|full|no)
 ブランチ名の表示形式
–date=(relative|local|default|iso|rfc|short|raw)
 日付表示
–pretty[=<format>], –format=
 フォーマット指定。%C()は色指定

 
自分の見やすいフォーマットで設定できたら、
aliasの設定をしておきます。
自分はlog –graphを略して&打ちやすさで
git logaにしています。

$ git config --global alias.loga 'log --graph --oneline --decorate=full -20 --date=short --format="%C(yellow)%h%C(reset) %C(magenta)[%ad]%C(reset)%C(auto)%d%C(reset) %s %C(cyan)@%an%C(reset)"'

※gitのバージョンが古い場合、「%C(auto)」の指定はできないようです。
 1.7だとエラーになりました。

 
詳細は
git log –help
で確認できます。

【0秒通勤】ワープについて考える

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

SF的な世界観を持ったアニメ・ゲーム等の多くのコンテンツにおいて、離れた場所へ自由に行き来(または一方通行)する手段が登場しています。今回はこのような離れた場所への移動術、ワープについて考えていきたいと思います。 ※本記事はフィクションを織り交ぜた説明を行っています。ご了承ください。

0秒通勤への道

今回考える、ワープの性質は次の1点のみです。
現状の実在するどの移動手段よりも速く移動できる。

コンテンツに登場するワープと言えば、体から光が出てふわっと消えたり、宇宙船でギューンと移動したり、ドア状の物体を通過するだけで目的地まで行けたり等々、不思議な性質を持ち合わせているものが多く見られますが、今回の記事で考える性質は上の1点だけになります。

ワームホール

早速本題に入りたいと思います。現状、物の速度は光の速度(秒速30万キロ)を超えることが出来ないと言われています。つまり、どんなに速い車やロケットを作っても、必ず上限が来てしまいます。
しかし、その上限値を超えた速さで移動ができる現象があります。それが、ワームホールです。
SF好きな方なら何度も耳にしているかもしれません。多くのSF作品の中でワープの説明として頻繁に用いられる現象です。
ワームホールは、時空と時空を繋ぐトンネルのようなもので、これを自在に扱えれば、通常の移動よりも高速で移動が可能になるのではないかと言われています。
時空と言っても難しいので、イメージとして書き下します。
私たちは普通、道路の上を歩いて目的地まで行きますが、ここにフタが外れた魔法のマンホールがあり、それが一直線にブラジルまで繋がっているようなものを考えてください。道路の上を歩いて進むと地球の表面に縛られるため、一直線にブラジルまで行くことはできません。しかし、魔法のマンホールを使えば、一直線にブラジルまで進むことが出来ます。同じ速度の乗り物を使っても、移動距離が違うため、結果的に速く移動できます。
enter image description here
これを応用して、どこの目的地にも一直線に行けるような魔法のマンホールが出来れば、現状実在するどの移動方法よりも高速な移動が可能になるわけです。

ワームホールは実在するのか

上述のようなワームホールは実在するのでしょうか。
最近、時空のさざ波と言われる「重力波」という現象をとらえることに成功した、とアメリカの研究機関が発表しました。このさざ波を詳細に解析すると、ワームホールがあるかどうか確かめられるようです。
これはイメージとして、水面に生活していると考えてください。水面に何もなければ、遠くから来た波はそのまま私たちのところへやってきます。しかし、水面に邪魔者があると、そこを波が通ろうとしても波の形が変形させられてしまいます。これと同じようなことがこの空間でも起こっていて、この邪魔者がワームホールということです。
現状取得できる信号では、信号の強度が十分ではないため、まだ決定的な証拠は見られていませんが、観測機のグレードアップによって、今後ワームホールを捉えられる日がすぐそこまで迫っているかもしれません。
従って、現状の答えは「現実味を帯びてきた」と言えるのではないでしょうか。

終わりに

SFよりも現実よりなお話をしたつもりですが、当記事はワープや0秒通勤が実現可能だと担保する物ではありません。また、繰り返しになりますが、イメージだけでもお伝えするために、事実にフィクションを織り交ぜた説明を行っております。「ホンマでっか!?」的な姿勢で読んでいただけたら幸いです。
*参考文献*
「Echoes from the Abyss: Evidence for Planck-scale structure at black hole horizons」Jahed Abedi, Hannah Dykaar, Niayesh Afshordi

EXCEL(VBA)から、JSONデータをHTTP送信してみよう。

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

EXCELでマスタデータ等を作成して、直接サーバへリクエストしデータの格納/情報の取得をしたいと思いませんか?
それを実現する簡単な方法をご紹介します。

JSONデータを使用するために

BOXのAPIを使って見よう。(その3)でも紹介していますが、
EXCEL(VBA)でJSONを扱うには、VBA-JSONを使用するのが簡単です。

サンプル JSON

JSONデータ交換フォーマットの定義は、ECMA-404を参照して下さい。

{
  "id" : 1,
  "name" : "John Smith",
  "friend_ids" : [ 10, 20, 30 ],
  "shipTo" : { "name" : "Appirits Inc.",
               "address" : "5F Kyocera-Harajuku Bldg. 6-27-8, Jingumae, Shibuya-ku",
               "city" : "Tokyo",
               "state" : "Japan",
               "zip"   : "150-0001" },
}

VBA で JSON 生成

連想配列 Dictionary と、可変配列 Collection を使って JSONオブジェクトにデータをセットする

 '---------------------------------
 ' リクエストパラメタ生成
 '---------------------------------
 Dim JsonObject As Object
 Set JsonObject = New Dictionary

 JsonObject.Add "id", 1

 JsonObject.Add "name", "John Smith"

 JsonObject.Add "friend_ids", New Collection
 JsonObject("friend_ids").Add 10
 JsonObject("friend_ids").Add 20
 JsonObject("friend_ids").Add 30

 JsonObject.Add "shipTo", New Dictionary
 JsonObject("shipTo").Add "name", "Appirits Inc."
 JsonObject("shipTo").Add "address", "5F Kyocera-Harajuku Bldg. 6-27-8, Jingumae, Shibuya-ku"
 JsonObject("shipTo").Add "city", "Tokyo"
 JsonObject("shipTo").Add "state", "Japan"
 JsonObject("shipTo").Add "zip", "150-0001"

 ' イミディエイトウィンドウで確認(デバック用)
 Debug.Print JsonConverter.ConvertToJson(JsonObject, Whitespace:=2)

イミディエイトウィンドウで確認すると、JSONが生成されている事が確認できますね。
イミディエイトウィンドウで表示したJSON

VBAからHTTP通信

CreateObject(“MSXML2.XMLHTTP”)でIXMLHTTPRequestオブジェクトをを生成して送信するのが簡単な方法です。

'---------------------------------
' リクエスト
'---------------------------------
Dim objHTTP As Object
Set objHTTP = CreateObject("msxml2.xmlhttp")
objHTTP.Open "POST", "http://localhost:8080", False
objHTTP.setRequestHeader "Content-Type", "text/plain"
objHTTP.send JsonConverter.ConvertToJson(JsonObject)

' レスポンスコード(正常)
If objHTTP.status = 200 Then
    MsgBox "正常に終了しました"
End If

実際のHTTP通信内容を proxy ツールで確認

proxyツールfiddler を使った、HTTP通信内容を確認した様子です。

FiddlerでHTTP通信内容を確認

FiddlerでリクエストのPOSTデータが、JSONとなっていることを確認した結果です。
FiddlerでJSON内容確認

まとめ

送信するデータをEXCELから取得する部分は割愛しましたが、簡単にJSONデータが作成出来ましたね。
次回は、VBAで画像などのファイルをアップロードするにはどうすればいいか?
を考えて見ます。

Ruby on Rail 4.2 から 5.1 へ移行した際のメモ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby on Railsのバージョンを 4.2 から 5.1 へ移行したときのメモです。 基本的にRailsガイドの手順通りにやれば問題ありませんが、Turbolinksではまります。

移行の前に

これはRuby on Rails 4.2 から新機能は使わずに5.1に移行したときのメモです。
rubyをとりまく環境の変化はとても速いので、前のバージョンがすぐに Deprecatedになったりします。
ですが、古いバージョンから一気に最新バージョンをあげるのはとても大変です。
日頃からバージョンアップを試みてJenkinsなどのCIで自動テストする環境を整えておきましょう。

環境

移行前

Ruby 2.3.3
Ruby on Rails 4.2.7.1

移行後

Ruby 2.4.1
Ruby on Rails 5.1

大きな変更点

  • rakeコマンドのかわりにrailsコマンドを使うよう変更になった。
  • ActiveModelの親クラスが ActiveRecord::Base から ApplicationRecord に変更になった。
  • ActiveJobの親クラスが ActiveJob::BaseからApplicationJobに変更になった。
  • assetsにyarn, webpacker が正式に導入された。
  • jquery-ujs が rails本体 (ActionView) に取り込まれ、jqueryに依存しなくなった。

おおまかな手順

  1. バージョンアップ用のブランチを切る
  2. Gemfileでロックされているrailsのバージョンを 5.1.2 に変更してbundle update rails
  3. エラーがでるので必要なgemのバージョンをあげたり、不要なgemを消したりして bundle update
  4. bin/rails app:update diffを確認して問題なければ上書きなどしてゆく
  5. ActiveModelなどの親クラスを変更
  6. アセットまわりの変更
  7. (余力があれば)アセットのライブラリを yarn管理に変更

使用しなくてもよくなったgems

  • debugger
  • jquery.turbolinks
  • quiet_assets

Turbolinksの対応

Turbolinks5に対応しているため変更になっています。

  • page:loadturbolinks:loadに変更になった
  • History.back(ブラウザの戻る)などでも呼ばれるようになった

上記の変更から、以下を実施しました。

  • jquery.turbolinksを使っていたがturbolinks5では動作せず使わなくてもよさそうなのでGemfile, application.jsから削除した。
  • Turbolinks.enableProgressBar() は本家に含まれたので削除した。
  • jquery.turbolinksでは $()を使うが、かわりに $(document).on "turbolinks.load", function()に変更した
  • javascript_include_tagdata-turbolinks-track 属性を “reload”に変更した

AdminLTEで表示崩れ

AdminLTEという管理画面テンプレートを使っていますが、リンクでページを遷移した際画面が崩れることがありました。
turbolinks.load内で、$.AdminLTE.layout.fix(); を呼び出すことで解決しました。

ブラウザの戻るボタンで表示崩れ

History API で戻った場合でも turbolinks.load で呼ばれるようになりました。
そのため、二重に表示されることがあります。
戻るボタン押下時にはキャッシュを使って表示しますので、turbolinks:before-cache イベントを使って不要なDOMのクリアなどを行う必要があります。

その他の変更点

ActiveRecord: time型にもタイムゾーンが適用される

案外メジャーになっていませんでした。 time型は従来はタイムゾーンは適用されませんでしたが、Rails5よりタイムゾーンを考慮するようになりました。 何も対策をせずにアップデートするとJSTの場合9時間ずれます。
移行時に、DBを確認してUTCに変換する必要があります。

または、以下のオプションで従来の動きに変更できます。

config.active_record.time_zone_aware_types = [:datetime]

ActiveRecord: uniqは廃止、distinctを使う

ActiveRecord::QueryMethod#uniq が削除されました。
以前はAnimal.uniq のような記述でselect distinct * from animalsを発行してくれましたが、Rails5以降はArrayに変換されます。

同じ挙動にするためには、Animal.distinct を使います。

ActiveModel::Dirty: attribute_was の挙動の変更

モデルオブジェクトの属性を変更すると、変更前の値が xxx_was に保存される便利な機能がありましたが、Rails5以降は取得できなくなりました。xxx_was は xxx と同じ値を返すようです。xxx_changeの最初の要素を使います

animal.name = "Pig"
animal.name_was # => nil
animal.name_change # => [nil, "Pig"]
animal.name_previous_change # => nil
animal.save
animal.name_was # => "Pig"
animal.name_change # => nil
animal.name_previous_change # => [nil, "Pig"]
animal.reload
animal.name_was # => "Pig"
animal.name_change # => nil
animal.name_previous_change # => nil

リソース

Railsガイドのドキュメントや他のリソースを検索するのは当たり前なのではぶきます。
(Railsガイドの日本語版は訳が追いついていないので英語版もあわせて参照してください)

Railsアップグレードガイド(日本語)
A Guide for Upgrading Ruby on Rails

ゲーム制作におけるコンセプトの作り方

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

コンセプトってどんなものを書けばいいのかわからない方にコンセプトの作り方についてまとめてみました。

はじめに

どうもモツ太郎です。
ゲーム業界を志望している方の中には<企画書>の提出が課題として出された方もいると思います。企画書ではそのゲームの一番のウリ<コンセプト>というものを書くことになります。今回はその<コンセプト>についてお話していきたいと思います。
※いつものように個人の考えになります。

失敗するコンセプト

さて、実際にゲームのコンセプトというものを考えた時に皆さんはどういうものを想像するでしょうか?
実際によく使われそうな例を思い付きでひとつ挙げてみたいと思います。

☆動物園の園長に!様々な動物を飼育する育成ゲーム!
(モツ太郎はけ〇のフ〇ンズを応援しています。)

はい、よく使われそうな耳障りのいいコンセプトが出てきました。
一見やりたいことはわかるコンセプトとなっていますが、このコンセプトには大きな問題があります。

それは、<ただのゲームの概要になってしまっている>というところですね。
このようなコンセプトにしてしまうと企画が通って実際に作る際に仕様変更をするとコンセプト変更に直結します。
また、偉い人(企画書を見る人)からするとツッコミどころが多いものになります。
ツッコミ例としては以下
・動物園である理由は?
・様々な動物って何を出すの?
などなど

コンセプトとは

ゲームにおけるコンセプトとは<ユーザーに対してどういう体験(経験)を与えたいか>というところところが重要になってきます。

それを受けて先ほどの例を変えてみたいと思います。

☆普段動物と触れ合えない人に、自宅でできる動物ふれあいゲーム!

こんな感じに変えてみました。
コンセプトからターゲットが明確になりましたね。
しかもゲームを作っている最中に仕様が変わってもコンセプトに関しては影響がないというものになっています。

おわりに

コンセプトについて書いてみましたがいかがだったでしょうか?
今回はコンセプトの内容について簡単にしか書いていないのでまだまだ言いたいことが言いきれていないというところもあります。
その部分に関してはまたどこかで書く機会があったら書いていきたいなと思いますので気長にお待ちください。

【Rails】バリデーションのエラーメッセージにI18nを使うとキャッシュされる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

エラーメッセージをvalidatesメソッドに指定する際、
I18nで国際化した文字列を使用するとキャッシュされてしまうようです。

validates :name, presence: { message: I18n.t('.invalid') }

 
現象として、キャッシュされたものが表示されたり、エラーとなったり、
翻訳されずに英語のままになったりと、挙動が不安定になるケースがありました。

 
バージョン
 rails 4.2
 rails-i18n-4.0.9

 
対策としては、Procオブジェクトを使用すると解消されるようです。

validates :name, presence: { message: Proc.new{ I18n.t('.invalid') } }

lambdaでも同様ですが、
単純にブロックを指定しただけでは解消されません。
以下はどれも同様に解消されます。

validates :name, presence: { message: Proc.new{ I18n.t('.invalid') } }
validates :name, presence: { message: proc{ I18n.t('.invalid') } }
validates :name, presence: { message: lambda{ I18n.t('.invalid') }.call }

 
バリデーションメッセージだけでなく、scopでも同様の現象が発生するようですが、
とりあえず今回はバリデーションの表示のみです。

最近人気な記事