kazasiki's blog

プログラミングとかVRゲームとか

ActiveRecordから理解するgroupの使い方

この記事では主にActiveRecordのgroupメソッドやそれに関連するメソッドをみつつ、仕組みを説明します。あと、ActiveRecordの集計関数ちょっと癖あるよね的な話です。

テーブル作成

まずはじめにこういうテーブルを作ります。

exam_scores

  • 教科(subject) : String
  • 人の名前(name) : String
  • 点数(score) : Integer
bundle exec rails g model exam_scores subject:string name:string score:integer

データ作成

中身のデータは以下のようにします

subject name score
国語 田中 50
社会 田中 70
社会 山田 30
scores = [
  { subject: '国語', name: '田中', score: '50' },
  { subject: '社会', name: '田中', score: '70' },
  { subject: '社会', name: '山田', score: '30' }
]
ExamScore.create! scores

メソッドたち

次に、集計操作に使うメソッドを列挙します。

  • groupを作る条件を指定する
    • group
  • 集計
    • sum
    • average
    • count
  • 集計結果のフィルタリング
    • having

group + sum

まず、groupとsumを指定してみましょう。

subject name score
国語 田中 50
社会 田中 70
社会 山田 30
ExamScore.group(:subject).sum(:score)
=> {"国語"=>50, "社会"=>100}

scoreの合計値をsubject毎に表示できました。しかし、結果が ExamScoreインスタンスではなくハッシュですね?

SQLの出力を見てみましょう。

SELECT SUM("exam_scores"."score") AS sum_score, subject AS subject FROM "exam_scores" GROUP BY "exam_scores"."subject"
-- 50|国語
-- 100|社会

SQLの結果にnameが含まれません。これはexam_scoresテーブルをsubjectでgroup化した時に、nameが特定できないからです。

AcriveRecordは

を原則として設計されています。

これは ActiveRecordパターン という有名なオブジェクト指向設計パターンに由来しています。group + sum を使ったSQLの結果は、そのテーブルの1レコードと等しくなりません。故に、 ExamScore.group(:subject).sum(:score) の結果も ExamScoreインスタンスではなく、只のハッシュとして戻ってきます。

group + average + having

次に、subjectをnameに、sumをaverageに変えてみましょう。

subject name score
国語 田中 50
社会 田中 70
社会 山田 30
ExamScore.group(:name).average(:score)
=> {"山田"=>30, "田中"=>60}
# 実際は数字の部分はBigDecimal型でもどってきますが今回は省略

havingを使ってみましょう。

ExamScore.group(:name).having('avg(score) > 50').average(:score)
=> {"田中"=>60}

山田くんが消えましたね(^-^)
SQLを見てみます。

SELECT AVG("exam_scores"."score") AS average_score, name AS name FROM "exam_scores" GROUP BY "exam_scores"."name" HAVING avg(score) > 50
-- 60.0|田中

結果がハッシュなのは先ほどと同様ですが、今回は2つ注意点が有ります。
1つ目は、havingメソッドが 引数をそのまま文字列としてSQLに入れること です。

例えば、 avg(score)average_score に変えます。

ExamScore.group(:name).having('average_score > 50').average(:score)
=> {"田中"=>60}

とすれば、SQLでも avg(score)average_score に変わります。

SELECT AVG("exam_scores"."score") AS average_score, name AS name FROM "exam_scores" GROUP BY "exam_scores"."name" HAVING average_score > 50
-- 60.0|田中

になります。

2つ目は、havingメソッドを入れる場所は、集計メソッド(sum, average, count)よりも前にする必要があります。 集計メソッドの戻り値はその時点でハッシュにされてしまうからです。

先ほどのrubyコードでhavingメソッドを最後に持ってきてみます。

ExamScore.group(:name).average(:score).having('average_score > 50')

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

NoMethodError: undefined method `having' for #<Hash:0x007f5c837ec778>
    from (irb):11
    from /share/git_dir/simple_app/vendor/bundle/ruby/2.3.0/gems/railties-4.2.3/lib/rails/commands/console.rb:110:in `start'
    from /share/git_dir/simple_app/vendor/bundle/ruby/2.3.0/gems/railties-4.2.3/lib/rails/commands/console.rb:9:in `start'
    from /share/git_dir/simple_app/vendor/bundle/ruby/2.3.0/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:68:in `console'
    from /share/git_dir/simple_app/vendor/bundle/ruby/2.3.0/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
    from /share/git_dir/simple_app/vendor/bundle/ruby/2.3.0/gems/railties-4.2.3/lib/rails/commands.rb:17:in `<top (required)>'
    from bin/rails:8:in `require'
    from bin/rails:8:in `<main>'

¯\_(ツ)_/¯

まとめ

集計関数は ActiveRecordの原則の外にあるので、ActiveRecordからDBに触り始めた人には馴染みにくく、関係するメソッドにも癖が強いです。 ただ、集計関数こそSQLの醍醐味なので、しっかり使えるようになりましょう。

Effective "if-else" ~分岐に関わる達人の思考~

経験豊かなプログラマの皆さんであればif文はご存知だと思います。
この記事では、様々な言語でのif文の機能や書き方の違いに触れつつ、より安全なif文の書き方を導きます。要点は、静的言語でコンパイルエラーになるような書き方は動的言語でもしないほうが良い、もしくは場所を選ぶということです。
言語によって文だったり、式だったり、関数だったりしますが、この記事では文という表現を主に使います。

機能の違い

if文は言語によって様々な文法が存在します。が、ここでは主にその機能の違いに触れます。

基本(分岐)

一番基本的な書き方は以下のようなものだと思います。rubyっぽい書き方です。

if 条件
  条件がtrueだったら何かする
end
if 条件
  trueの場合に実行する文
else
  falseの場合に実行する文
end

初期化

golangで実装されている機能です。http://golang.jp/effective_go#if

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

for文のように、if文の中だけで使う変数の初期化を行います。
これは非常に便利な手法なのですが、他の言語での良い代替手段はほぼありません。Cでよくある書き方は

if(  (fp = fopen( filne_name, "r" )) == NULL ){
  printf( "%sファイルが開けません¥n", filne_name );
  return -1;
}

ですが、あんまり綺麗ではありません。結局、変数(fp)のスコープも限定できません。
if文を全てメソッドに分離させれば同じことができますが、現実的ではありません。コンセプトは理解しておくといいでしょう。for文のiのスコープを限定でないとしたらゾッとしますね。(C言語からは目を背けながら)

戻り値

rubyではif文は正確には式であり、戻り値を返します。条件部分がtrueであれば、trueの場合に実行される部分の最後の文の戻り値が返されます。
それを利用して以下のような記述ができます。

# if式の結果を変数に入れる
result =
  if 条件
    trueの場合に実行する文
  else
    falseの場合に実行する文
  end

また、haskellでも同様に戻り値を返します。haskellは静的型言語であるため、戻り値の型が揃っていなければコンパイルエラーになります。

変数の代入検査

一部の言語においてif式が戻り値を返すことを説明しましたが、戻り値を返さない場合でも同じようなことはできます。例えば、Javaでは以下のようになります。

// if文の結果を変数に入れる
int result;
if (条件) {
  result = trueの場合に実行する文;
} else {
  result = falseの場合に実行する文;
}
System.out.print(result);

Javaで面白いのは、例えば以下の記述はコンパイルエラーになります。

int result;
if (条件) {
  result = trueの場合に実行する文;
} else {
  falseの場合に実行する文;
}
System.out.print(result);

これがコンパイルエラーになるのはelseの場合にresultに値が代入されないからです。このコンパイルエラーを回避する方法は大きく2つあります。
1つはresultの宣言時に初期値を入れることです。これはif文の条件がtrueになるのがあくまでも特殊なケースである場合に有効です。基本的には初期値のままで、特殊な場合にif文の中で値が設定されます。ただ、この方法は破壊的代入と言われ、あまり推奨されません。変数の状態が確認しにくくなるからです。
もう1つは元の通りelseの場合に値を入れる方法です。値は初期値と同じかもしれませんが、代入を複数回行わないため、変数の状態が確認しやすくなります。

条件の判定

if文において何がtrueで何がfalseか、です。私の知る範囲においてよくあるのは、0, null, falseがfalseで、それ以外は全てtrueというものです。golanghaskellではtrue/false以外を返す式を条件に入れるとコンパイルエラーになります。

シンタックスシュガー

if文には様々なシンタックスシュガー(書き換え)がありますが、有名どころはやはり三項演算子と後置if文でしょう。

三項演算子(?演算子

C言語Javaのようなif文が戻り値を返さない言語でも、1行で戻り値の代入までを明示することが出来ます。可読性が下がるという理由で嫌われがちですが、80~100文字程度で収まる場面なら使っていいと思ってます。ワンライナー御用達。

result = 条件 ? trueの場合に実行する文 : falseの場合に実行する文

後置if文

RubyCoffeeScriptにあるシンタックスシュガーです。以下のように書きます。

trueの場合に実行する文 if 条件

片方の条件の処理が長いときにif文を閉じるところまで読まずに済むというメリットが有ります。ネストも減ります。

# before
def do_something 
  if condition
    method0
  else
    method1
    method2
    method3
    method4
    method5
    method6
  end
end

# after
def do_something 
  return method0 if condition
  method1
  method2
  method3
  method4
  method5
  method6
end

テクニック

条件式を変数に入れて持ち回る

条件式を変数に入れて持ち回れば、例えばこんな書き換えが出来ます。
あと、条件式に尤もな名前を付けるのは常に良い習慣です。

# before
def do_something(a, b)
  if a > 0 && b > 0 && a > b
    c = a
    d = b
  else
    c = b
    d = a
  end
  puts "c:#{c}, d:#{d}"
end

# after
def do_something(a, b)
  condition = a > 0 && b > 0 && a > b
  c = condition ? a : b
  d = condition ? b : a
  puts "c:#{c}, d:#{d}"
end

等価演算子の変数を右側に書く

等価演算子の==と、代入の=を書き間違えたときにコンパイルエラーにしてくれるための技法です。C言語ではよく書いてました。
読みにくいので皆しなくなりました。条件式にboolean型しか許されない言語では無用な心配です。
ただ、現在においても==と=を間違えるのは比較的ポピュラーなバグであるというのは覚えておいたほうが良いです。

if (1==a) {
  do_something
}

まとめ

冒頭に書いたとおり、静的言語でコンパイルエラーになるような書き方は動的言語でもしないほうが良い、もしくは場所を選ぶの一言です。 それを学ぶためにも動的言語でプログラムを覚えた人は一度はモダンな静的言語のプログラムを覚えるのは良い刺激になると思います。
あと、if文のtrue/falseの時の実行内容を見比べやすくする意識を持つとよいです。

言い訳

検索などをせずに記憶をもとに書いたのでプログラムが間違ってるかもしれません。勿論動作確認もしてません。わかればいいのだ。

Twitterの報告(通報)を押す前に知ってほしい事

あなたがもしTwitterが公開している報告に関するポリシーを熟読したことがあるのであれば、私から特に言うことは特に有りません。ここから先を読む必要も有りません。ぜひ報告(通報)機能を使ってTwitter上の治安を守る助けをしてください。

さて、この文を読んでいるということはあなたは報告に関するポリシーを読んだことがない、もしくは単にこの先の文に興味があるということでしょう。 著作権・ポルノなどについてはこの記事では触れません。この記事ではもっと微妙なケースについて具体例を取り挙げます。それは「自分にとっては迷惑極まりないユーザ及びツイートだったとしても、それを報告すべきでない」というケースです。

例えば、Twitter経由で執拗なナンパを仕掛けるアカウントがあるとします。実際良く見かけます。このアカウントは何かの機会で知り合った女性に1日4-5件のリプライを送ります。単にツイートに対するリアクションだったり「今度一緒に食事に行こう」という露骨な誘いだったり。半匿名なSNSではありがちなことです。 もし、あなたが女性側であればまずブロック機能を使うでしょう。そこまでは問題有りません。ここで議題にしたいのはこのアカウントをTwitterの運営に報告するかです。

Twitterの2016/06/08現在のポリシーに沿って考えれば、このアカウントは報告すべきではありません。該当する条項がないからです。迷惑なアカウントでありますが、それはTwitterの運営の効力をもって解決すべき問題ではありません。

ブロック機能はユーザ側に閉じた機能です(多分)。自由に使えばいいでしょう。しかし、報告機能は運営がその集計によってなんらかの決定(BANとか)を行います。ならば、報告するかどうかの判断は絶対に運営のポリシーに従うべきです。

Twitterでインプレッションを増やす簡単な(汚い)方法

Twitterでインプレッションを増やす簡単な方法を教えます。 一応1週間弱ほど実験して効果があることは確認済みです。

f:id:kazasiki:20160523103905p:plain

5/18に実験開始して2日間だけ異常に増えてるのか分かるかと思います。
5/20が0になってるのは多分計測エラーです。そのあと2日間も10万インプレと以前に比べればそこそこの大きい数字になってるかと思います。

そもそもTwitterにおけるインプレッションとは?

Twitterにおけるインプレッションとはあなたのツイートが他のユーザに表示された表示された回数です。 普通はTLに表示された回数とほぼ同義になります。フォロワーが多かったりRTが多かったりするとそれに伴って大きくなる数字です。

簡単にインプレッションを増やす方法

結論から述べます。方法は以下の手順です。

  1. RT数が伸びてるツイートを見つける。
  2. そのツイートに対してリプライを送る。

細かいは話は順を追って説明します。

この方法でインプレッションが増える理由

まず、何故この方法でインプレッションが増えるのかを説明します。
Twitterの仕様として、ツイートの詳細情報を確認する際にそのツイートへのリプライを併せて表示するという仕様があります。
この仕様はリプライしたユーザとの関係性(フォロワーかどうか)などは全く関係しません。 つまり、10万人に見られるツイートにリプライを送ればそのリプライも10万人に見られるということになります。
実際は単にツイートを見ることとツイートの詳細情報を表示するのには乖離が有りますが、細かいことはいいんだよ。

RT数が伸びてるツイートの見つけ方

さて、理由がわかったところで細かい方法を説明します。RT数が伸びてるツイートをどうやって見つけるのか、です。
私は以下の様なブックマークを作りました。

https://twitter.com/search?f=tweets&q=lang%3Aja%20min_retweets%3A5000

このURLはツイッターの検索機能を使って以下の条件でツイートを検索します

  • "lang:ja" => 日本語
  • "min_retweets:5000" => 5000回以上RTされた

大抵の場合はこれで十分だと思います。 もっと頑張りたい場合はこちらのTwitterアカウントをチェックすると良いかと思います。

twitter.com

リプライの内容

次はどんなリプライを送るか、です。私は以下のことに気をつけています。

  • スパムっぽくしない
  • 礼儀正しく、送り先に失礼のないようにする
  • あんまり気の利いたことは言わず直感に任せる

上記のことに気をつけていれば邪険にされることは有りません(多分)
具体例としてはこんな感じです。

まとめ

ぶっちゃけ実験的な意味合いが強く、インプレッションが増えていいことがあったかと言われると特に無いです。
プロフィールへのアクセスは倍増しましたが、フォロワーは増えてません。アカウントの性質にも寄るかもしれません。
ただ、Twitterにこういう性質というか抜け穴的な仕様が存在することは認識してたほうがなにかと良いかと思って記事にした次第です。
あと、この性質を利用して業者への広告を貼る悪質なアカウントもやはり存在するようです。

Nike FuelBand SEを2年間使ってみて

Nike FuelBand SE(以下、FuelBand)を購入して早2年が経ちましたので、所見を纏めます。買って1週間くらいのレビューは巷に溢れてますが、2年後のレビューはなかなかないと思います。

f:id:kazasiki:20151211115116p:plain

筆者のNike+ ダッシュボード(2015/12/11当時)

 

Nike FuelBand SEだけでなく、運動を測定するタイプのウェアラブルデバイス全体についての洞察も含むので、購入を検討されている方の参考になれば幸いです。

前提

簡単に、FuelBandの性能や筆者の使用状況などを箇条書きします。

FuelBandの機能

  • 主な機能は運動量(Fuelという独自単位)、歩数、消費カロリーの測定/表示。
  • 時刻を表示できる。
  • 睡眠判定、脈拍計の機能はない。
  • iOS/Android端末から通知を受け取る機能はない。
  • 生活防水。

筆者の状況

  • 筆者はFuelBandをほぼ毎日腕時計の代わりとして装着している。
  • 基本的には毎日入浴時に充電している。
  • 日常的にスポーツはしていない。
  • 運動不足や肥満には特に悩んでない。

わかったこと

充電切れになることは殆ど無い

1日のどこかに充電するタイミングを用意していれば充電切れになることはまず有りません。筆者の場合は入浴するときに外して充電しています。生活防水なので装着したままの入浴も可能ですが、体洗ったりするときに邪魔なので。友人宅に外泊した場合などは充電できませんが、それでも充電が切れたことはほとんど有りません。

まとめると、ウェアラブルデバイスのバッテリーの要件は以下のようになります。

  • 追加充電なしで2日間連続使用可能(欲を言えば3日間)
  • 10~30分程度で満充電できる

逆に、「連続使用5日間可能」とか言われても5日に1回だけ充電みたいな生活習慣を作るほうが大変でしょう。ウェアラブルデバイスは毎日充電、これをベースに多少余裕が有るくらいで十分です。逆に、バッテリーのためにデバイスが大きくなったり重くなったりするほうが損でしょう。

測定結果はほぼ直感通り

簡単に言うと、歩けば数値が増えます。出社日の運動量のほぼ全ては通勤時の最寄り駅への徒歩です。以下、細かい気づきを箇条書きにします。

  • エスカレータやエレベータを使わずに階段を使うのは効果的。
  • 昼はオフィスでお弁当食べるよりも、外に出て飲食店で食べるほうが運動になる。
    • 歩いて外に出るから考えてみれば当たり前。
    • 1日の総量から見ても数値的な差はかなり大きい。
  • 同じ距離を走っても歩いてもそんなに運動量は変わらない。
    • FuelBandの測定方法の問題かもしれない。
  • 太鼓の達人をプレイするとめっちゃ歩数が増える。

数値だけ見ても行動は変えられない

測定結果を見て実際の行動に反映できるかは、個々人の知識や意思に依存します。私はFuelBandをつけて運動する気になったかと言われるとNoです。月に1回位は数値の確認をしていますが、特に何も思うことはありません。

ただ、休日に沢山歩いたりした時に数値をみると嬉しくなるのは確かです。そういう時の話の種にはなります。

おわりに

やはり、”収集した値を専門知識と掛けあわせて具体的なアドバイスをしてくれるサービス”が必要だなぁというのが現時点の感想です。あと、実際はおしゃれな万歩計にしかならないので、お安く済ませたい人は万歩計を買えばいいと思います。

この話題を載せるのにちょうどいいアドベントカレンダーがあったら教えて下さい。

 

RailsのActiveJobからurl_helperを呼ぶ

RailsのActiveJob内から*_urlヘルパーを呼ぶ方法です。

どんなときに使う?

Jobから何かしらのAPIにアクセスして、そのパラメータにcallbackアドレスを設定する場合など

やり方

  1. url_helpersをinclude
  2. default_url_optionsをoverride(これをしないとhost名がわからないよって怒られる)
  3. お好みでBaseJobを作ってそれを継承する形でHogeJobを作成する
# app/jobs/base_job.rb
class BaseJob < ActiveJob::Base
  include Rails.application.routes.url_helpers

  protected

  def default_url_options
    { host: ENV['HOST_NAME'] }
  end
end
# app/jobs/hoge_job.rb
class HogeJob < BaseJob
  queue_as :default

  def perform(*args)
    # something
    # foobar_urlが呼べる
  end
end