ホーム ブログ ページ 32

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でも同様の現象が発生するようですが、
とりあえず今回はバリデーションの表示のみです。

mongodbにおいてよく利用する基本的な操作について

0

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

mongodbにおいてよく利用する基本的な操作について以下に記載致す

DB操作

  1. DBの選択> use <DB名> もしくはMongoのシェル起動前に$ mongo <DB名>
  2. DBの一覧取得> show dbs

コレクション操作

  1. コレクションの一覧取得> show collections
  2. コレクションの作成> db.createCollection(“<コレクション名>”)
  3. インデックス
    3.1. インデックス確認> db.<コレクション名>.getIndexes() 3.2. インデックス追加> db.<コレクション名>.ensureIndex({ <フィールド名>: <値> }) 値)
    • 1: 昇順
    • -1: 降順
  4. データ取得
    4.1. 書式> db.<コレクション名>.find({検索オプション})
  • 結果表示を見やすくなるように調整
    db.<コレクション名>.find().pretty()
  • 制限付加
    db.<コレクション名>.find().limit()
  • 件数
    db.<コレクション名>.find().count()
  • ソート
    db.<コレクション名>.find().sort({<フィールド名>: <値>})
    値)
    • 1: 昇順
    • -1: 降順
  • findの第二引数に表示オプションを指定
    db.<コレクション名>.find({}, {<フィールド名>: <値>})
    値)
    • 0: 非表示(0を指定したもののみ消える)
    • 1: 表示(_idと、1をしていしたもののみ表示)
    • 0と1は混ぜて指定できない
  1. 検索条件 findの第一引数内に指定
  • 文字列 .find({<フィールド名>: “”})
  • 正規表現 .find({<フィールド名>: /aaa/})
  • 比較 .find({<>: {<比較演算子>: <値>})
    • 一致 $eq
    • 不一致 $ne
    • 以下 $lte
    • 以上 $gte
    • 大なり $gt
    • 小なり $lt 例) db.<コレクション名>.find({hoge:{$lt: 3}})

mongoが起動しない場合の対処方法

ホストOSがクラッシュし, 仮想環境の不正終了が発生した場合などに,
仮想環境再起動後、mongoが正常起動しない現象が発生した場合,
以下のコマンドで対処することが可能である.
(発生環境: CentOS 6)

# cd /var/lib/mongo
# rm -f mongod.lock
# mongod --dbpath=/var/log/mongodb --repair
# service mongod start

ActiveRecordのpluckを用いた処理の高速化に関する調査

0

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

大量のレコードに対してデータ処理を行う場合, ActiveRecordのpluckを使用することが有効な場合が存在する. 本記事では、処理速度向上に関する有効性の確認を行う.

処理内容

テーブル内の全レコードに対して、
現在レコードが所持しているパラメータを利用して、更新をかける処理

  • レコード:50万件
  • パラメータ:25種類

動作比較

1. レコード1件ごとに更新をかける(比較用)

比較用に1件ごとに更新をかける処理を行ってみたところ、
処理に時間がかかり実行が終わらないため、問題外であった

2. find_each

  • find_eachでレコードを取得して、パラメータごとのリストを作成
  • リストのパラメータごとに、update_allで更新をかける

処理の所要時間およそ10分

1SQL自体のログに出力されるSQL実行時間,
発行されるSQLの構文への explain の実行でのインデックスの適用具合自体は問題が無いように見られたが,
ActiveRecordオブジェクト生成にかかるコストが処理速度に影響を与えている可能性が考えられる.

3. pluck

  • pluckで、パラメータごとのリスト作成に必要なカラムの値を取得
  • リストのパラメータごとに、update_allで更新をかける

処理の所要時間およそ30秒

ActiveRecordオブジェクト生成を防ぐことにより,
同一のリスト作成処理において大幅な処理速度向上が見込まれた.

まとめ

上記のことから,
pluckの返却値はArrayでの取得となるため, ActiveRecordのメソッド等は使用できなくなるが,
取得値のみで処理が可能な場合など利用可能な場面においては,
pluckを使用することにより, 処理速度向上を行うことができることを確認することができた.

UnityEditor上で複数のゲームオブジェクトをプレハブ化する方法

0

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

はじめに

Unityを使っていて、複数のゲームオブジェクトを同時にプレハブ化したい時がありました。Unityではひとつずつしかプレハブ化できないようになっています。それでは不便なのでどうにかできないか調べ、作ってみました。

新しくウィンドウを作る

では、新しくウィンドウを作ってみましょう。

Assetsフォルダの中に、Editorと言う名のフォルダを作りましょう。基本的にエディタ拡張関連のスクリプトはこの中に保存しておきます。

次に、そのEditorフォルダの中に新しくクラスを作りましょう。今回はExampleという名前のクラスを作ります。

注意点として、新しくUnity上でウィンドウを開きたいときは、必ずEditorWindowクラスを継承しなければいけません。

では、Openというメソッドを作り、そこにEditorWindow.GetWindow()と書きましょう。これで新しくウィンドウを作る準備は終わりました。ですが、実際に動かしてみると、どこにも表示させません。
では次に、新しいウィンドウを表示させるために、それを表示させるメニューを作ります。
下記のソースコードのように少し特殊な書き方になります。その下にあるopenメソッドは必ずstaticでなければなりません。
またOnGUIの中にウィンドウ内のレイアウトなどを記述します。
これでウィンドウが開けるようになりました。

public class Example : EditorWindow {
    //メニューに項目追加
    [MenuItem("Example/新しくウィンドウを出します")]
    static void open() {
        EditorWindow.GetWindow<Example> ("Example");
    }

    void OnGUI() {
        EditorGUILayout.LabelField ("ようこそ");
    }
}

実行結果が以下のようになります。

enter image description here

複数プレハブ化

では、複数プレハブ化するためにドラッグアンドドロップできるウィンドウを作りましょう。

UnityがDragAndDropと言うUnityEditor上で行われるドラッグ&ドロップを操作するクラスを用意してくれているのでこれを使います。

先ほど作ったOnGUIメソッドの中にソースコードを書き足していきます。
まず、ドロップするエリアを追加するコードを書きます。
Event.currentで現在のUnityが処理中のEventが取れます。
そのEventがドロップエリア内でドロップしているなら、DragAndDrop.objectReferencesでドラッグしているオブジェクトすべてをプレハブ化する処理になっています。
OnGUIの中に、以下のソースコードを書きます。

private string saveFolderPath = "Assets/Prefab/";

void OnGUI() {
    var dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true));
    GUI.Box(dropArea, "drop");
    var evt = Event.current;

    switch(evt.type) {
        case EventType.DragPerform:
            if(!dropArea.Contains(evt.mousePosition)) break;
            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
            DragAndDrop.AcceptDrag();
            break;

        case EventType.DragExited:
            foreach(GameObject go in DragAndDrop.objectReferences) {
                Debug.Log(go);
                DropList.Add(go);
            }

            CreatePrefabs();
            Event.current.Use();
            break;
    }
}

//プレハブ化する関数
void CreatePrefabs() {
    foreach(GameObject go in DropList) {
        if(DropList == null) {
            return;
        }
        string prefab = saveFolderPath + go.name +".prefab";
        if(go.GetComponent<Image>() != null) {
            CreatePrefabImage(prefab, go);
        } else {
            PrefabUtility.CreatePrefab(prefab, go);
        }
    }
    DropList.Clear();
}

まとめ

今回は、複数のオブジェクトをプレハブ化する機能を実装しましたが、このエディタ拡張を使えば、自分の好きなようにカスタマイズすることができるようになります。
そうやって、Unityを自分好みに染めていってください

今回この機能を作るにあたっていくつか参考にさせていただいたサイトのリンクを載せておきます。
【エディタ拡張徹底解説】初級編①:ウィンドウを自作してみよう【Unity】
エディター拡張入門

rails で dbテーブルを truncate する

0

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

ruby からDBを操作するとき activerecord に頼らずにはいられません。insert, update, delete などは当然のように使っていますが truncate したいときはどうしたらよいでしょう。何故かよく忘れちゃうのでメモ

truncate するメソッドはモデルクラス用には用意されていませんでした。

ですので execute で直接 sql 文を実行します。

ActiveRecord::Base.connection.execute("TRUNCATE TABLE テーブル名;")

クラスメソッドとして登録してしまうときのサンプル

class TransactionTable < ActiveRecord::Base
  class << self
    def truncate
      connection.execute "TRUNCATE TABLE #{table_name};"
    end
  end
end

モデルには記載しないでトランザクションデータだけ削除するスクリプトを作っておくと安全かも

TRANSACTION_TABLES=TransactionTable, NotMasterTable
def clear_transaction
  TRANSACTION_TABLES.each do |transaction_model|
    ActiveRecord.execute "TRUNCATE TABLE #{transaction_model.table_name};"
  end
end

UnityでゲームのUI作りたい! 〜 物並べ編②

0

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

17新卒エンジニアがUnity関係で学んだことの備忘録を書きます。

はじめに

前回の記事では、Unityでシーンを作成し、キャンバス上に同じ形のボタンをVerticalLayoutGroupというコンポーネント(機能)を用いて自動配置で並べるところまで実装できました。
しかし、VerticalLayoutGroupで並べたボタンが画面外にはみ出してしまっています。

enter image description here

(前回の記事はこちら→UnityでゲームのUI作りたい! 〜 物並べ編①)

今回は、画像のようにはみ出してしまったボタン群を、スクロールしてクリック出来るようにしてみましょう。

準備

スクロールするUIを作るためには、大きく分けて
・スクロール対象となる領域
・対象を表示する領域

の二つが必要になります。

例として新幹線の窓から外の風景を眺める状況を挙げると、外の流れていく風景が「スクロール対象となる領域」にあたり、新幹線の窓が「対象を表示する領域」にあたります。
(この場合、実際に動いてるのはスクロール対象の風景じゃなくて新幹線ですが)

前回の時点で、スクロール対象となる領域の作成とボタンの配置が完了しているため、今回はまず対象を表示する領域を作成します。

enter image description here

さて、唐突ですが水色の部分が表示領域となります。
水色の領域の外にあるボタンは、たとえキャンバス上に存在していてもスクロールするまで見えないようにする必要があります。
その機能を追加する前に、この水色の表示領域の作り方をご紹介します。

前回はCanvas直下にScrollAreaというスクロール領域を作成しましたが、その間に表示領域となるオブジェクトを作成します。(本記事ではViewAreaとします)
親子関係の間に新たにオブジェクトを挟み込むため、まずCanvas直下にCreateでViewAreaを作成し、ScrollAreaをドラッグしてViewAreaの子オブジェクトにします。

enter image description here

ViewAreaを左側に寄せたかったので、PosXを-240にしています。
(前回は-160でしたが、さらに左側に調整しました。)

enter image description here

これで表示領域自体は作成できましたが、表示領域の外側にあるボタンが見えてしまっているので、自分の領域内だけ見えるようにする必要があります。
これを実現するためのコンポーネントが、 Mask です。
厳密には、マスク処理を行うコンポーネントは Mask と RectMask2D の2種類があります。

Mask

Maskコンポーネントを持っている親オブジェクトの子オブジェクトの形を、
親オブジェクトの形に制限する。
子オブジェクトが親オブジェクトからはみ出す場合、はみ出した部分が表示されなくなる。
マスクの形はImageコンポーネントに依存するため、画像次第でどんな形のマスクも作成可能。
(※画像に透過箇所がある場合、アンチエイリアスが効かないため境目が汚くなってしまいます。
綺麗な透過グラデーションを作る場合は、Mask機能を自前で実装する必要があります。)

RectMask2D

機能そのものはMaskコンポーネントと同じだが、
形が四角形(RectTransform依存)に制限される。
ただし、描画コストが少ないため、パフォーマンスはMaskより良い。

今回の表示領域は四角形なので、無駄の少ない RectMask2D を使用します。

enter image description here

RectMask2Dは、AddComponentするだけで自動的にそのオブジェクトの RectTransform を参照してマスク処理を行ってくれます。

さて、マスク処理を適用し、表示領域内のみボタンが見えるようになりました。

enter image description here

これでスクロール領域を作る準備が出来ました。
この勢いでスクロール機能を追加していきましょう。

スクロールの実装

早速ですが、スクロール機能は ScrollRect というコンポーネントで実現出来ます。
このScrollRectを、スクロールの対象となる領域(=本記事ではScrollArea)にAddします。

enter image description here

設定項目がたくさんありますね。一つ一つ見ていきましょう。

Content
 スクロール対象オブジェクトのRectTransformをアタッチします。
Horizontal 、 Vertical
 スクロールの方向を水平か垂直か決められます。
 両方にチェックを入れると上下左右にスクロール可能になります。
MovementType
 ・Unrestricted
  無制限にスクロール出来ます。
 ・Elastic
  マウス等のドラッグによりスクロール領域の端が表示領域の内側まで
  引っ張られた場合、ドラッグをやめることで表示領域の端と
  スクロール領域の端が一致するように、引っ張られたゴムが
  元に戻るような動作で自動的に戻ります。
 ・Clamped
  スクロールに制限を付け、スクロール領域の端が
  表示領域の内側に来ることがなくなります。
 ・Elasticity
  Elasticを選択した場合のみ設定可能。戻る際の弾力を設定します。
Inertia
 慣性の有無です。慣性がある場合、スクロールをやめても少しだけ
 慣性によってスクロール領域が動きます。
 ・DecelerationRate
  慣性の強さを0〜1の値で設定します。
ScrollSensitivity
 マウスのホイールや指によるドラッグに対する感度を設定します。
Viewport
 表示領域オブジェクトのRectTransformをアタッチします。
 ここでアタッチしたオブジェクトに基づいて、
 MovementTypeのElastic、Clampedが動作します。
HorizontalScrollbar 、 VerticalScrollbar
 スクロールバーがある場合、水平・垂直それぞれここにアタッチします。
OnValueChanged (Vector2)
 スクロールが行われ、位置が変わったときに呼び出されるイベントを設定できます。

これらの設定を、本記事の実装に合わせて行ったものがこちらになります。

enter image description here

スクロール対象領域をScrollAreaに、表示領域をViewAreaにそれぞれ設定し、
垂直方向のみスクロールさせたいのでHorizontalのチェックを外しています。
また、Elasticとの選択は好みの問題な気がしますが、MovementTypeをClampedにすることで見せる必要の無い部分(スクロール領域の外側) を見られないようにしました。

スクロールバー以外の設定が出来たので、とりあえず実行して動作を確認してみましょう。

enter image description here

ボタンが全て同じなのでピンと来ませんが、いい感じにスクロール出来ていますね。
MovementType:Clampedにより、必要以上にスクロールさせてもスクロール領域の端までしかスクロールしないようになっています。

スクロールバーの追加

次は、無くてもいいのですがあった方がスクロール領域っぽさが出るため、スクロールバーを追加します。
スクロールバーを実装するためのコンポーネントはScrollbarです。そのまんまですね。
スクロールバーはScrollbarコンポーネントとImageコンポーネントを自力で組み合わせて作成することも出来ますが、実はUnityのUI機能にそれらが最初から準備されています。

ViewAreaの上で右クリックし、UI -> Scrollbar を選ぶと、ScrollAreaと同じ階層にScrollbarが作成されます。

enter image description here

Scrollbarの子オブジェクトにはSliding Areaがあり、さらにその子にHandleがあります。
どれもその名の通りで、Sliding Areaはスクロールバーの可動領域、Handleはバーの掴む部分です。

enter image description here

Scrollbarコンポーネントにもたくさんの設定事項があります。
案の定、一つ一つ見ていきます。

Interactable
 スクロールバーに対して入力を受け付けるかどうかの設定です。
 Inspectorで設定することはほとんど無いと思いますが、
 ゲーム中の特定の時だけスクロール出来なくさせたい時など、
 スクリプトから設定することで効果を発揮します。
Transition
 スクロールバーの状態に応じて、バーの色や画像などを個別に設定出来ます。
 今回は ColorTint の場合のみ説明します。(状態に応じて色の遷移を行う)
 ・TargetGraphic
  遷移対象となるグラフィックです。デフォルトではHandleが設定されています。
 ・NormalColor 〜 DisabledColor
  それぞれの状態の時の色を設定します。
  上から順に、通常時、ハイライト時、押下時、無効時の色を設定出来ます。
 ・ColorMultiplier
  上記設定の明るさに倍率を設定します。暗い画像を用いる際に使うと効果的です。
 ・FadeDuration
  遷移にかかる時間を設定します。
Navigation
実行中、方向キーによってオブジェクトに対するフォーカスを切り替える際の設定です。
今回は使用しないため無視して構わないと思います。
HandleRect
HandleのRectTransformを設定します。
Direction
スクロールバーの方向を設定します。
垂直方向のスクロールならBottom To Top
水平方向ならLeft To Rightが一般的だと思われます。
Value
Handleの(初期)位置を0〜1の値により設定します。
Size
Handleのサイズを設定します。
NumberOfSteps
Handleの移動の際、段階を設けることが出来ます。
0、1なら滑らかに動き、それ以上の数の場合、数に応じて移動の段階が決定されます。
(例:3に設定した場合、初期位置、真ん中、終点の3箇所にしかHandleが移動しない)
OnValueChanged (Single)
Handleが移動した際に呼び出されるイベントを設定できます。

これらを本記事の目的に合わせて設定してみます。
…とはいえ、特に拘りがあるわけではないので、DirectionをBottom To Topに設定するだけにしておきます。
もちろん、このままではスクロールバーの位置がおかしいので、座標やサイズの調整は行います。

enter image description here

enter image description here

高さをViewAreaと同じ400に設定し、位置を右端に持ってきました。
ここで、少し話が逸れますが、UIを作る際に大事なポイントをご紹介します。


上の画像をよく見ると、今までの画像のRectTransformと違うところがあると思います。
そうです、前回の記事でチラっと触れた、Anchorの設定がされています。
このスクロールバーがどんな画面サイズでもViewAreaの右端に位置するようにするため設定したのです。
Anchorとは、親オブジェクトに対する中央座標のことでした。
つまりこのスクロールバーは、ViewAreaの右端中央を中心座標として座標を設定するようにしてあるのです。PosXもPosYも0になっていますが、中心座標(右端)=自分の座標 にしたいので、このようにしてあります。
ただし、Anchorを設定して座標を両方0にしただけでは、望む結果は得られません。
きっと、以下の画像のようになると思います。

enter image description here

スクロールバーが半分はみ出てしまっていますね。
実は先ほどのRectTransformには、もう一点、手を加えてありました。
それはPivotです。
Pivotは、オブジェクトの座標の起点のようなものだと思ってください。
Pivotは0〜1の値でX、Y座標それぞれに設定でき、デフォルトは0.5です。
Xの場合、0が左、1が右。Yの場合、0が下、1が上になります。
デフォルトのままの場合、Xの起点が0.5、つまり真ん中に設定されているので、スクロールバーのオブジェクトは自身のX座標の起点(=真ん中)と中心座標を合わせようとします。
すると、先ほどの画像のように右側半分がはみ出してしまうのです。
PivotのXを1(=右端)にすることで、右端が起点となるため、オブジェクトは右端と中心座標を合わせることになり、結果として少しもはみ出さずに表示されるのです。

このようにオブジェクトの各座標の起点 Pivotと、オブジェクトの中心座標Anchorの設定を行い、PosX、PosYを限りなく0に近づけることで、画面サイズが変動しても親オブジェクトとの位置関係を保つことが可能になります。


さて、大事な話とはいえ話が逸れてしまいましたが、これで任意の位置にスクロールバーを作成出来ました。
早速、実行してスクロールさせてみましょう。

enter image description here

そりゃ動きませんよね
当然です、ScrollRectの方に、このスクロールバーを設定していないのです。

というわけで、ScrollRectにScrollbarオブジェクトを設定してみます。

enter image description here

今回は垂直方向のスクロールなので、VerticalScrollbarのみ設定します。
すると、Visibilityという項目が出現します。これは、スクロールバーの表示設定です。

Permanent
 どんな時でもスクロールバーを表示します。
AutoHide
 (今回の例で言えば)中身のボタンの数が少ないなど、
 スクロールの必要が無い時にスクロールバーを非表示にします。
AutoHideAndExpandViewPort
 AutoHideの機能に加え、スクロールの必要が無い時に
 表示領域を拡大して表示します。

今回はAutoHideを選びました。
実際のゲームでも、スクロールバーはあるけどスクロールの必要が無いなんてUIはなんだか変な感じがしますよね。(※個人の感想です)

さて、これでようやくスクロールバーが真の力を発揮できます。

enter image description here

うまくいきましたね。
ちなみに、ScrollAreaのAnchorをcenter・top(上端真ん中)、Pivot(Y)を1にして、PosYを0にすることで、ScrollAreaのスクロール初期位置を一番上にしてあります。
(これを設定しないと、スクロールが真ん中から始まると思います)

まとめ

今回も長くなってしまいましたが、無事にスクロール機能を実装することが出来ました。
少しだけ、おさらいをしましょう。

・マスク処理はMaskまたはRectMask2Dコンポーネント
・スクロール領域にはScrollRectコンポーネント
・スクロールバーにはScrollbarコンポーネント
AnchorPivotを適切に設定して表示ずれを防ぎましょう

次回は、少し話題の違った記事を書きたいと思います。

それでは、長々とお付合いいただきありがとうございました。

Ruby/Rails に潜む罠 (find_by編)

0

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

ActiveRecord::Baseのクラスメソッドの中で最も基本的なメソッドの内の1つである find_by 。Railsを使う案件では頻繁にソースコードの中に現れるだろう。 この至極基本的なメソッドに、実は罠が潜んでいて、稀な条件下では発動する可能性がある。 (条件がレアな為、ほとんど発動しないけれど)

find_by の基本的な使い方

モデル(モデルインスタンスではない)をレシーバにして、引数に条件をハッシュまたはSQL文字列をセット。
返り値はモデルインスタンスとなる。

例1

Artist.find_by(name: 'The Beatles')
# SELECT `artists`.* FROM `artists` WHERE `artists`.`name` = 'The Beatles' LIMIT 1

例2

Artist.find_by("name = 'The Beatles'")
# SELECT `artists`.* FROM `artists` WHERE (name = 'The Beatles') LIMIT 1

罠が発動する条件

モデルに対するテーブルが動的に変化する場合

class Artist < ActiveRecord::Base
  self.table_name = :artists_1 # デフォルトの参照テーブル
end
# 参照テーブルを切り替えるクラス
class TableSwitcher
  def self.exec
    if Artist.table_name == :artists_1
      Artist.table_name = :artists_2
    else
      Artist.table_name = :artists_1
    end
  end
end

この条件は、Railsの規約には沿っていない。つまりRailsの規約に全て沿っていれば、本記事の内容は気にする必要はない。
大半の案件はRailsの規約には沿うだろうが、大量データ更新の為、2つのテーブルを、TRUNCATE+INSERTしながら交互に参照を切り替えていく方針を取っている場合、この問題に直面する事になる。

罠が発動する条件下でfind_byを使うとどうなるか

Artist.table_name # => artists_1
Artist.find_by(name: 'The Beatles')
# SELECT `artists_1`.* FROM `artists_1` WHERE `artists_1`.`name` = 'The Beatles' LIMIT 1

TableSwitcher.exec # 参照テーブル切り替え

Artist.table_name # => artists_2
Artist.find_by(name: 'The Beatles')
# SELECT `artists_1`.* FROM `artists_1` WHERE `artists_1`.`name` = 'The Beatles' LIMIT 1

参照テーブルは切り替わっているはずなのに発行されるSQL中の参照しているテーブル名が切り替わっていない!
これでは、(テーブルを切り替えて)データを新しても、実際は参照するデータが古いままになってしまう可能性がある。

原因

デフォルトのfind_byメソッドは発行したSQLをキャッシュする。(ActiveRecord::StatementCache)
よって、参照テーブルを切り替えた後でも、切替前のキャッシュが残って、切替前のテーブルが参照されてしまう。

対処方法

罠が発動する条件に該当するモデルでは、デフォルトのfind_byが使用されないよう、クラス内でオーバーライドし、where+firstに書き換える。
whereメソッドはキャッシュされないので、この対処が可能となる。

class Artist < ActiveRecord::Base
  self.table_name = :artists_1 # デフォルトの参照テーブル

  class << self
    def find_by(*args)
      where(*args).first
    rescue
      super # 例外処理はスーパークラスに任せる
    end
  end
end

注意点

rubocop を導入している場合は、「where+first は find_by と書き直せ」という警告が出る。
意図的にwherefirstに分けている訳なので、該当部分だけ以下のようにrubocopチェックの対象外とする必要がある。

def find_by(*args)
  # rubocop:disable Rails/FindBy
  where(*args).first
rescue
  super
end

【Rails】gem ‘global’の使い方【global】

0

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

環境ごとに値を設定する場合よくgem ‘config’が使われますが、個人的にgem ‘global’の方が使いやすいと思うので紹介します。

gem ‘config’との違い

gem ‘config’との大きな違いは.ymlファイルの持ち方です。

gem ‘config’

config/settings.yml
config/settings/development.yml
config/settings/production.yml
config/settings/test.yml

config/settings.yml

web_domain: localhost
api_domain: api.local

config/settings/production.yml

web_domain: web.com
api_domain: api.com

gem ‘config’は環境ごとに.ymlを持つのに対し、gem ‘global’は機能ごとや値の種類ごとにファイルを持つ形になります。

gem ‘global’

config/global/domains.yml

default:
  web: localhost
  api: api.local

development:
  web: localhost
  api: api.local

production:
  web: web.com
  api: api.com

test:
  web: test.com
  api: api.test.com

導入方法

1.Gemfileにgem ‘global’の追記

gem 'global'

2.config/initializers/global.rbの作成

Global.configure do |config|
  config.environment = Rails.env.to_s
  config.config_directory = Rails.root.join('config/global').to_s
end

使い方

基本

> Rails.env
=> "development"

> Global.domains
=> { "api" => "api.local", "web" => "localhost" }

> Global.domains.api
=>"api.local"
> Rails.env
=> "production"

> Global.domains
=> { "api" => "api.com", "web" => "web.com" }

> Global.domains.api
=>"api.com"

名前空間付き

config/global/name_space/domains.yml

> Global.name_space.domains
=> { "api" => "api.com", "web" => "web.com" }

> Global.name_space.domains.api
=> "api.com"

ネストを入れる

config/global/domains.yml

default:
  nest:
    web: localhost
    api: api.local

development:
  nest:
    web: localhost
    api: api.local

production:
  nest:
    web: web.com
    api: api.com

test:
  nest:
    web: test.com
    api: api.test.com
> Global.domains.nest
=> { "api" => "api.com", "web" => "web.com" }

> Global.domains.nest.api
=> "api.com"

ERB

config/global/domains.yml

default:
  web: localhost<%= 1 + 2 %>
  api: api.local
> Global.domains.web
=> "localhost3"

リロード

> Global.reload!

他にもjavascriptで使えたりするのでぜひ使ってみてください。

キーフレームアニメーション、絵コンテ制作

0

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

はじめに

この記事はspritestudioを使用する前提で書いております。
他のソフトには対応できない部分もありますのでご了承ください。

こんにちは、2Dモーションを勉強中のなすちゃです。
今回も自身の復習用にまとめていきます。

絵コンテとは

アニメーションとしてものを動かすことに関して必要になってくるのが絵コンテになります。
映像物を作る前にイラストなどで1つのカットを説明するものになります。

いきなり物体を動かすのではなく「こんな動きにしたい」「ここのシーンは尺を長くする」など設計図を組み立てていきます。
漫画のネームみたいなものですね。

自身で製作したものをわかりやすくまとめるものとして大切ですが、仕事として通す際には様々な人とチームとして製作します。食い違いや説明不足にならないようにする役割でもあります。

案出し

コンテを作る前に、このモーションはどの場面をイメージしているのかおおまかなプロットを考えるとやりやすいです。

たとえば、キャラの走るモーションを考えてみます。
走るといっても場面により演出が変わっていき様々なパターンが生まれます

・どんな場面で走っているのか
・喜怒哀楽が含まれるのか
・キャラの個性

など、基礎にある世界観・キャラクターの設定をもとに感情が含まれる動きを考えます。
今回は女の子のキャラクターを使用していきます。

・道を走るなど移動する場面をイメージ
・軽やかに走り、余裕のある表情
・長いスカートを身につけているのでフワッと揺れるように

ざっと思いついたネタを出していき、そこから組み合わせていくのも面白いと思います。

いざ、コンテを製作します。

キャラクターはキーフレームを打てるよう手足・頭部・胴体など各パーツに分かれたテクスチャを使用していますが、相手に伝われば手描きのスケッチでも棒人間などでも構いません。
各製作現場に沿ったスタイルで行いましょう。

プロットに沿ってキャラクターを動かしていきますが、
実際にコマ切をしていると当初のイメージに追加したい要素やここはこうした方が可愛くなるのでは…!というアイディアも出てきます。

enter image description here

この場合も、より個性が表現出来るように表情差分の追加をしました。
瞬きや笑う要素一つ加えるだけでも見栄えが変わっていきます。

ループで走っているように見せたいため、始まりと終わりに同じポーズを入れ後半に走りながらふと笑う要素を組みました。

こういったコンテから考えていくとモーションが制作しやすくなります。
自分がどういう動きをさせたいか、楽しんで案をだしていくことが大切だと思います。

簡単デザイン。ThemeRollerを使う

0

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

Webページに欠かせないボタン、フォーム、セレクトメニュー…。 デフォルトのものを使うと味気ないけれど、サイトの雰囲気にあったものを0から作るのはなかなか難しい… そんなときの強い味方。そんな「ThemeRoller」について紹介します。

http://jqueryui.com/themeroller/

上記URLにアクセスすると、アコーディオンメニューやボタン、チェックボックスなどのサンプルが並んでいます。

enter image description here
▲ボタンやオートコンプリートは、実際に押したり入力したり出来ます。

左にあるツールバーで、フォントや色合いを調節できます。
調節したものは右のサンプルで即確認でき、分かりやすいです。

enter image description here
▲背景のテクスチャなども設定可能。

基本的には、デフォルトのデザインをこのツールバーで調整し、
理想のデザインに持っていくのですが、
「デフォルトのデザインが理想と離れ過ぎていて調整が面倒…」という場合もあります。

そんなときはツールバーの「Gallery」タブをクリック。
ThemeRollerにあらかじめ用意されたテーマがずらりと表示されます。
それぞれのテーマの横にある「Edit」をクリックすると、そこから調整が出来ます。
自分の理想イメージに近いテーマから、調整していきましょう。

enter image description here
▲現時点で25種類のテーマが用意されています。もちろんこれをそのまま使うのもアリ。

調整が出来たら、「Download theme」をクリック。
作ったスタイルシートをまるまるダウンロードできます。
ダウンロードしたスタイルシートをWEBページで読み込むようにすれば完成です。


ThemeRollerではGUIで各フォームやボタン等のスタイル調整が簡単に出来ます。
ただし、ボタン間のマージンや、枠線の太さなどはサポートされていないので、
細かい部分は自分でCSSを改修する必要がありますが、
大まかにデザインをするのには便利だと思います。

最近人気な記事