小さなエンドウ豆

まだまだいろいろ勉強中

1 つのモデル(テーブル)に複数の外部キーをもたせる

テーブルの同じモデルの外部キーを複数もたせる

バージョン rails: 5.1.4

Railsマイグレーションで外部キーを持たせるために必要な記述として以下のように reference を使った方法がある

def change
    create_table :records do |t|
      t.references :users, index: true, foreign_key: true
      t.timestamps null: false
    end
end

ただ、以下のようなモデル(テーブル)を作りたいときにどのようにすればよいかわからなかった…

user_id start_station_id end_station_id
1 2 3
  • user_id は User モデルを参照している
  • start_station_id は Station モデルを参照している
  • end_station_id は Station モデルを参照している

ま、要は 1つのテーブルを複数のカラムで参照したい ということ。

以下のように書くとできたため、記録しておく

class CreateRecords < ActiveRecord::Migration[5.1]
  def change
    create_table :records do |t|
      t.references :users, index: true, foreign_key: true
      t.references :start_station
      t.references :end_station
      t.timestamps null: false
    end
    add_foreign_key :records, :stations, column: :start_station_id
    add_foreign_key :records, :stations, column: :end_station_id
  end
end

参考:

Railsマイグレーションのindex、foreign_keyの設定

複合外部キーなども設定できるらしく知らなかった…
Rails は直感的に書くとできるのでありがたい

Safari で SVG の画像が表示されない

SafariSVG の画像が表示されない

表題のまんま。
D3.js でこねこねした SVG 要素の中に image タグを用いて画像を扱う際になぜか Safari だけ表示されなかった。
D3.js のコードは以下のようなもの

svg.append('image')
            .attr("id", icon.name)
            .attr("href", IMAGE_PATH)
            .attr("class", "icon")
            .attr("x", x)
            .attr("y", y)
            .attr("width", ICON_WIDTH)
            .attr("height", ICON_HEIGHT)

解決策

Stack Overflow に同様の問題を抱えた人がいてその回答を試すことによって解決した。

stackoverflow.com

自分の場合は href の部分を xlink:href に変更することによってなんとか表示できた。
あとあと調べてみると Safari では SVG で href をサポートしていないらしい…
IE で辛い思いをしたことは多々あるが、Safari ではあまりないため、今後のためにも残しておこう。

WordPress の記事に D3 で作ったグラフを挿入する

WordPress に D3 で作ったグラフを挿入する

wp-d3 というプラグインを使って d3 で作ったグラフを載せてみました。 使い方などをここに示しておきます。

プラグインをインストール

WPのダッシュボード > プラグイン > 新規追加 > 「wp-d3」で検索 > インストール!

記事にグラフを載せる

適当に新規で記事を作ります。
ここで「WPD3」のボタンを押します。 f:id:h-piiice16:20171231160506p:plain

すると新たにウィンドウが開かれます。
ここにD3のコードを書いていきます。 f:id:h-piiice16:20171231160802p:plain

今回作ったコードは下記のようなものです。
ただの黒い円ですね。

var svg = d3.select('.wpd3-18850-0')
    .append('svg')
    .attr({
        width: 500,
        height:500
    });
 
svg.append('circle')
    .attr({
        cx:150,
        cy:150,
        r:80,
        fill: "black"
    });

コードを書き終わったら「insert」を押し、「save」を押し保存します。
「insert」を押すことで記事に下記のようなタグが埋め込まれます。
f:id:h-piiice16:20171231160745p:plain

これはさっき開かれたウィンドウのタブに振られていた番号です。(わかりにくいので下記の画像の赤い部分です) f:id:h-piiice16:20171231160802p:plain

これで記事を見てみると… f:id:h-piiice16:20171231161339p:plain

できてたー!簡単ですね!
ただ、d3 のバージョンがこのプラグインに内包されているものに依存してしまうためあまり凝ったものは載せれないかも… ちなみに d3 は3系で、wordpress は4系です。

Docker compose を使って WordPress ローカル環境を作った話

Docker を使ってWordPressのローカル環境を構築

現在 WordPress 公開されているページに対して新たに機能を追加する案件が降ってきました。仕事ではないですが…
まずローカルに本番同様の環境を作りたいが、今までだと仮想環境を作って、Webサーバどうしようなどなど環境構築はめんどくさい
そこで Docker を使ってWordPressを落として5秒で構築していきます。

WordPress エクスポート

元の環境からWordPress(以下WP)のコンテンツをダウンロードします。 「WordPress Export」とかで調べるとプラグインがいっぱい出てくるのでそれを使いましょう

Docker で環境構築

ここから速攻で出来上がります。
まず docker-compose.yml を作ります。内容は下記のようにしてください。

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:

次に下記のコマンドで環境を

$ docker-compose up -d

これでWPサーバがたちあがったぽい。
sshしてみると...

$ docker exec -it e2d3wordpress_wordpress_1 bash
root@5b18004fe877:/var/www/html#

すごい...

WordPress インポート

さっきダウンロードしたWP全コンテンツを適当にインポートします。
こちらもプラグインを使えばらくちん
これで無事本番WPからローカル環境構築完了!

【Rails】Javascript で使う変数等を環境変数にまとめる

Rails プロジェクトの環境変数を JS で使用する

Rails アプリケーションで外出したくない環境変数.env に書き込んで dotenv-rails を使って呼び出すのが一般的らしいので設定してみました。

ただ、今回は mapbox という Javascript のライブラリの API token を .env に定義したいので、Rails環境変数を JS側でも使用できるように gon という Gem を使って参照する方法をまとめていきます。

Gem のインストール

まずRails プロジェクト配下の Gemfile に下記を追記する。

 gem 'gon'
 gem 'dotenv-rails'

※バージョンは今回指定していません...
それから $ bundle install でそれぞれインストールします。

.env の設定

次に .env ファイルを作ります。
例えば、下記のように環境変数を定義します。

MAPBOX_ACCESS_KEY={{自分のAPI token}}

Rails環境変数を扱う

Rails で定義した環境変数( MAPBOX_ACCESS_KEY )をコントローラで使用する際は下記のように書きます。

def show
  p ENV['MAPBOX_ACCESS_KEY']
end

※確認のためにprint してます
ENV[key] って感じで呼び出すことができます。便利!

Rails 環境変数を JS ファイルで使用

先程説明した gon という Gem を使用して環境変数を JS に渡します。
まずコントローラ側で下記のように書きます。

def show
  gon.mapbox_access_key = ENV['MAPBOX_ACCESS_KEY']
end

これでバックエンド側での設定は終わりです。
次に JS ファイル側に下記のように書いて使用します。

mapboxgl.accessToken=gon.mapbox_access_key;

gon.{{変数名}} で参照できるのすごく便利です。

最後に

.env は公開したくないので .gitignore などに追加しておきましょう。

参考 qiita.com

Kaggle に初挑戦

Kaggle とは

簡単に言うにまとめると

  1. データ持ってる人がデータ投稿する
  2. 投稿者がコンペを開く
  3. 世界中のデータサイエンティストたちが分析を行う
  4. 分析結果を Kaggle に提出してスコアが算出される
  5. 算出されたスコアをもとに競う

細かくいうとこんな感じです。
僕も存在は知っていたのですが、なかなか手が出せずにいました。
今日は Kaggle の中でも有名な タイタニックの生存予測」 のコンペに提出するまでの流れを説明したいと思います。

Kaggle 始め方

Kaggle

まずはアクセスしてアカウントを作ります。
FacebookGoogle のアカウントでサインインできるのでそれらを使うと便利かもです。

コンペへの参加の仕方

アカウントを作るとよくわからない偉い人たちのフィードが現れます。
とりあえず、これらは無視して、ページ上部の Competitions とクリックします。 f:id:h-piiice16:20170820173325p:plain

これですね。
これをクリックすると、いろいろなコンペが表示されます。
スクロールするとタイタニックのコンペもあるので、これをクリックします。

f:id:h-piiice16:20170820173600p:plain

すると、コンペの詳細ページが表示されます。
ここで予測に必要なデータをダウンロードします。
f:id:h-piiice16:20170820173828p:plain

ここで、以下の2つのファイルをダウンロードします。

(名前からして説明はいらないかもですけど、一応訓練データと予測用のデータです。)

生存予測

今回は提出するまでを目標としているので予測に関しては凝りませんでした。
使うモデルは Random Forest です。
予測を行うにあたり便利なのが Jupyter notebook でした。
これに関しては以下の記事を参考にして Anaconda をインストールしていただくと環境が整うと思います。

h-piiice16.hatenablog.com

train.csv の中身は数値データだけでなく文字列で与えられるデータもあります。
ランダムフォレストに学習させるデータは数値データでなくてはならないので train.csv の中からは以下のカラムを抜き出して使用しました。

  • Pclass … 乗客のクラス(1が高いらしい)
  • Sex … 性別
  • Age … 年齢
  • SibSp … 兄弟と配偶者の数
  • Parch … 親と子供の数
  • Fare … 料金

前処理として以下の2つを行いました。参考

# male ... 0, female ... 1
data = pd.read_csv('train.csv').replace('male', 0).replace('female', 1)

# データの抜き出し
train_data = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare]]

# Age の欠損値を中央値で埋める
train_data["Age"].fillna(train_data.Age.median(), inplace=True)

# Fare の欠損値を中央値で埋める
train_data["Fare"].fillna(train_data.Fare.median(), inplace=True)

性別のデータは男性が male、女性が female で与えられているためそれを数値に変換します。
欠損値(NaN)に関しては中央値で埋めます。
これを test.csv にも行います。
そして scikit-learn の Random Forest を使ってモデルを作り、結果を出します。
ランダムフォレスト使ってタイタニックの生存予測をする例が以下になります。

# lable_data は正解データ
# label_data = data['Survived'] で抽出
# モデルの作成
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
model.fit(train_data, label_data)

# test_data は test.csv を加工したもの
# 予測
output = model.predict(test_data)

# 提出用のファイルへの書き出し
# test_origin は test.csv を読み込んだもの
test_origin['Survived'] = output
submit_data = test_origin[['PassengerId', 'Survived']]
submit_data.to_csv('output.csv', index=False)

これで output.csv に予測結果が出力される。
PassengerId は予測用のデータに付与されている乗客を表すIDのことです。
提出用のファイルの形式はこの PassengerId と 生存結果を表す Survived を並べたものです。

提出

先程のコンペの詳細ページへ戻り、Submit Prediction を押します。 このページで output.csv をアップロードし提出完了です!
このあとスコアが計算され、順位も出ます。(僕は6000番くらいでした…)

まとめ

以上が提出までの手順となります。
タイタニック以外にもコンペがたくさんあるのでなにかしらにチャレンジしたと思います。
今回提出に当たって便利だと思ったものを以下にまとめます。

  • Chrome ? の翻訳
    Kaggle のページで右クリックでできる
    ページが英語なので結構役に立つw
  • Jupyter notebook
    書いたコードを残せるので便利!
    グラフなどもかけるので分析が捗りそう
  • pandas
    python のライブラリで csv の読み込みや書き出し、欠損値の埋め合わせやデータの抽出などが簡単にできる

これで Kaggle では怖いものなしだと思われます(たぶん)

Railsでdb:seedを用いたデータの用意

db:seed とは

Rails アプリに予め必要なデータをデータベースに一括で投入することができる機能。今回はアプリ内でenumっぽく使いたいデータをどんどん投入していきます。

手順

まずはモデルとmigrateファイルを作り、db:migrateを実行します。

$ rails generate model area name:string
$ rails db:migrate # railsは5系です

これでAreaテーブルが作成されます。(ここらへん曖昧)

で、Rails プロジェクトの db/seeds.rb と以下のように編集します。

Area.create({name: '北海道'})
Area.create({name: '東北'})
Area.create({name: '関東'})
Area.create({name: '甲信越'})
Area.create({name: '東海・北陸'})
Area.create({name: '近畿'})
Area.create({name: '中国'})
Area.create({name: '四国'})
Area.create({name: '九州'})

保存して以下のコマンドを実行します。

$ rails db:seed

すると Area テーブルに9行のレコードが生成されます。 試しに rails console で見てみます。

$ rails console

irb> Area.all

   (2.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Area Load (1.5ms)  SELECT  `areas`.* FROM `areas` LIMIT 11
=> #<ActiveRecord::Relation [#<Area id: 1, name: "北海道", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 2, name: "東北", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 3, name: "関東", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 4, name: "甲信越", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 5, name: "東海・北陸", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 6, name: "近畿", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 7, name: "中国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 8, name: "四国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 9, name: "九州", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">]>

まあなんかいけてるっぽいw

db/seeds.rb に以下のように書くとCSVからデータを投入することもできる

require "csv"

CSV.foreach('db/company.csv') do |row|
  Company.create({area_id: row[0]}, {name: row[1]})
end

めっちゃ便利!

seed で IDを指定してレコードを作る

下記のようにブロックで書くとIDを指定することができます。

Area.create do |a|
  a.id = 1
  a.name = '北海道'
end