2015年12月15日火曜日

EmbulkのFILTER系プラグインを使ってみた(embulk-filter-insert,embulk-filter-split)

こんにちは、井下です。

前回に引き続き、EmbulkのFILTER系プラグインを使ってみるということで、"embulk-filter-insert"と"embulk-filter-split"を使ってみます。

なお、今回も基となるデータはexampleコマンド⇒guessコマンドによって生成されるデータ・ymlファイルです。

使用データ
id,account,time,purchase,comment
1,32864,2015-01-27 19:23:49,20150127,embulk
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
4,11270,2015-01-29 11:54:36,20150129,NULL

デフォルトでのpreviewコマンド実行結果
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long |          time:timestamp |      purchase:timestamp |             comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
|       1 |       32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC |                     embulk |
|       2 |       14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC |               embulk jruby |
|       3 |       27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
|       4 |       11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC |                       NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+


1.embulk-filter-insert

"insert"とありますが、このプラグインはカラムのインサートを行います。
単純にカラムをインサートするだけではなく、"どこ"にインサートするのか、"どの型"でインサートするのかを設定することができます。
特に"どこ"にインサートするのかの設定方法は多く用意されており、最初のカラムや最後のカラムといった指定から、何カラム目かの指定、◯◯カラムの前or後の指定ができるようになっています。

GitHub上のExampleの通り、最初のカラムとして"host_name"カラムを追加してみます。

Embulkの読み込み用ymlファイル(緑字を追記)
in:
  type: file
  path_prefix: ~/lib/try1/csv/sample_
  decoders:
  - {type: gzip}
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    trim_if_not_quoted: false
    skip_header_lines: 1
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: id, type: long}
    - {name: account, type: long}
    - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'}
    - {name: purchase, type: timestamp, format: '%Y%m%d'}
    - {name: comment, type: string}
filters:
  - { type: insert, column: { host_name: host01 }, at: top }
out: {type: stdout}

デフォルトでのpreviewコマンド実行結果
+------------------+---------+--------------+-------------------------+-------------------------+----------------------------+
| host_name:string | id:long | account:long |          time:timestamp |      purchase:timestamp |             comment:string |
+------------------+---------+--------------+-------------------------+-------------------------+----------------------------+
|           host01 |       1 |       32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC |                     embulk |
|           host01 |       2 |       14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC |               embulk jruby |
|           host01 |       3 |       27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
|           host01 |       4 |       11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC |                       NULL |
+------------------+---------+--------------+-------------------------+-------------------------+----------------------------+

最初のカラムにhost_nameカラムが追加されました。同様に"at"パラメータに"bottom"を指定すれば、最後のカラムとして追加されるようになりますし、数値を指定すれば、指定したカラムとして追加されるようになります。("2"を指定すれば、2カラム目として追加)
※なお、"at"パラメータには他に"head"、"tail"も指定できますが、実行してみた限りでは、"head"は"top"と同じ、"tail"は"bottom"と同じ動作をしていました。

また、"at"パラメータの代わりに"before"もしくは"after"パラメータの設定でカラム名を指定すると、指定したカラムの"前"or"後"にカラムをインサートすることができます。

2015年12月3日木曜日

EmbulkのFILTER系プラグインを使ってみた(embulk-filter-column,embulk-filter-row)

こんにちは、井下です。

しばらく触れられていませんでしたが、久しぶりにEmbulkについて書いていきます。
タイトル通り、2つのFILTER系プラグインを使ってみて、どのようにデータをいじれるのかを検証してみます。
なお、検証のために使うデータ・ymlファイルのベースは、Embulkのexampleコマンド⇒guessコマンドによって生成されるデータ・ymlファイルです。


ちなみに以前書いたEmbulkの内容は以下の通りです。

1.embulk-filter-column

2015年12月現在、FILTER系では一番ダウンロードされている(GitHubから)プラグイン、embulk-filter-columnです。
主にできることは"カラムの絞り込み"と"カラムの追加"です。

GitHub上のREADMEではExampleとして"columns"、"add_columns"、"drop_columns"の説明があります。
"add_columns"は"カラムの追加"、"drop_columns"は"カラムの絞り込み"、"columns"は"カラムの絞り込みとカラムの追加の両方"に対応しています。

1-1.add_columnsの例

add_columnsでは2通りのカラムの追加方法があります。

1つ目は、追加するカラムのデフォルト値を用意する方法。
2つ目は、元々存在しているカラムの値をコピーし、新しいカラムとする方法。

例として、1つ目の方法として"add_time"カラムを追加し、2つ目の方法として"copy_id"カラムを追加します。

データ(guessコマンドの自動生成のまま。特筆しない限り以降も同じデータです)
id,account,time,purchase,comment
1,32864,2015-01-27 19:23:49,20150127,embulk
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
4,11270,2015-01-29 11:54:36,20150129,NULL

Embulkの読み込み用ymlファイル(緑字を追記)
in:
  type: file
  path_prefix: ~/lib/try1/csv/sample_
  decoders:
  - {type: gzip}
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    quote: '"'
    trim_if_not_quoted: false
    skip_header_lines: 1
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: id, type: long}
    - {name: account, type: long}
    - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'}
    - {name: purchase, type: timestamp, format: '%Y%m%d'}
    - {name: comment, type: string}
filters:
  - type: column
    add_columns:
      - {name: add_time, type: timestamp, default: '20151201', format: "%Y%m%d" }
      - {name: copy_id, src: id}
out: {type: stdout}

なお、デフォルト値は"default"キーに指定します。上記のymlファイルでは"20151201"を指定しました。
コピー元のカラムは"src"キーに指定します。上記のymlファイルでは"id"を指定しました。

実行結果(preview)
+---------+--------------+-------------------------+-------------------------+----------------------------+-------------------------+--------------+
| id:long | account:long |          time:timestamp |      purchase:timestamp |             comment:string |      add_time:timestamp | copy_id:long |
+---------+--------------+-------------------------+-------------------------+----------------------------+-------------------------+--------------+
|       1 |       32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC |                     embulk | 2015-12-01 00:00:00 UTC |            1 |
|       2 |       14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC |               embulk jruby | 2015-12-01 00:00:00 UTC |            2 |
|       3 |       27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin | 2015-12-01 00:00:00 UTC |            3 |
|       4 |       11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC |                       NULL | 2015-12-01 00:00:00 UTC |            4 |
+---------+--------------+-------------------------+-------------------------+----------------------------+-------------------------+--------------+

実行結果を見ると、ymlファイル内で定義した通りの値で、"add_time"カラムと"copy_id"カラムが追加されていることが分かります。
※カラム名を分かりやすくするため、previewの実行結果を表示しています。timestamp型のデータにタイムゾーンが表示されているのはそのためです。
 なお、runの場合、"add_time"の値は"2015-12-01 00:00:00.000000 +0000"と表示されていました


2015年11月11日水曜日

AWS無料利用枠のインスタンスで、Ruby on Railsの環境を作成してみる③

こんにちは、井下です。

前々回前回と「AWS無料利用枠のインスタンスで、Ruby on Railsの環境を作成してみる」ことをテーマとしていましたが、今回は「デフォルトで入っているサーバの差し替え」について書いていきます。

ちなみにWebサーバは"Nginx"、アプリケーションサーバは"Unicorn"を導入する手順としています。

1.デフォルトのサーバ設定

サーバの差し替えを行う前の前提知識として、Railsでは"Webrick"というWebサーバが設定を行うことなく起動させることができ、RailsのWebアプリケーションの動作を確認することができます。

"Webrick"の起動はWebアプリケーションのルートディレクトリで"rails server"もしくは"rails s"コマンドで行えます。
※外部からブラウザでアクセスするには、さらにオプションとして"-b 0.0.0.0"を追加します。このオプションがないと、起動したマシンのlocalhostのURLでしかアクセスできません

試しに前回DBをSQLiteからPostgreSQLに差し替えたRailsのWebアプリケーション、"sample"で"Webrick"を起動してみます。
[ec2-user@ip-xxx ~]$ sudo service postgresql start

Redirecting to /bin/systemctl start  postgresql.service
[ec2-user@ip-xxx ~]$ cd sample/
[ec2-user@ip-xxx sample]$ rails s -b 0.0.0.0
=> Booting WEBrick
=> Rails 4.2.4 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-11-08 19:48:46] INFO  WEBrick 1.3.1
[2015-11-08 19:48:46] INFO  ruby 2.2.3 (2015-08-18) [x86_64-linux]
[2015-11-08 19:48:46] INFO  WEBrick::HTTPServer#start: pid=1954 port=3000

これでブラウザからアクセス、する前に、EC2インスタンスのセキュリティグループを編集し、ポート3000へのアクセスを許可します。

ブラウザからのアクセス準備が整ったので、"http://EC2インスタンスのIPアドレス:3000/"へアクセスします。

Railsではおなじみの画面が開きます。
この画面はブラウザからのリクエストを処理するWebサーバと、Rails自体を動作させるアプリケーションサーバが連携して開くことができます。
"Webrick"はWebサーバに分類されますが、Railsではアプリケーションサーバの役割も与えられています。

"Webrick"は特に設定を行うことなく、手早くWebアプリケーションの動作させられるメリットがありますが、レスポンスや起動の所要時間はあまり早くないため、本番用としてはWebサーバ・アプリケーションサーバを別途用意するのが一般的です。

それでは、ここからWebサーバ"Nginx"、アプリケーションサーバ"Unicorn"を導入していきます。

2015年10月29日木曜日

AWS無料利用枠のインスタンスで、Ruby on Railsの環境を作成してみる②

こんにちは、井下です。

前回はAWS無料利用枠のインスタンスを使って、とりあえずRailsがコンソール起動できるところまでの環境を構築しました。

今回はデフォルトで入ってるツール・ソフトウェア群を、よりスペックの良いものに差し替える工程を説明していきます。
なお、差し替え対象は下記の3つです。
  • JavaScriptランタイム
  • データベース
  • アプリケーションサーバ・Webサーバ
今回はJavaScriptランタイムと、データベースの差し替えを行います。
アプリケーションサーバおよびWebサーバは、次回差し替えを行っていきます。

1.JavaScriptランタイムの差し替え

前回も触れましたが、気軽に利用できるJavaScriptランタイムとして、「therubyracer」がありますが、bundle installに失敗するケースが出てきたり、メモリ消費量が多めな問題があります。

そこでよく利用されるJavaScriptランタイムの、「node.js」を導入してみます。

まずはGit Hubに掲載されているnode.jsのインストール手順に従って、node.jsをインストールしてみます。

[ec2-user@ip-xxx ~]$ curl --silent --location https://rpm.nodesource.com/setup | bash -

## Inspecting system...

+ rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release
+ uname -m

## Confirming "el7-x86_64" is supported...

+ curl -sLf -o /dev/null 'https://rpm.nodesource.com/pub/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Downloading release setup RPM...

+ mktemp
+ curl -sL -o '/tmp/tmp.s0QxNR3NXV' 'https://rpm.nodesource.com/pub/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Installing release setup RPM...

+ rpm -i --nosignature --force '/tmp/tmp.s0QxNR3NXV'
error: can't create transaction lock on /var/lib/rpm/.rpm.lock (No such file or directory)

Error executing command, exiting

…が、ダメでした。権限の問題らしいので、sudoしてみても結果は変わらず。
仕方がないので、ソースを入手してインストールする方法を試してみたところ、こちらはインストールができました。

まずは、最新(2015年10月時点)のバージョン、0.12.7のソースコードを入手します。
[ec2-user@ip-xxx ~]$ mkdir work
[ec2-user@ip-xxx ~]$ cd work/
[ec2-user@ip-xxx work]$ wget http://nodejs.org/dist/v0.12.7/node-v0.12.7.tar.gz
-bash: wget: command not found

…その前に、wgetがインストールされていなかったので、yumを使ってインストールします。

[ec2-user@ip-xxx work]$ sudo yum install wget
~略~
Complete!

今度こそ、0.12.7のソースコードを入手します。
[ec2-user@ip-xxx work]$ wget http://nodejs.org/dist/v0.12.7/node-v0.12.7.tar.gz
--2015-10-25 21:00:46--  http://nodejs.org/dist/v0.12.7/node-v0.12.7.tar.gz
Resolving nodejs.org (nodejs.org)... 104.20.23.46, 104.20.22.46, 2400:cb00:2048:1::6814:162e, ...
Connecting to nodejs.org (nodejs.org)|104.20.23.46|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 20063992 (19M) [application/gzip]
Saving to: ‘node-v0.12.7.tar.gz’

100%[=====================================================================================>] 20,063,992  7.49MB/s   in 2.6s

2015-10-25 21:00:49 (7.49 MB/s) - ‘node-v0.12.7.tar.gz’ saved [20063992/20063992]
[ec2-user@ip-xxx work]$ ls
node-v0.12.7.tar.gz

lsコマンドでソースコードを確認できたので、解凍した後、お決まりの「./configure」⇒「make」⇒「make install」でインストールします。

[ec2-user@ip-xxx work]$ tar xvf node-v0.12.7.tar.gz
[ec2-user@ip-xxx work]$ ls
node-v0.12.7  node-v0.12.7.tar.gz
[ec2-user@ip-xxx work]$ cd node-v0.12.7
[ec2-user@ip-xxx node-v0.12.7]$ ./configure
creating  ./icu_config.gypi
~略~
creating  ./config.gypi
creating  ./config.mk


[ec2-user@ip-xxx node-v0.12.7]$ make
~略~
[ec2-user@ip-xxx node-v0.12.7]$ sudo make install
~略~
[ec2-user@ip-xxx node-v0.12.7]$ node -v
v0.12.7

バージョン確認コマンドによって、node.jsがインストールできたことが確認できました。
これでtherubyracerは不要になったので、アンインストールしてしまいます。

[ec2-user@ip-xxx node-v0.12.7]$ cd ~/sample
[ec2-user@ip-xxx sample]$ gem list therubyracer

*** LOCAL GEMS ***

therubyracer (0.12.2)
[ec2-user@ip-xxx sample]$ gem uninstall therubyracer
[ec2-user@ip-xxx sample]$ gem list therubyracer

*** LOCAL GEMS ***

therubyracerがアンインストールされたので、Railsをコンソール起動して、JavaScriptランタイムが見つからないエラーが発生しないことを確認します。

[ec2-user@ip-xxx sample]$ rails c
Loading development environment (Rails 4.2.4)
irb(main):001:0>exit
[ec2-user@ip-xxx sample]$

特に問題はなさそうです。最後にGemfileを編集して、therubyracerを再度コメントアウトの状態にし、bundle installした際にインストールされないようにします。

[ec2-user@ip-xxx sample]$ vi Gemfile

「Gemgile」(緑字の部分を追記)

~略~
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
~略~

これでJavaScriptランタイムの差し替えが完了しました!

2015年10月14日水曜日

AWS無料利用枠のインスタンスで、Ruby on Railsの環境を作成してみる①

こんにちは、井下です。

休日に犬と遊んでいるはずみで、セロテープを切るあのギザギザで出血する羽目になりました。身近な凶器ですよね、あれ…。

さて、今回はRuby on Railsの「環境構築」がテーマです。

Webアプリケーションを開発するうえで、Ruby on Railsは選択肢の1つとして挙げられるようになっていますが、実際に開発を行おうとすると、Ruby on Railsの稼動にこぎつけるまで、様々な障害が出てきます。
幸いそれなりに問題を解決するための情報は出回っているので、完全に手詰まりになることはありませんが、バージョンや環境の違いによって、情報が適切でないことも少なくありません。

そこで、AWS無料利用枠のインスタンスを利用して、1からRuby on Railsの環境構築を行ってみようと思います。

なお、後でも改めて記述しますが、インスタンスやRubyのバージョンなどは下記の通りです。

インスタンス⇒Red Hat Enterprise Linux 7.1(t2.micro) ※2015/10時点の無料利用枠インスタンス
Ruby⇒2.2.3
Ruby on Rails⇒4.2.4

1.インスタンス作成

まずはRuby on Railsをインストールするインスタンスを作成します。
選択するインスタンスは、現時点で無料利用枠となっている「Red Hat Enterprise Linux 7.1」、インストールタイプは同じく無料利用枠の「t2.micro」です。

今回はRuby on Railsを動かすことが目的なので、それ以降の設定はデフォルトのまま、インスタンスを作成します。

2.Rubyインストール

Rubyをインストールする方法は、ソースコードから直接ビルドする方法と、ツールを利用する方法に大別されますが、今回はRubyのインストールによく利用される「rbenv」というツールを利用します。

また、rbenvをインストールするためには、「git」というバージョン管理ツールが必要になります。
順序としてまとめると、次のようになります。

(1).gitをインストールするために必要なライブラリをインストールする
(2).gitをインストールする
(3).gitを利用してrbenvをインストールする
(4).rbenvを利用してRubyをインストールする

(1).gitをインストールするために必要なライブラリをインストールする

下記のライブラリをインストールします。記載しているコマンドそのままで通るはずです。
[ec2-user@ip-xxx ~]$ sudo yum install zlib zlib-devel openssl-devel sqlite-devel gcc-c++ glibc-headers libyaml-devel readline readline-devel zlib-devel libffi-devel

色々とコンソールに表示されますが、最終的に「Complete!」と出てくればライブラリの導入は完了です。
Installed:
  gcc-c++.x86_64 0:4.8.3-9.el7              glibc-headers.x86_64 0:2.17-78.el7  libffi-devel.x86_64 0:3.0.13-11.el7
  openssl-devel.x86_64 1:1.0.1e-42.el7_1.9  readline-devel.x86_64 0:6.2-9.el7   sqlite-devel.x86_64 0:3.7.17-6.el7_1.1
  zlib-devel.x86_64 0:1.2.7-13.el7

Dependency Installed:
  cpp.x86_64 0:4.8.3-9.el7                                 gcc.x86_64 0:4.8.3-9.el7
  glibc-devel.x86_64 0:2.17-78.el7                         kernel-headers.x86_64 0:3.10.0-229.14.1.el7
  keyutils-libs-devel.x86_64 0:1.5.8-3.el7                 krb5-devel.x86_64 0:1.12.2-15.el7_1
  libcom_err-devel.x86_64 0:1.42.9-7.el7                   libmpc.x86_64 0:1.0.1-3.el7
  libselinux-devel.x86_64 0:2.2.2-6.el7                    libsepol-devel.x86_64 0:2.1.9-3.el7
  libstdc++-devel.x86_64 0:4.8.3-9.el7                     libverto-devel.x86_64 0:0.2.5-4.el7
  mpfr.x86_64 0:3.1.1-4.el7                                ncurses-devel.x86_64 0:5.9-13.20130511.el7
  pcre-devel.x86_64 0:8.32-14.el7

Dependency Updated:
  krb5-libs.x86_64 0:1.12.2-15.el7_1   openssl.x86_64 1:1.0.1e-42.el7_1.9   openssl-libs.x86_64 1:1.0.1e-42.el7_1.9
  sqlite.x86_64 0:3.7.17-6.el7_1.1

Complete!

(2).gitをインストールする

gitをインストールします。やること自体は前の手順とほぼ変わりありません。
[ec2-user@ip-xxx ~]$ sudo yum install git

次のように表示されていれば、gitのインストールも完了です。
Installed:
  git.x86_64 0:1.8.3.1-4.el7

Dependency Installed:
  libgnome-keyring.x86_64 0:3.8.0-3.el7                   perl.x86_64 4:5.16.3-285.el7
  perl-Carp.noarch 0:1.26-244.el7                         perl-Encode.x86_64 0:2.51-7.el7
  perl-Error.noarch 1:0.17020-2.el7                       perl-Exporter.noarch 0:5.68-3.el7
  perl-File-Path.noarch 0:2.09-2.el7                      perl-File-Temp.noarch 0:0.23.01-3.el7
  perl-Filter.x86_64 0:1.49-3.el7                         perl-Getopt-Long.noarch 0:2.40-2.el7
  perl-Git.noarch 0:1.8.3.1-4.el7                         perl-HTTP-Tiny.noarch 0:0.033-3.el7
  perl-PathTools.x86_64 0:3.40-5.el7                      perl-Pod-Escapes.noarch 1:1.04-285.el7
  perl-Pod-Perldoc.noarch 0:3.20-4.el7                    perl-Pod-Simple.noarch 1:3.28-4.el7
  perl-Pod-Usage.noarch 0:1.63-3.el7                      perl-Scalar-List-Utils.x86_64 0:1.27-248.el7
  perl-Socket.x86_64 0:2.010-3.el7                        perl-Storable.x86_64 0:2.45-3.el7
  perl-TermReadKey.x86_64 0:2.30-20.el7                   perl-Text-ParseWords.noarch 0:3.29-4.el7
  perl-Time-HiRes.x86_64 4:1.9725-3.el7                   perl-Time-Local.noarch 0:1.2300-2.el7
  perl-constant.noarch 0:1.27-2.el7                       perl-libs.x86_64 4:5.16.3-285.el7
  perl-macros.x86_64 4:5.16.3-285.el7                     perl-parent.noarch 1:0.225-244.el7
  perl-podlators.noarch 0:2.5.1-3.el7                     perl-threads.x86_64 0:1.87-4.el7
  perl-threads-shared.x86_64 0:1.43-6.el7

Complete!

(3).gitを利用してrbenvをインストールする

先ほどの手順でインストールしたgitを利用して、rbenvをインストールします。
[ec2-user@ip-xxx ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

下記のような表示がされれば、rbenvのインストールは完了です。
Cloning into '/home/ec2-user/.rbenv'...
remote: Counting objects: 2162, done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 2162 (delta 34), reused 0 (delta 0), pack-reused 2094
Receiving objects: 100% (2162/2162), 371.13 KiB | 175.00 KiB/s, done.
Resolving deltas: 100% (1312/1312), done.

rbenvでRubyをインストールするために必要なプラグインも、gitを利用してインストールします。
[ec2-user@ip-xxx ~]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

下記のような表示がされれば、Rubyのプラグインのインストールは完了です。
Cloning into '/home/ec2-user/.rbenv/plugins/ruby-build'...
remote: Counting objects: 5056, done.
remote: Total 5056 (delta 0), reused 0 (delta 0), pack-reused 5056
Receiving objects: 100% (5056/5056), 944.89 KiB | 318.00 KiB/s, done.
Resolving deltas: 100% (2734/2734), done.

これでrbenvをインストールできましたが、rbenvを利用するためにパスを通す必要があります。
「.bash_profile」にrbenvのパスが通るように設定を追加し、以降のログインでrbenvのパスが通っている状態にします。
[ec2-user@ip-xxx ~]$ vi ~/.bash_profile

「.bash_profile」ファイル(緑字の部分を追記)
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH
export PATH=$PATH:$HOME/.rbenv/bin:$HOME/.rbenv/shims

ここでログインし直すか、「source .bash_profile」コマンドで「.bash_profile」を読み直すと、rbenvにパスが通り、rbenvコマンドを認識するようになります。
[ec2-user@ip-xxx ~]$ source .bash_profile
[ec2-user@ip-xxx ~]$ rbenv -v
rbenv 0.4.0-169-g0f44c57

(4).rbenvを利用してRubyをインストールする

ここでようやくRubyをインストールします。バージョンは2015年10月時点での安定版、2.2.3を選択しました。
[ec2-user@ip-xxx ~]$ rbenv install 2.2.3

3~4分かかりましたが、無事Rubyがインストールできました。
Downloading ruby-2.2.3.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/df795f2f99860745a416092a4004b016ccf77e8b82dec956b120f18bdc71edce
Installing ruby-2.2.3...
Installed ruby-2.2.3 to /home/ec2-user/.rbenv/versions/2.2.3

ただし、rbenvでRubyをインストールした場合、どのバージョンを利用するのか設定する必要があります(rbenvはRubyのバージョン管理を行っているため)

下記のコマンドによって、Rubyの2.2.3を利用する設定を行います。
[ec2-user@ip-xxx ~]$ rbenv rehash
[ec2-user@ip-xxx ~]$ rbenv global 2.2.3

最後にRubyのバージョン確認コマンドで、Ruby 2.2.3が確認できれば、Rubyのインストールおよび利用準備は完了です。
[ec2-user@ip-xxx ~]$ ruby -v
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]

2015年10月1日木曜日

Ruby on Railsで、実際にありそうなシーン別にテストコードを書いてみる

こんにちは。
鷲尾です。


現在Ruby on Railsに触れ始めて少し経ちますが、個人的には非常にとっつきにくく、理解に時間がかかることがたくさんあります。

例えば、テストコードを作成する際に、遷移先ページに表示されたことをテストするためにはどうすればよいのか、なにを書けばいいのかなど、戸惑うことがあるかと思います。(URLが指定通りに変化しているのか、正しい値がセットされているかなど・・・)

バリバリのプログラマーの方であればなにをそんなことと思うかもしれませんが、同じ初心者の方にもわかりやすいよう、シーン別にこのあたりを少しまとめてみようと思います。

そこで今回は、Ruby on Railsでテストコードを作成する際に、比較的多く使用されるマッチャーの使い方をシーン別に紹介していきたいと思います。


今回紹介するものは
1.なにかボタンを押されたりして、きちんと指定した遷移先に遷移しているかどうかをテストしたい
2.指定した文字列のメッセージが表示されているかどうかをテストしたい
3.登録処理などを行ったときに、本当にDBに登録されているかをテストしたい

といった3つのシーンを考えてみます。

※今回はRspecでテストを行っています。また、画面での操作をシミュレーションすることが出来るCapybaraというパッケージを導入していますので、事前に導入しておいてください。
参考:http://www.oiax.jp/rails/rspec_capybara_primer/rspec_capybara_the_first_step.html


1.きちんと指定した遷移先に遷移しているかどうかをテストしたい
例えば、ログイン画面でIDとパスワードを入力したあとに、正しい認証情報だったらトップ画面に遷移させたいといったことがあると思います。

こういった場合、例えば遷移後のURLを確認することで、テストを行うことが出来ます。

では、例文です。

let(:main_page_url){
  current_host << '/main_page'
}

let(:default_params){
  user_id => 'abcde1234'
}

  scenario 'ログイン後にmain_pageに遷移するか' do
      visit "ログインページURL"
      user = User.create default_params
     
      fill_in 'user_id', with: default_params[:user_id] ← 注1 
      click_button "ログイン"
         
      expect(current_url).to eq(main_page_url)
end

注1 ここでfill_in 'user_id'とありますが、これは入力を行うテキストボックスのIDを指しています。
例えば、ログインページにユーザIDを入力するテキストボックスがあり、そのテキストボックスにuser_idというIDが設定されていたとします。その際、Capybaraのfill_inメソッドを使って"user_idというIDがついたテキストボックス"に値を入力させることができます。



テストコードを実行する前にmain_page_urlなどの名前で遷移先のURLなどを設定しておきます。
※テストコードが書かれているファイルの初めに宣言しておくイメージです。
今回で言うと、main_page_url、default_paramsになります。

動作的には、遷移後のURLが設定したmain_page_urlと等しいかどうかを確認します。
current_urlには現在表示しているページのURLが格納されているため、

expect(今いるページのURL).to eq(遷移先のURL) 

となるわけです。
こうすれば、URLを比較することができますね。


2.指定した文字列のメッセージが表示されているかどうかをテストしたい
では、例えばなにかエラーメッセージが表示されるようにするときや、リダイレクト先にきちんと想定している文字列が表示されているかどうかを確認したいことがあると思います。
そういった場合は、上記のURLだけでなく、そのページに意図した文字列がきちんと表示されているかどうかを確認する必要があります。
例えば、ログイン画面を作っていて、ユーザIDに不適切な値を入力されたままログインボタンを押した場合。こういった場合、ページを遷移せずに、入力フォームの上や下などにエラーメッセージを表示することがありますね。





この場合、例えばこのように書くことができます。


let(:main_page_url){
  current_host << '/main_page'
}

let(:default_params){
  user_id => 'abcde1234'
}

let(:login_page_url){
  current_host << '/login_page'
}

let(:miss_params){
  user_id => 'abcde4321'
}
  
  scenario 'ログインに失敗した場合、ユーザ名は必須項目です!が表示されるか' do
      visit "ログインページURL"
      User.create default_params

      user = User.first



      fill_in 'user_id', with: miss_params[:user_id]

      click_button "ログイン"
         

      expect(current_url).to eq(login_page_url)  ← 注2
      expect(page).to have_content('ユーザ名は必須項目です!')
end
注2 ログインページにアクセスした状態が"/login_page"だったとして、ログイン失敗後も"/login_page"であるとしています。

ここで使用しているhave_contentというマッチャは、テスト対象のページに指定した文字列が存在するかどうかを確認します。

したがって、現在いるページの上部にエラーメッセージが表示される場合など、比較対象のページが現在いるページと同じページで、エラーメッセージだけが追記されるという場合に、よく利用されます。


3.登録処理などを行ったときに、本当にDBに登録されているかをテストしたい
実際にDBになにか登録する際に、本当に登録されているかどうかを確認するシーンがあると思います。
そう言った際に、どうすればDBに値が登録されているかを確認することができるのでしょうか。
この場合、登録先のDBのレコードが+1されているかどうかを確認します。
また、同じように、データ(レコード)の削除を行うメソッドの場合、DBからレコードが-1されているかどうかを確認することが出来ます。

例えば、以下のようになります。

let(:valid_attributes) {
  {
    :user_id => "abcde1234"
  }
}

it "ユーザ情報を1件登録出来ていること" do

 expect {

         post :create, {:user => valid_attributes}

     }.to change(User, :count).by(1)

 end

この場合、postでcreateメソッドが実行された際に、UserというDBにレコードが1件追加されているかどうかを確認しています。

同じように、DBから該当のレコードが削除される場合は、以下のように書きます。

let(:valid_attributes) {
  {
    :user_id => "abcde1234"
  }
}

it "ユーザ情報を1件登録出来ていること" do
 expect {

         post :delete, {:user => valid_attributes}

     }.to change(User, :count).by(-1)
 end

こうすると、レコードが1件減ったという状態をテストすることができるので、正常に削除されたかどうかをテストすることが出来ます。


Ruby on Railsでは、テストコード内で様々な状態をシミュレートするためのマッチャー(matchers)が他にもたくさん用意されています。

私も何度も経験していますが、はじめに「なにをテストしたいのか」ということを明確にしておかないと、テストコードを作成しても確認したいことが確認できていないテストコードが出来上がってしまいます。
当たり前のように聞こえるかもしれませんが、なにを確認したくて、そのためにはどのマッチャを使う必要があるのかをきちんと考えてから、テストコードを作成すると良いかと思います。

今回紹介したテストコードの例が少しでも参考になれば幸いです。

以上です。





2015年9月16日水曜日

AWS SDK for Rubyでインスタンスに関連づいていないセキュリティグループを削除する

こんにちは、井下です。

前回の鷲尾のブログに引き続いて、AWS SDK for Rubyを利用して、AWSの操作を行ってみます。
AWSへの操作という点では、AWS CLIと同じなのですが、SDKの場合は好きな言語で操作を行える点が魅力ですね。
なお、今回利用するAWS SDK for Rubyはv2です。導入方法・利用方法は最後に補足として説明しています。

さて、今回のテーマはタイトル通り、インスタンスに関連づいていないセキュリティグループの削除ですが、セキュリティグループはいくつ作っても課金対象ではありません。
そのセキュリティグループをわざわざSDKで削除させようとしているのは、セキュリティグループが意図せず増える土壌があるからです。

まず、EC2インスタンスを新規作成するとき、デフォルトの設定ではセキュリティグループもそのインスタンス用に新規作成・関連づけしてくれるようになっています。
ただし、EC2インスタンスを削除するときには、関連づいたセキュリティグループを削除してくれません。恐らくはセキュリティグループの仕様として、セキュリティグループ1つで複数のEC2インスタンスと関連づけが可能なためですね。

つまり、"とりあえず使える環境が欲しい"とEC2インスタンスをデフォルト設定で作成・削除を繰り返すと、使われないセキュリティグループがどんどん増えていきます。
また、セキュリティグループの一覧画面からは、EC2インスタンスに関連づいているかが見えないため(EC2インスタンス側からは、どのセキュリティグループが関連づいているかが見えます)、不要なセキュリティグループの削除には手間がかかってしまいます。

下の画像は私個人のAWSコンソールですが、EC2インスタンス4つに対し、セキュリティグループは19個もあります。15個は使われてない&今後使われることもないセキュリティグループです…。

前置きが長くなりましたが、実際にAWS SDK for Rubyを使ってインスタンスに関連づいていないセキュリティグループを削除してみます。
コードは以下のようになりました。

2015年9月11日金曜日

AWS SDK for Ruby を使って、インスタンスの一覧を取得してみる

こんにちは。
鷲尾です。

現在AWSからは様々な言語用のSDKが提供されていますが、今回はRuby用のSDK"AWS SDK for Ruby"を使って、AWS上のインスタンスの一覧を取得したいと思います。


それでは、はじめにプログラムを実行する準備からです。

1.必要なもの
RubyでAWSのインスタンス情報を取得する際に必要となるものは、以下の4つです。

・Rubyの実行環境
・AWS SDK for Ruby
・AWSアカウント(認証情報)
・リージョン情報(EC2)


※今回は実行環境としてRedHatを使用していますが、事前にRubyのプログラムを実行できる環境を作成しておいてください。
※AWSアカウント、及びIAMユーザは既に作成済みであることを前提としています。RubyでAWSにアクセスする際にはIAMユーザを作成時にのみ取得することが出来る認証情報(シークレットアクセスキー)を使用しますので、忘れずに保存してください。

なお、現在使用できるリージョンの一覧を以下に記載します。(2015年9月7日現在)


リージョン一覧(引用:http://docs.aws.amazon.com/ja_jp/general/latest/gr/rande.html#ec2_region)
Region NameRegionEndpointProtocol
米国東部(バージニア北部)リージョンus-east-1ec2.us-east-1.amazonaws.comHTTP and HTTPS
米国西部(オレゴンリージョン)us-west-2ec2.us-west-2.amazonaws.comHTTP and HTTPS
米国西部(北カリフォルニア)リージョンus-west-1ec2.us-west-1.amazonaws.comHTTP and HTTPS
欧州(アイルランド)リージョンeu-west-1ec2.eu-west-1.amazonaws.comHTTP and HTTPS
アジアパシフィック(シンガポール)リージョンap-southeast-1ec2.ap-southeast-1.amazonaws.comHTTP and HTTPS
アジアパシフィック(シドニーリージョン)ap-southeast-2ec2.ap-southeast-2.amazonaws.comHTTP and HTTPS
アジアパシフィック(東京)リージョンap-northeast-1ec2.ap-northeast-1.amazonaws.comHTTP and HTTPS
南米(サンパウロ)リージョンsa-east-1ec2.sa-east-1.amazonaws.comHTTP and HTTPS



1.aws-sdkをインストールする
以下のようにGemコマンドを利用して、aws-sdkをインストールすることができます。
RubyでSDKを使用する場合は、非常に簡単に準備が出来てしまうのがいいですね。

なお、AWS SDK for Rubyには、"v1"と"v2"という2つのバージョンが存在します。本来はv2を使用する予定だったのですが、私の環境だとなぜかうまく動いてくれず、今回は泣く泣くv1を使用しています。そのため、今回は明示的にv1のバージョンを使用することを明記してSDKのインストールを行います。


gem install aws-sdk -v "~>1"


これでインストールは完了です。


2.Rubyのプログラムを作成する
では実際にインスタンスの一覧を取得するRubyのプログラムを作成します。
今回はインスタンスの一覧と同時に、"インスタンスのID"、"インスタンスのステータス"、"インスタンスのIPアドレス"の3つを取得してみます。


◆ aws_instance_list.rb
プログラムはこちらからダウンロード出来ます。

require 'aws-sdk-v1'

AWS.config(:access_key_id => 'アクセスキー',
           :secret_access_key => 'シークレットアクセスキー',
           :ec2_endpoint => 'リージョン')

ec2 = AWS::EC2.new
puts '#instance-id    status    ip-address'

ec2.instances.each{|instance|
  puts "#{instance.id}\t#{instance.status}\t  #{instance.ip_address}"
}



なお、処理の流れとしては、"aws-sdk"を使用するためにrequireで外部ライブラリを宣言しておき、AWS.configで認証情報を設定した後に、その認証情報をもとにインスタンス情報を取得してくるという流れになっています。


3.実行
それでは気を取り直して、作成したRubyプログラムを実行してみましょう。

プログラム修正後、プログラムを実行します。

ruby aws_instance_list.rb


実行結果:

[ec2-user@ip-172-31-40-77 ~]$ ruby aws_instance_list.rb
#instance-id    status    ip-address
i-9*******      stopped
i-a*******      stopped
i-7*******      stopped
i-5*******      running   52.**.***.**
i-e*******      running   52.**.***.**
i-5*******      stopped

このように、インスタンスID、インスタンスのステータス、インスタンスのIPアドレスを取得することが出来ました。
AWSでは基本的に実行しているインスタンスにしかグローバルIPアドレスは付与されないため、現在ステータスが"running(実行中)"のもの以外は、割り当てられていないために、表示されていません(※)。

※Elastic IPが割り振られているインスタンスであれば、ステータスが"stopped"でも、グローバルIPアドレスが表示されます。



以上、AWS SDK for Rubyを使用してAWSのインスタンス一覧を取得してくるまでの流れになります。

なお、取得できるインスタンスの情報として、今回の情報以外にも様々な情報を取得することが出来ます。

・使用できるメソッド一覧
http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html#hypervisor-instance_method


使用する際は、ぜひ参考にしてみてください。

※v2での動作確認が出来ましたら、また報告します!


以上です。

2015年8月31日月曜日

Googleドライブの不要なファイルを自動で削除してみる 【自動削除編】

こんにちは、井下です。

前回からかなり間が空きましたが、引き続きGoogleドライブから不要なファイルを自動で削除してみます。

参考:前回→Googleドライブの不要なファイルを自動で削除してみる 【リストアップ編】

方法としては、以下の手順になります。
  1. "最終更新日から一定の日にちが経過しているファイル"を削除対象候補として、スプレッドシートにリストアップする
  2. 削除対象を確定する(手動で行います)
  3. 確定させたリストを基に、削除対象のファイルを全て削除する
手順1については前回実装・説明したので、今回は手順2について実装・説明します。
ただ、今回は手順1で実装した内容についてパフォーマンスの改善と、手順2で利用するカラムの追加を行っているのでご注意ください。
NAME_COLUMN = 0;
OWNER_COLUMN = 1;
MAIL_COLUMN = 2
LAST_UPDATED_COLUMN = 3;
ID_COLUMN = 4;

START_ROW = 1
START_COLUMN = 1

// 手順1
// 不要と思われるファイル(最終更新日から一定の日にちが経過したファイル)をリストアップする
function listup() {
  var DATE_OFFSET = 10;
  var sheet = SpreadsheetApp.getActiveSheet();

  var nowDate = new Date();
  var baseDate = dateFormat(new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate() - DATE_OFFSET));

  var files = DriveApp.searchFiles('modifiedDate <= "' + baseDate + '"');

  var values = [['ファイル名', 'オーナー', 'オーナー(メールアドレス)', '最終更新日', 'ファイルID']];

  var row = 1;

  while(files.hasNext()){
    var file = files.next();
    var owner = file.getOwner();
    values[row] = [];
    values[row][NAME_COLUMN] = "=HYPERLINK(\"" + file.getUrl() + "\",\"" + file.getName() + "\")";
    values[row][OWNER_COLUMN] = owner.getName();
    values[row][MAIL_COLUMN] = owner.getEmail();
    values[row][LAST_UPDATED_COLUMN] = file.getLastUpdated();
    values[row][ID_COLUMN] = file.getId();
    row++;
  }

  sheet.getRange(START_ROW, START_COLUMN, values.length, values[0].length).setValues(values);
}

// 手順3
// 手順1でリストアップしたファイルを削除する(自分がオーナーでないファイルは、マイドライブから表示しなくするだけ)
function trash() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var values = sheet.getRange(START_ROW, START_COLUMN, lastRow, ID_COLUMN + 1).getValues();

  for(var index = 1; index < lastRow; index++){
    if(values[index].join('') == ""){
      continue;
    }
 
    var owner = values[index][MAIL_COLUMN];
    var file = DriveApp.getFileById(values[index][ID_COLUMN]);
 
    // マイドライブから表示されないようにする
    DriveApp.removeFile(file);

    if(owner == Session.getActiveUser().getEmail()){
      // 自分がオーナーのファイルはゴミ箱に移動する
      file.setTrashed(true);
    }
  }
}

function dateFormat(date) {
  return date.getFullYear() + '-' + ('0' + (date.getMonth() + 1) ).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
}

前回から不要なファイルを削除するために、trashメソッドを追加しています。

trachメソッドはシートに書き込まれたファイルを参照し、自分がオーナーのファイルはゴミ箱へ移動します。
また、オーナーに関係なく、シートに書き込まれているファイルは全てマイドライブから表示されないようにします。
※ あくまでマイドライブから表示されなくなるだけで、ファイル自体は削除されません。

運用方法としては、次のようになると考えています。
  1. listupメソッドによって最新更新日から一定期間以上経過したファイルをリストアップする(手動でlistupを起動 or トリガーによって定期的に自動起動)
  2. リストアップされたファイルを確認し、削除したくないファイル名が記載された行を削除する(ここは手動で行います)
  3. trashメソッドによってリストアップされたファイルを全て削除する(手動でtrashを起動 or トリガーによって定期的に自動起動)
listupメソッドはトリガーで自動起動しても問題ありませんが、trashメソッドは人の目によるリストのチェックが事前にあるものと想定しているので、トリガーによる自動起動はご注意ください。

なお、最悪誤って削除してしまった場合も、ゴミ箱に移動しているだけなので、数日間は元に戻すことができます。

Google Driveの整理にご利用ください。