Slackでボットを5秒以内に作れるツールを作った
はじめに
最近エンジニア界隈で話題沸騰中のSlackですが、IRCに比べてBOTを作る際に手間がかかります。自称クソボットクリエイターの私にとってはこれは死活問題です。
そこでボットを超簡単に量産できるプロダクトを開発して公開したのでご紹介いたします。
BotHeaven (ぼっとてんごく)
Slackボットを大量生産できるプロダクトです。いわばボットファームです。
SlackのOAuthでBotHeaven*1にログインし、Javascriptで数行のコードを書くだけでSlackボットを簡単に生成できます。
HTTP通信機能や、ボット毎のストレージなどを持てることが特徴です。
デモ
このような感じでボットを実装します。
するとこのようなSlackボットが生まれます。
仕組み
Rails + V8 + Slack
インストール方法
VPS等はもちろん、Herokuにもデプロイして使えるような設計になっています。
システムにRuby(2.0.0以上)とbundlerがインストールされていることを前提に解説します。
1. 本体をデプロイする
下記のリポジトリからBotHeaven本体を入手し、実行するマシンの任意のディレクトリに配置します。
github.com
2. 環境変数を設定する
下記の環境変数をセットします。
SLACK_TEAM_ID=SlackのチームID SLACK_APP_ID=SlackのアプリケーションID SLACK_APP_SECRET=Slackのアプリケーションキー SLACK_BOT_NAME=Slackのボット名 SLACK_BOT_TOKEN=Slackのボットトークン SECRET_KEY_BASE=任意の暗号化キー RAILS_SERVE_STATIC_FILES=1 # Herokuなどにデプロイする場合はセット
SlackのアプリケーションIDとキー
下記のページからアプリケーションを生成して取得できます。
アプリケーション生成時、OAuthのリダイレクトURLに `http://{ホスト}/auth/slack/callback` を設定してください。
任意の暗号化キー
Railsが暗号化などに使用するキーです。SHA256で適当に値を生成するなどして設定してください。
3. Gemをインストールする
Gemをインストールします。
bundle install --path vendor/bundle
4. DBを初期化する
マイグレーションコマンドを実行してDBを初期化します。
デフォルトではsqlite3で動作します。herokuで動かす場合はページ下部の データベースに関して の項目を確認してください。
bundle exec rake db:migrate RAILS_ENV=production
5. 実行する
あとは実行するだけです。
bundle exec rails s -e production
以上がインストールと実行手順になります。
もしわからないことがあればコメント欄やgithubのissueでお気軽にご質問ください。
使い方
インストールが終わったら、SlackOAuthでログインしてボット作成ボタンをクリックするとサンプルコード付きのボットが生成されます。*2
その他
データベースに関して
特に指定しない場合はsqlite3で動きます、heroku等で動かす場合は下記の環境変数を追加することでpostgresqlを使用することができます。
ADAPTER=pg DATABASE_URL=postgresqlのデータベースURL
その他のデータベースを利用する場合はActiveRecord用のアダプターをGemfileに追加してdatabase.ymlを書き換えてください。次のリリースでは上記のような環境変数でmysqlも使えるようにしようと思います。
レールから外れないためのActiveRecordリファレンス
はじめに
RailsのActiveRecordはとても優秀なORMですが、使い方を理解せずに生のSQLを書いてしまうと、DBアダプターを変更した時に正しく動作することが保証されません。
今回はActiveRecordのレールから外れてしまいそうになった時、護輪軌条となるように、過去のプロダクト開発で使用したクエリの生成方法についてまとめてみました。
前提条件
ショッピングサイトのユーザー情報と購入情報を格納する下記のようDBテーブルとクラスをイメージしてください。
ユーザー情報
usersテーブル
id | ユーザID |
---|---|
name | 名前 |
age | 年齢 |
Userクラス
class User < ActiveRecord::Base has_many :orders end
購入情報
ordersテーブル
user_id | ユーザID |
---|---|
price | 購入した価格 |
created_at | 購入日 |
Orderクラス
class Order < ActiveRecord::Base belongs_to :user end
使用方法一覧
〜以外(否定)
例:20歳以外のユーザ一覧を取得する。
User.where.not(age: 18) #=> SELECT "users".* FROM "users" WHERE ("users"."age" != 20)
もしくは(OR条件)
例:18歳もしくは20歳のユーザー覧を取得する。
User.where(id: [18,20]) #=> SELECT "users".* FROM "users" WHERE "users"."id" IN (18, 20)
以上、以下
例:2015年以降の購入情報を取得する。
Order.where(Order.arel_table[:created_at].gt(Time.zone.local(2015))) # => SELECT "orders".* FROM "orders" WHERE ("orders"."created_at" > '2015-01-01 00:00:00.000000')
これは仕方がないのでarelを使用します。
以上という条件は`gt`、以下の場合は`lt`を使用します。
また、それも含む場合(`>=`)はそれぞれ `gteq` `lteq` を使用します。
重複を削除
例:5000円ピッタリ購入したユーザのIDを重複しないように取得する。
Order.where(price: 5000).select(:user_id).uniq #=> SELECT DISTINCT "orders"."user_id" FROM "orders" WHERE "orders"."price" = 5000
pluckしてユーザIDを取得するときなどに、重複をなくします。
サブクエリ
例:20歳のユーザの購入情報を取得する。
Order.where(user_id: User.where(age: 20).select(:id)) #=> SELECT "orders".* FROM "orders" WHERE "orders"."user_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."age" = 20)
内部結合
例:購入金額が5000円ピッタリのユーザーを内部結合して取得する。
User.all.joins(:orders).merge(Order.where(price: 5000)).references(:orders) #=> SELECT "users".* FROM "users" INNER JOIN "orders" ON "orders"."user_id" = "users"."id" WHERE "orders"."price" = 5000
以上です。今後、他のクエリ生成方法を使用したら随時追加していきます。
kakurenbo-putiというrails4.2に対応した論理削除gemの紹介
はじめに
kakurenboを公開してから1年が経過し、いろいろ思うところがあり acts_as_paranoid の呪縛から脱却した論理削除gemである kakurenbo-puti を公開しました。
経緯についてはこちらをご覧ください。
kakurenbo-puti
kakurenbo(acts_as_paranoid系のgem)はrailsの削除機能を論理削除に置き換えることを目的に作られたgemですが、kakurenbo-putiはrailsに論理削除機能を追加する目的で作られたgemです。
置き換えとは異なり、ActiveRecordを魔改造するgemではありませんので、ActiveRecordの内部構造に左右されにくく、コードもシンプル(コアファイルは70行程度)になるため、堅牢さや保守性の面でかなり優秀なgemと言えると思います。
基本的な使い方
モデルの準備
論理削除機能を追加したいモデルに論理削除用のカラムを定義し、クラス内で #soft_deletable メソッドを呼び出します。
論理削除用カラムを追加して
$ rails g migration AddSoftDestroyedAtYourMember soft_destroyed_at:datetime:index
モデルのクラス内でsoft-deletableメソッドを呼びます。
class Member < ActiveRecord::Base soft_deletable end
以上で論理削除機能がモデルに追加できます。
もし、論理削除用に使うカラム名を変更したい場合はオプションで変更可能です。
class Member < ActiveRecord::Base soft_deletable :column => :delete_at end
論理削除
論理削除時には #soft_destroy メソッドを使います。
member.soft_destroy # 結果を true/false で返します。 member.soft_destroy! # 削除に失敗した場合に例外が出ます。 member.soft_destroyed? # 論理削除済みのレコードか確認ができます。
復元
論理削除したレコードの復元には #restore メソッドを使います。
member.restore # 結果を true/false で返します。 member.restore! # 削除に失敗した場合に例外が出ます。
スコープ
論理削除されたモデルをスコープから外したり、論理削除されたものだけを取得するにはスコープを使います。
Member.without_soft_destroyed.find(1) # ID1番のレコードが論理削除された場合は取得できない。 Member.only_soft_destroyed.find(1) # ID1番のレコードが論理削除された場合のみ取得できる。
親子関係の定義
kakurenbo-putiではあらかじめ親子関係を定義しておき、親のレコードが削除された場合に子のレコードを削除されていることにできます。
soft_deletableのオプションで依存関係を定義します。
class Parent < ActiveRecord::Base soft_deletable end class Child < ActiveRecord::Base # parentが依存アソシエーションであることを定義 soft_deletable dependent_associations: [:parent] belongs_to :parent end
このように親子関係が定義された状態では次のように動作します。
parent = Parent.create! child = Child.create!(parent: parent) child.soft_destroyed? # => false parent.soft_destroy! child.soft_destroyed? # => true Child.without_soft_destroyed.count # => 0 Child.only_soft_destroyed.count # => 1
使い方は以上になります。
これから新規でプロジェクトを立ち上げる際には kakurenbo-puti をおすすめします。
論理削除gemを1年ほど保守してみて。重大な欠点にやっと気づいたポエム。
はじめに
kakurenboというgemはご存知でしょうか?paranoiaの欠点を克服すべく1年ほど前に私が開発を始めたgemです。(参考:Rails4と3で論理削除を行うためのGem Kakurenbo の紹介と今更論理削除Gemを実装した理由。 - 波打際のブログさん)
issueやpullrequestを送信してくださる善意のコミッターの方々に支えられながら1年ほど保守をしてきました。その上で薄々は気がついていたのですが、どうしても認められなかった重大な欠点をハッキリと認識させられたのでポエムにしました。
論理削除gemの起源
kakurenboもparanoiaも、廃れてしまった acts_as_paranoid を再実装したものです。
これらのgemは導入するだけで、いつも使っているdestroyメソッドが論理削除メソッドに早変わりする素晴らしいgem...になるはずだったのです。
destroyメソッドを置き換えるということ
ActiveRecordは様々な事情で成り立っているので、destroyメソッドをオーバーライドすれば論理削除gemが完成するわけではありません。コールバックにアソシエーション、destroy_allにdelete_allなどのメソッドから、find時のスコープまで様々な箇所に関係があります。
ActiveRecordはCRUDを提供しているので、CRUDのDを変更する論理削除gemはActiveRecordの深くにまで関わらなければいけなくなるのは当然です。
深く関わっているのでkakurenboのバージョンアップ対応中にActiveRecordのバグを発見するなんてこともありました。(default scopes should break the cache on singulur_association. by alfa-jpn · Pull Request #17559 · rails/rails · GitHub)
paranoia
paranoiaは保守性を優先して、ActiveRecordと深く関わらない代わりに不完全な対応のままgemをリリースしています。今となっては一理あるなと思えるものの、delete_allメソッドを呼び出すと、全て物理削除されてしまう挙動には、あまりにも整合性が保てていないと思います。(というか事故る可能性すらあります。)
kakurenbo
ということでActiveRecordの削除に関わる部分を完全に置き換えたkakurenboが生まれたわけですが、railsのパッチアップデートですら動かなくなることがありました。マイナーアップデートならほぼ確実。メジャーアップデートならacts_as_paranoidのようにプロダクトが廃れるのではないかとすら思います。
destroyという崇高なメソッドをオーバーライドするとrails様の祟りが下るのかもしれません。
なぜ、acts_as_paranoidがメンテされなくなってしまったのか、公開1年後にしてやっと理解できたような気がします。
acts_as_paranoidの呪縛から開放
悩んだ末に私が出した答えがこれです。destroyのオーバーライドをやめ、素直にsoft_destroyというメソッドを設けることです。冷静に考えるとdestroyで論理削除できなくても不便なことなんてありません。
ただし、kakurenboでそんな大幅な改変を行うのは無理があります。設計思想の根底から違うのですから。そこで、新たなgemの開発に取り組みました。
kakurenbo-putiリリース
もちろんkakurenboの保守は続けます。...が保守性を考慮して一貫性のある論理削除機能を追加可能な kakurenbo-puti をリリースしました。
alfa-jpn/kakurenbo-puti · GitHub
思い切って呪縛を立ち切れたgemです。コアファイルは70行以下です。
使っているメソッドもrailsのレールを脱線していないメソッドばかりなので、何も修正せずにメジャーアップデート対応できる気すらします!
まとめ
kakurenboの重大な欠点はActiveRecordのdestroyを完全に置き換えようとして、ActiveRecordの深い箇所まで改変し、保守が高コスト・高頻度になっていることです。
rails既存の機能を置き換えるgemならこの問題を多かれ少なかれ持っており、やがて耐えられなくなるとacts_as_paranoidのように廃れてしまいます。
そこで、今後railsで開発を行う際には、
という2点に留意すると、同じ轍を踏まずに素晴らしいrailsライフを送れると思います。
数学を放棄したエンジニアの3Dプログラミングさんすう入門 第一回 sin、cosってなんぞや
はじめに
この記事は高校数学を放棄したエンジニアが、ウェブ系エンジニアになって安心していたところに、突如3Dプログラミング(Unity)案件が降ってきて、悪戦苦闘しながら3Dプログラミングのためのさんすうを勉強した内容のまとめです。
目標
Unityにおいて勉強する必要がない内容については省いていきます。例えばベクトルの演算や、平行移動などはUnityの機能で実現できるので省きます。
ニコニ立体のカメラ や メタセコイアのカメラ を思い浮かべてください。双方とも、中心座標から一定の距離を保ち、円を描くように回転する動きをしています。この回転座標を計算できるようになることが本連載の目標となります。
Unityに触ったことがある方なら、RotateAroundを使えば実現できると考えるかもしれません。GameObject同士の回転であればRotateAroundを使えますが、座標(Vector3)同士の演算はできません。もしかしたら座標同士の演算をしなければならなくなることがあるかもしれません。わたしです(^q^)
そのような状況に対応できるようになるための3Dプログラミングさんすう入門になります。
注意
筆者は高校数学を放棄してきたので、数学的に誤った説明をする可能性があります。
SinとCosについて
数学アレルギーな私はこのワードを聞いただけで発狂しました。体中に蕁麻疹ができて泡を吹いて倒れそうな気分でした。でもまぁなんとなく理解したら大したことない奴らでした。
こいつらって何なの?
プログラミングで例えれば、ただのメソッドみたいなものです。きっと実装があればこんな感じです。
public float Sin(float radian) { /* Sinの計算がきっとこの辺にある。 */ return sin; } public float Cos(float radian) { /* Cosのがきっとこの辺にある。 */ return cos; }
なにも怖くありません。第一引数に角度を入力すれば、結果が計算されて帰ってくるのです。エンジニアはSinメソッドと、Cosメソッドの実装を知らなくても使えるのです!
一体なんに使うの?
3Dプログラミングでは SinとCosが返す値の性質を利用します。SinとCosが返す値をグラフにしてみました。
角度が0度の時に Cos(0)=1、Sin(0)=0になります。角度が90度で Cos(90)=0、Sin(90)=1、180度ならCos(180)=-1、Sin(180)=0、270度ならCos(270)=0、Sin(270)=-1。なにか見えてきませんか?
2次元座標のXにCosを、YにSinを当てはめてみます。new Vector2(Cos(radian), Sin(radian)) ってとこですね。
なんと円になります。CosとSinが返す値を使うと半径が1の円を書けるのです!
もうちょっと補足
こんなイメージです。ちなみに半径が2の円を書きたければ cosとsinを2倍にし、半径が0.5の円を書きたければ0.5倍すれば書くことができます。
まとめ
CosとSinは単なるメソッドだ!中身の実装なんか知らなくても返す値の性質さえ知っていれば後はなんとかなる!(適当)
ActiveAdminノウハウまとめ
はじめに
私が関わったプロジェクトでActiveAdminを使い倒す機会があったので、ドキュメントに掲載されていないことを中心に トラブル編 と 活用編 の2項目を備忘録程度にまとめてみました。
想定環境
Rails4 + ActiveAdmin(1.0.0.pre)
トラブル編
Rails4でインストールできない!!
原因
Rails4に対応していない`meta_search`というgemが含まれていることが原因です。
対処法
RubyGemsにリリースされているActiveAdminのバージョンが古いため(0.6.3)、次期リリース予定の`1.0.0.pre`をgithubのリポジトリから取得するようにします。
(早く正式リリースしてほしいいけど、なんか大変そう→Release of version 1.0.0 · Issue #3213 · activeadmin/active_admin · GitHub)
gem 'activeadmin', github: 'activeadmin/active_admin'
bundle exec rails g active_admin:install がdeviseの所で止まる。
こんな感じ
$ bundle exec rails g active_admin:install invoke devise To use devise you need to specify it in your Gemfile. If you don't want to use devise, run the generator with --skip-users.
原因
deviseが入っていないことが原因です。
対処法
Gemfileに`devise`を追加します。管理画面の認証を使わない場合は --skip-users オプションを使用すれば良いのですが、、、さすがにそれは。。。
ActiveAdmin::ResourceCollection::ConfigMismatch
You're trying to register ActiveAdmin::Comment as Comment, but the existing
ActiveAdmin::Resource config was built for Comment!
とか出る場合。
原因
ActiveAdminの管理者コメント機能と、プロジェクト内のコメントモデル(Comment)が競合してしまっている状態です。
対処法
ActiveAdminの管理者コメントのページ名を明示的に指定します。(管理者コメントのページ名`Comment`はさすがに競合しやすいし 1.0.0 でデフォルトを`AdminComment`とかにしちゃえばいいのに・・・)
config/initializers/active_admin.rb
config.comments_registration_name = 'AdminComment'
I18n::MissingTranslationData
`translation missing: ja.time.formats.long`
とか出てくる場合。
原因
ActiveAdminで使用している日時フォーマット用の言語ファイルが存在しないことが原因です。
フォームがローカライズされない。
原因
ActiveAdminがフォーム作成に使用している`formtastic`の言語ファイルが存在しないことが原因です。
対処法
言語ファイルを作ります。(ちなみにformtasticの方針では、言語ファイルの管理はしないみたいなので言語ファイルのプルリクは蹴られていました。)
ja: formtastic: :yes: 'はい' :no: 'いいえ' :create: '%{model}を作成' :update: '%{model}を更新' :submit: '%{model}を投稿' :cancel: '%{model}を中止' :reset: '%{model}を初期化' :required: '必須'
活用編
ActiveAdminのロケールファイル
ドキュメントのI18nの項目を見ても、`Coming soon…` になっています。基本的にはActiveRecordのロケールファイルを見に行くので、batch_actionsとscopesに関して記述します。
ja: active_admin: batch_actions: labels: hoge: 'ほげ' # hoge というバッチアクションに使用されるラベル。 scopes: foo: 'ふう・・・' # foo というスコープに使用されるラベル。
特定のアクションの時だけEargerLoadしたい。
EargerLoadはscoped_collectionを上書きすることで実現できますが、特定のアクションだけEargerLoadしたい場合は次のように実装します。
controller do def scoped_collection case action_name when 'show' super.includes(:hoges) else super end end end
ActiveAdminのコントローラはApplicationControllerを継承した、普通のコントローラなのでaction_nameなどのメソッドを使用して挙動を変化させます。
関連付けされたレコードを表示したい。
belongs_to等で関連付けされたレコードを表示する場合は`attribute_table_for`を使用します。
下記のコードはユーザの名前と年齢を出力する例です。
panel 'タイトル' do attributes_table_for user do row(:name) row(:age) end end
関連付けされた複数のレコードを表示したい。
has_many等で関連付けされた複数のレコードを表示する場合は`paginated_collection`を使用することで、ページネーションできる表が出力されます。
下記のコードはparentに関連付けされたchildrenを30件ずつ出力する例です。
panel "タイトル" do paginated_collection(parent.children.page(params[:children_page]).per(30), param_name: 'children_page') do table_for collection do column(:id) column(:hoge) column(:fuga) end end end
ActiveAdminのフォームにいろんなやつ(てきとう)を表示したい
いろんなやつ 表示できます!!!
# 日時選択 f.input :foobar_at, :as => :datetime_picker # セレクトボックス f.input :hoges, :as => :select, :collection => [['ラベル1', 1],['ラベル2',2]] # チェックボックス f.input :fugas, :as => :check_boxes, :collection => [['ラベル1', 1],['ラベル2', 2]]
ページに簡単なJSを埋め込みたい。
ちょっとしたJSをそのページだけに埋め込みたい場合はこんな感じで記述します。
script do raw <<RAW_SCRIPT alert('aaaaa'); RAW_SCRIPT end
最後に
未熟者ゆえ、「ここ間違ってるよー」とか「もっとかっこいい実装あるぜ!!」とかあったらコメント欄からご指摘おねがいします。
論理削除Gem、 Kakurenbo をアップデートしました。
様々なわけがあってkakurenboは非推奨です。新規のプロジェクトではkakurenbo-putiの使用をおすすめします。
Kakurenboとは
Rails4で論理削除を実現するためのGemです。実装に至った経緯は Rails4と3で論理削除を行うためのGem Kakurenbo の紹介と今更論理削除Gemを実装した理由。 - 波打際のブログさん を見てください。
アップデート
今回のアップデートの目的は、ActiveRecord4.0.2以降との親和性を向上させることにあります。主な変更点は次のとおりです。
メソッド名のコンフリクト解消
yusabana (Yuji Takaesu) · GitHub さんから、 destroy! メソッドのコンフリクト修正とRspec3対応のプルリクエストをいただき、マージしました。
従来の Kakurenbo で物理削除を行う場合、物理削除用のメソッド destroy! を呼び出していました。しかし、Rails4.0.2にdestroy!メソッドが追加されたため、メソッドがコンフリクトしてしまいました。解決策として、物理削除のメソッドを廃止してオプションで物理削除を指定する設計に変更しました。
この変更に伴い全ての物理削除メソッドを廃止し、オプションで物理削除を指定する設計に変更しました。
リレーショナルの論理削除に完全対応
従来の Kakurenbo はリレーショナルの論理削除に一部未対応でしたが、今回のアップデートで完全対応しました。
Rails3サポートの打ち切り
Rails4の対応に十分な時間を割り当てるために0.2以降のバージョンでは、Rails3の対応を打ち切ります。
アソシエーションの強化(7月23日追記)
belongs_toやhas_manyなどでレコードに関連付けされた他テーブルのレコードの読み込みを、近日リリース予定のバージョン0.2.1で強化します。具体的には、読み込み元のレコードが論理削除されていた場合に、関連付けされたレコードのスコープに依存削除されたレコードを含めるようにします。(完全対応はRails4.1以降のみで、Rails4.0.xではThroughアソシエーションには未対応となります。)
リファレンス
今回のアップデートで Kakurenbo のインターフェイスは次のようになりました。
論理削除
# コールバックあり model.destroy model.destroy! Model.destroy_all(hoge: 'fuga') Model.where(hoge: 'fuga').destroy_all # コールバックなし model.delete Model.delete_all(foo: 'bar') Model.where(foo: 'bar').delete_all
復元
destroy!メソッドの挙動に合わせてrestore!メソッドを修正しました。コールバックなどで復元がキャンセルされた場合に例外が発生するようになります。
# コールバックあり # before_restore, after_restore コールバックが利用可能です。 model.restore model.restore! Model.where(hoge: 'fuga').restore_all
物理削除
物理削除は、削除系のメソッドにオプションで指定するようになりました。
# コールバックあり model.destroy(hard: true) model.destroy!(hard: true) Model.destroy_all(hoge: 'fuga', hard: true) Model.where(hoge: 'fuga').destroy_all(nil, hard: true) # コールバックなし model.delete(hard: true) Model.delete_all(foo: 'bar', hard: true) Model.where(foo: 'bar').delete_all(nil, hard: true)
その他のメソッドや使い方は alfa-jpn/kakurenbo · GitHub を確認してください。
謝辞
今回のアップデートに関して、プルリクエスト、バグレポートを送信していただいた下記の方々に心より御礼申し上げます。