RubyKaigi1日目に行ってきた

はじめに


ここ最近はRubyを書くことが少なくなってきたのですが、
RubyKaigiの一日目に行ってきた

ミドルウェアとかインフラとか、設計とかコードから離れた生活を繰り広げております

RubyKaigiの一日目は、これまた時間と仕事の都合上午前中のみしか参加できずorz

なので、一日目は、keynoteだけ聞いてきました

Crystalにも興味はあったので、ちょっと気になるのですがここは涙を飲んで・・・

ところでマネーフォワードさん、すごいですね。

Rubyコミッターのフルタイム採用とか松田さんの技術顧問就任とか。

という訳でメモを残しておく

keynote


プログラマーの美徳

  • 怠惰(怠けるための努力をしない)
  • ガマンしない(PCが活用されていないことに怒る)
  • 傲慢(ソフトウェアのクォリティ)

全て、怒りに通じている
(Rubyにケチ付けられていることも・・・)

怒りは伝染るので、親切も伝染る

なので、内心腹たってても親切に対応するといいことがある

親切な対応が多いので、Rubyコミュニティは素晴らしい

最近のニュース

  • Ruby2.3.0リリースが出た(クリスマスにリリースする)
  • RubyKaigiに合わせてやった
  • もしかして機能をつけた(NameErrorの際にスペル指摘とか)
  • Enumerable#grep_vが追加(マッチしないものを提示)
  • Hash#fetch_at(指定しないキーがあれば例外)
  • Numeric#negative? positive?
  • Hashの包含関係をチェックできるようになった
  • Hash#to_proc(h= {1: 2,2: 4,4: 6} (1..3)..map(&h))
  • Array,Hash#digメソッド追加(data.dig(:user, 0)のようにあればアクセスして値を返す)
  • ヒアドキュメントにチルダをつけると、インデントの浅いところに合わせる
  • frozen-string-literalをつけると、文字列に追加するとエラー、パフォーマンスがよくなる&エラーがはっきりする
  • safe navigation オペレーター「&.」の追加(ぼっちオペレーター・・・) u && u.first && u.name が u&.first&.name になる
  • 年に5%~10%の改善(2.3も同様)

よくなる提案を受け入れてもっと良い物をという、Rubyコミュニティの成果

  • mruby1.2
  • ビルドプロセスとか、諸々の改善

  • Streem

  • githubに公開してた
  • 何もしないのに取り上げられて、色々とバズってしまった

Ruby3

  • 変化はコスト、苦痛
  • 言語デザインは難しい
  • 矛盾する要求がある

未来の状況はわからない

スクリプト言語にオブジェクト指向要らない
→21世紀になってくると何らかの形でオブジェクト指向を持つものが多くなった
→人に合わせてつくることが無かった
新しい当たり前になれた
Ruby成功の秘訣
webappの分野ではトップ近くまでなれた
とはいえ、言語して完成しているわけではない

互換性の問題や、移行の問題(5年かかった)
# Python3よりはマシ

全部を捨てることはせず、段階的に変化させていく方法を取った
1つずつ、テストを書いて確認して実施
移行パスの提示

ドラスティックな変更はしないし、ひとつずつの進歩
バージョニング問題(1.8 vs 1.9のときは除くの轍は踏まなかった)は他の言語のようなことは起きなかった

変化のうえで学んだこと

  • 何もかもいっぺんに捨てない
  • 劇的な変化はしない(互換性を理由なく捨ててはいけない)
  • 変化を続ける
  • ユーザーへの利益(簡潔にかけるとか)

Ruby3では以下をやりたい

  • マルチコアへの対応(開発当時はシングルコアでマルチスレッド)
  • コードの拡大(主戦場が変った)
  • 扱うデータ量の増加でのパフォーマンス対応

こんな感じ(約束はしていない)

  • アクターモデル(goroutin+channelも一応こちらに)
  • オーナーシップモデル
  • STM(software transaction)は難しいし、無理かも
  • streamモデル
  • pipeラインを使ったイベントループ(2番めのVMを動かすことで実現)
  • コンパイルを賢く
  • soft typing(flowtyped interfaceでFlow / Crystal言語、ある程度の型チェックDialyzar(Erlang))
  • 3倍早くをパフォーマンスも目標にする(Ruby2.0よりも3倍)
  • 2020までにRuby3を出したい・・・
  • 難しいことに挑戦
  • メモリを大量に使うけど、消費量をおさえるようにはしたい

Rails5を使ってみる

はじめに


とある要件があり、使用するFramework最近ブログネタに賑わっているRailsでということになった
せっかくなので、開発開始&リリースはいつになるのかという噂のRails5を使ってみることにした

開発変更時期にRails5もリリースされているだろうという多少の先を見越す形でもありますがw

準備


どうやら、後方互換とはなんだったのかの勢いで最新っぽいので、以下を準備します。

  • Ruby2.2.2以降のインストール
  • railsのリポジトリをclone

なにはともあれ、現時点の最新版だとスッキリした気分ですねw

1
$git clone https://github.com/rails/rails.git

まずはバージョン情報


昔はrailties/bin/railsだったのですが、railties/exe/rails変更になったようです
ちょっと焦りましたw

1
2
3
4
5
6
7
8
$./rails/railties/exe/rails -v
/usr/local/var/rbenv/versions/2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- concurrent/map (LoadError)
  from /usr/local/var/rbenv/versions/2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
  from /Users/moonstruckdrops/rails/activesupport/lib/active_support/inflector/inflections.rb:1:in `<top (required)>'
  from /usr/local/var/rbenv/versions/2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
  from /usr/local/var/rbenv/versions/2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
  from /Users/moonstruckdrops/rails/activesupport/lib/active_support/inflections.rb:1:in `<top (required)>'
    (省略)

どうやら、activesupport/activesupport.gemspecを見ると、
concurrent-rubyが足りなかったようなので追加します(正式版では無く、alpha版が必要です)

1
$gem install concurrent-ruby -v 1.0.0.pre5 --no-ri --no-rdoc

気を取り直してもう一度実行すると動作します

1
2
$./rails/railties/exe/rails -v
Rails 5.0.0.alpha

プロジェクト生成


せっかくなのでRails-APIオプションを使います
(api以外のオプションはお好みで設定してください)

1
$./rails/railties/exe/rails new my_app --api -d mysql -T --skip-javascript --skip-test-unit --skip-turbolinks

が、そのまま実行してもエラーになりますのでGemfileを修正します

  • railsは、cloneしたソースコードになるようにする
  • rack,arelはgit or cloneしたソースコードになるようにする

最終的にGemfileを以下のようにする(コメントのみ削除)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
source 'https://rubygems.org'

gem 'rack', path: '../rack'
gem 'arel', path: '../arel'

gem 'rails', '5.0.0.alpha', path: '../rails'
gem 'mysql2', '>= 0.3.18', '< 0.5'
# gem 'bcrypt', '~> 3.1.7'

# gem 'unicorn'

# gem 'capistrano-rails', group: :development

gem 'active_model_serializers', '~> 0.10.0.rc2'

# gem 'rack-cors'

group :development, :test do
  gem 'byebug'
end

group :development do
  gem 'spring'
end

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

起動する


ここは普段どおりにrackで起動します rails

1
2
3
4
5
6
7
8
9
$cd my_app
$bin/rails s
=> Booting WEBrick
=> Rails 5.0.0.alpha application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-11-06 00:00:05] INFO  WEBrick 1.3.1
[2015-11-06 00:00:05] INFO  ruby 2.2.2 (2015-04-13) [x86_64-darwin14]
[2015-11-06 00:00:05] INFO  WEBrick::HTTPServer#start: pid=14583 port=3000

ポイント


特に挙げることもないですが、 rails配下にあるgemspecファイルに目を通しておけば、インストールや起動に必須なgemがわかるので問題解決が早くなるということぐらいでしょうか

Pythonで文字変換

はじめに


AWSのLamdaを使ったのですが、どうも日本語が文字化けする形の模様
これをライトに解決したい
数が多いと使えないですが、少量の文字であれば直接Unicode Escapeする形にする

やり方


Pythonの対話側インターフェースでライトに行う

1
2
3
4
5
6
7
8
9
10
11
$python
Python 2.7.10 (default, Jul 14 2015, 19:46:27)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s =  u'2015年春'
>>> s.encode('unicode_escape')
'2015\\u5e74\\u6625'
>>> unicode('2015年春', 'UTF-8')
u'2015\u5e74\u6625'
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit

こんな感じ、別にAWS絡みではなくてもライトに使える

逆にデコードする場合、s.decode('unicode-escape')でできる

簡単ですね。

GoogleMapで逆Geocodingをしたい

はじめに


緯度と経度はわかるものの、住所がわからなくて困りまったことがありました。
特にGoogleMapのI/Fからは住所が導き出せなったので、ほとほと困り果てました。
ふと、その時ドキュメントを覗いたときに逆Geocodingができる記述があったのでやってみました。

リクエストパラメータ


以下の表のように設定する

パラメータ 説明 指定方法 必須
address 取得対象の住所 テキスト latlngかどちらかを指定
latlng 取得対象の緯度軽度 テキスト addressかどちらかを指定
sensor 位置センサーからのリクエストか boolean 必ず指定する
bounds ビューポートの境界ボックス(境界ボックスの南西と北東の角の緯度・経度で指定) テキスト
region 地域コード(ccTLDの2文字) テキスト
language 結果取得時の言語 言語コード

boundsは以下のような形で指定する(緯度、経度がそれぞれどっちに向いてるのかを指定すると思えばよい)

1
36.798805,-120.498805|136.733332,-130.498805

実行する

以下のようにすることでjsonが返却される

1
$ curl -v http://maps.google.com/maps/api/geocode/json?latlng=36.798805,%20136.733332&sensor=false |jq .

unixtimestampをDatetimeに変更する

はじめに


とあるログファイルを解析する必要があったわけなのですが
以下の状況のようなファイルでした

  • 中途半端にDateTimeが出力されている
  • 但し、unixtimpstampであれば確実に出力されている

なので、正確に発生日時を調べる必要が出てきました

変換コマンド


Linuxの場合であれば、以下を実行するだけなのでそんなに難しくない

1
2
$ date --date '@1443883848'
2015年 10月  3日 土曜日 23:50:48 JST

Macの場合であれば、Homebrewでcoreutilsをインストールしてあれば以下を実行するだけ (以下のような形でフォーマットの設定が必要ですが・・・)

1
2
$ gdate --date '@1443883848' +'%Y/%m/%d %H:%M'
2015/10/03 23:50

ログ解析用のバッチにこのコマンドを仕掛ければ簡単に使えそうですね

Deviseでemailを空白でもよいようにする

やりたいこと


以下の2つを実現したい

  • ユーザー登録した場合はemailの入力は不要
  • ユーザー情報編集時、emailが空文字でも、uniqueバリデーションに引っかからないよう
  • ただし、uniqueは別なもので担保

対応方法


  1. indexの削除
  2. 別のものでuniqueを担保
  3. email判定のロジック箇所をオーバライド

indexの削除


以下のコマンドでマイグレーション用ファイルを作成する

1
$rails g migration remove_index_email_from_users

以下のように実装し、migrationを実行する

1
2
3
4
5
class RemoveIndexEmailFromUsers < ActiveRecord::Migration
  def change
    remove_index :users, column: :email, unique: true
  end
end

別のものでuniqueを担保


こちらは普通にunique制約とvalidates_uniqueness_ofを付与するのみ

とりあえずunique制約のみ

1
$rails g migration add_index_item_from_users

以下のような形でunique制約をつける

1
2
3
4
5
class AddIndexItemFromUsers < ActiveRecord::Migration
  def change
    add_index :users, :item, unique: true
  end
end

こんな感じにしておく

1
2
3
class User < ActiveRecord::Base
  validates_uniqueness_of :item
end

email判定のロジック箇所をオーバライド


emailカラムにはバリデーションが設定されているので、
これをオーバライドすることでバリデーション周りの挙動を変更できる

判定箇所を追いかけると、デフォルトで有効になっているのでここをfalseに変更する

こんな感じにしておく

1
2
3
4
5
class User < ActiveRecord::Base
  def email_required?
    false
  end
end

勿論、状況によってemail_required?が必要なこともあるのでここは要仕様検討というところでしょうか。

当たり前ですが、デカイライブラリなので、ちょっとしたことをやるだけでも結構ややこし目の実装だったりソースを読むことが必須になりますね。

RailsでSTI(単一テーブル継承)の予約語でハマった話対処

はじめに


ここ数日、自分用のアプリを作っているわけなのですが
その時に起きたエラーを記載しておきます

(STIの予約語のこと忘れてたというオチw)

事象


ActiveRecordで「type」というカラムをもつテーブルに接続し、取得系のメソッドを呼ぶと以下の様なエラーが出た

1
2
3
[1] pry(main)> Hoge.find(1)
  Hoge Load (0.4ms)  SELECT `hoges`.* FROM `hoges` WHERE `hoges`.`id` = 1 LIMIT 1
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'fuga'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Hoge.inheritance_column to use another column for that information.

エラーメッセージから分かるように「type」というカラムをrenameしろと出ています
こんなの場合によっては出来無いわけで・・・

これは何?


STI(単一テーブル継承)と呼ばれる機能のことを指しています
つまり、1つのテーブルを複数のModelで利用する仕組みのことを言います。

で、その仕掛けように予約語として、”type”という名前をActiveRecordが利用しているようです。

クラスで言うとこんなところでしょうか。
Humanクラスを継承した、Manクラス、Womanクラスがあったとした場合、以下のような形になります

1
2
3
4
5
6
7
8
9
10
11
class Human < ActiveRecord::Base

end

class Man < Human

end

class Woman < Human

end

で、例えばManオブジェクトをsaveすると、Humanテーブルにtype:Manとして保存されます。
Womanならtype:Womanになるようです

それぞれのモデルからデータへの操作を行った場合、適合するTypeのデータのみを操作対象となります
ManもWomanも関係なく取得したい(両方をまとめて扱いたい)場合は、Humanモデルから操作すればOKです

当然ひとつのテーブルを使っていますので、片方にしか無い項目(カラム)もテーブルに含まなければならないです。
例えばManとHumanの持つ項目があまりにも違う場合、カラム数が爆発的に増えます。
(ここは注意するところですね)

対処


もうざっくりというと、以下の2つの対応ができます

  1. カラム名に「type」という名前をつけず、別名に変更する
  2. エラーメッセージにあるようにSTIのカラム名を変更する

というわけなので、自分は1の対応を行いました。

では、2の場合はどうするかというと・・・ エラーメッセージにありますが、以下ように設定すればよいようです

1
2
3
class Hoge < ActiveRecord::Base
  self.inheritance_column = :_type_disabled
end

どういう場合に起きそう


ざっと思いつくのはこんなところかな

  • 既存のアプリケーション(PHPなりJavaなり)をRailsにのせかえるとき
  • 設計上どうしても止むえずつけたいとき(これはビジネスドメインの場合が当てはまる)
  • (あんまりないと思うが)STIの機能を使ってリファクタリングするとき

Railsのモデル周りのマイグレーション

はじめに


Railsでmigrationするときにいつもアレなんだっけなぁとなることが多いので、
備忘録程度にまとめておく

こんなものドキュメント見れば理解できるので、ざっくりレベルのサマリー程度にしておく

基本


マイグレーションファイル作成コマンド

1
$rails generate migration クラス名

モデル作成

1
$rails generate model モデル名

モデル&テーブル作成


フィールド指定で作成する場合、以下の形で行う
(大体はフィールド指定を行わずに、実行することが多いかも)

1
$rails g model モデル名 フィールド:型

カラムの型指定


細かい調整をしようと思えばできるけど、一旦これだけ覚えておけば良い

Ruby側の型 DB側の型
string 文字列
text 長い文字列
integer 整数
float 浮動小数
decimal 精度の高い小数
datetime 日時
timestamp タイムスタンプ
time 時間
date 日付
binary バイナリデータ
boolean Boolean

(補足)MySQLで使用する場合の文字列型


MySQLで使用できる文字列型は以下が存在している

DB側の型 内容
CHAR 255Bまでの固定長文字列
VARCHAR 64KBまでの可変長文字列
TINYTEXT 255Bまでの可変長文字列
TEXT 64KBまでの可変長文字列
MEDIUMTEXT 約1.6MBまでの可変長文字列
LONGTEXT 約4.3GBまでの可変長文字列

上記をそれぞれmigrationで利用する場合
以下のようにlimitをつけることで、使用する文字列型を変更することができる

1
2
3
4
5
6
7
8
9
class CreateHoges < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.text :value, :limit => 4294967295

      t.timestamps null: false
    end
  end
end

実行すると以下のようになる

1
2
3
4
5
6
7
8
9
10
$ rails db
mysql> show columns from hoges;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| value       | longtext     | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

limitの値は、以下のように対応している

設定値 DB側の型
1 ~ 255 TINYTEXT
256 ~ 65535 TEXT
65536 ~ 16777215 MEDIUMTEXT
16777216 ~ 4294967295 LONGTEXT

マイグレーション実行


DB作成

1
$rake db:create

マイグレーションの実行

1
$rake db:migrate

マイグレーション結果確認

1
$rake db:migrate:status

ロールバック

1
$rake db:rollback

DBの削除

1
$rake db:drop

シードの投入(マスターデータなどシードで投入することが多い)

1
$rake db:seed

既存カラムの変更


カラムを変更したい場合、以下のコマンドでマイグレーションファイルを生成する

1
$rails g migration ChangeColumnTo<モデル名>

変更は以下のような形で行う(upとdownを定義することでrollbackにも対応できる)

1
2
3
4
5
6
7
8
9
10
11
12
class ChangeColumnToYourModel< ActiveRecord::Migration

  # 変更内容
  def up
    change_column :users, :hoge, :string, null: false, default: 0
  end

  # 変更前の状態
  def down
    change_column :users, :hoge, :string, null: true, default: 0
  end
end

気をつけることとして、change_columnは以下のような順で記載すること

1
change_column :<テーブル名>, :<カラム名>, :<型>, <default値やindexといったオプション指定>

カラムの追加/削除


カラムの追加/削除をしたい場合、以下のコマンドでマイグレーションファイルを生成する
追加の場合は、フィールド指定可能

1
$rails g migration AddColumnTo<モデル>

以下のような形で記載する

1
2
3
4
5
6
7
8
9
10
11
12
13
class AddColumnToYourModel < ActiveRecord::Migration
  def change

    # 追加
    add_column :hoges, :hoge, :string

    # 削除
    remove_column :hoges, :fuga, :string

    # 追加する場所を指定する場合
    add_column :hoges, :fugafuga, :string, :after => :hoge
  end
end

インデックスの追加/削除


カラムの追加/削除と基本は変わらない

1
$rails g migration AddIndexTo<モデル>

以下のようにしてindexを設定する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AddIndexToYourModel < ActiveRecord::Migration
  def change

    # 追加
    add_index :hoges, :hoge

    # 削除
    remove_index :hoges, :fuga

    # 複合インデックスの場合
    add_index :hoges, [:hoge, :fuga]

  end
end

参考


ここに書いてる内容は、Railsドキュメント見れば一発で理解できるけどな

テーブル単位のmysqldump

めちゃくちゃ基礎的な内容なのですが、備忘録を兼ねてメモしておく

DBまるごと

よくやるmysqldumpコマンド

1
$mysqldump -u root -p development_db  > db_backup.sql

テーブル単位

tオプションは、「テーブル作成情報(CREATE TABLE ステートメント)を書き込まない」という内容を指す
migrationなどで先にテーブルを作った場合とかに有効活用できる

1
$mysqldump -u root -p -t development_db my_table1 my_table2 > table_backup.sql

上のコマンドのようにテーブル名は複数指定可能(my_table1, my_table2などが該当)

リストア

1
$mysql -u root -p development_db < summaries.sql

BuildConfigを使用して、環境ごとに値を取得仕分けてみる

はじめに


WebAPIと連携するようなAndroidアプリを作っているのですが 開発と本番で接続先を切り替える必要が出てきました。

それのライトな対応方法をメモ

build.gradleを編集


以下のような形で記載

1
2
3
4
5
6
7
8
buildTypes {
  debug {
    buildConfigField "String", "API_URL", "\"http://api-dev.hoge.co.jp\""
  }
  release {
    buildConfigField "String", "API_URL", "\"http://api.hoge.co.jp\""
  }
}

ソースコード側


以下のように特に気にせずBuildConfigを参照すればよい

1
String url = BuildConfig.API_URL

ビルド環境に応じた値が取得できる

できたけど・・・


これぐらいなら、まぁbuild.gradleでいいやという気がするもののパラメータ増えたら収拾つかなくなる予感

真面目にやるならBuild Variantsを使うほうがよいかと思います