読者です 読者をやめる 読者になる 読者になる

無気力生活 (ノ ´ω`)ノ ~゜

脱力系エンジニア。てきとーに生きてます。

Windows用バージョン管理ツール「Chocolatey」

もともとはWindowsを個人用PCにしていたのですが、Linux使う仕事をするようになった関係か、Macが非常に使いやすくそっちばかり使ってました(`・ω・´)

とはいえ、やはり自宅PCで作業したい感があり、Windowsでもまともに作業できるようにしたいなと。

Macだと、ベースがUnixなので、何も考えることなく、Homebrewを使えば動作する環境を楽に整えられます!便利!
しかしWindowsではVirtualBoxとかVmWareとか使って仮想化しないと、Linux的な環境を用意することはできません。

sygwin + spy-cygの組み合わせである程度はカバーできるのですが、あくまでUnixを再現しているのに過ぎないため、ちゃんと動かない言語やツールが稀にあります(´・ω・`)

となれば、一番楽に環境整えられるのは、docker + docker machineという選択肢になるわけですね。

Chocolatey

さて、ここから本題。

この記事書いてる時点でのWindowsにDocker導入する最新の方法は、chocolateyです。たぶん。

Windows版Homebrew + Homebrew-caskみたいなイケてるやつです。
インストール自体はかなり簡単。公式サイトに書かれているコマンドをPowerShellで実行するだけです。

が、おそらくそのまま実行すると、実行ポリシーによって、スクリプトの実行がブロックされます。
解消方法はここを参照 https://support.microsoft.com/ja-jp/kb/2411920

いったんExecutionPolicyをUnrestictedにして、インストール後Restrictedに戻す方法ですね。
それを踏まえて、以下をPowerShellで実行してください。

Set-ExecutionPolicy Unrestricted
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
Set-ExecutionPolicy Restricted

あとは適当にYesとかやってれば入ります。
インストールに成功したかどうかはchoco -vですね。バージョン番号出ればOK。

ちなみに、chocolateyの実体はただのexeなので、PowerShellでなくてもcmdやcygwinでも使えます。

dockerのインストール

choco install docker docker-machine

以上。

きっと環境移行にも便利につかえるはず

chocoがサポートしているパッケージはdocker以外にもいろいろあり、以下に対応しているやつが記載されています。
https://chocolatey.org/packages

Pythonとかその辺の環境以外にも、iTunesとかLinqpad、果てはVisual Studioといった、メジャーどころのアプリがあったりします。

もう環境構築これだけでいいんじゃないか(`・ω・´)
chocolateyインストールと、インストールアプリを管理したpsファイル作っておいて、新しい環境で実行するだけでOK。

ようやく、Windowsでもまともにパッケージ管理できるようになりましたね!




ただし、当然ながら日本語とか期待しちゃいけません。基本英語版になります。残念(´・ω・`)
日本語必要としている方は、普通にパッケージダウンロードしてきて入れましょう。

gulpでSlimのビルド環境作る2

以前、こんなことを書きました。

gdgd-shinoyu.hatenablog.com

現在、改めてこのへんの整備をしているのですが、もっと簡単に導入する方法があったのでメモがてら書いておきます。

今年3月くらいに、こんなものが作られていたようです。

www.npmjs.com

導入方法は上のリンク先見てもらえば全部書いてるので、特に特筆すべきことはないのですが、簡単な手順をば。

  • gem install slim
    ※結局これは必要
  • npm install --save-dev gulp-slim
  • gulpfileを作成

これでビルドできる( ・`ω・´)

ただ、これで吐き出したコードは圧縮されるので、改行やインデントが軒並み死にます。
インデント欲しい時は、gulp-slimの引数にこんなふうなやついれるといい感じになります。

gulp.src("./slim/*.slim")
    .pipe(slim({pretty: true})) //オプション追加
    .pipe(gulp.dest('./html/'));

使えるオプション一覧は

https://www.npmjs.com/package/gulp-slim#options

を参照してください。

mysql(mariadb)のテーブルサイズとインデックスを一覧で見る

小ネタ過ぎてエントリに上げるのもアレですが、備忘録兼ねて( ・`ω・´)

テーブルサイズ一覧

SELECT 
  table_name,
  table_rows AS "rows_count",
  ROUND(data_length/1024/1024,1) AS "table size(MB)",
  ROUND(index_length/1024/1024,1) AS "index size(MB)"
FROM 
  information_schema.tables 
ORDER BY
 data_length DESC 
table_name rows_count table size(MB) index size(MB)
articles 327540 10.5 5.5
users 2 0.0 0.0
schema_migrations 2 0.0 0.0
dummy 0 0.0 0.0

インデックスのサイズも見れる。まあそこそこ使う。

インデックス情報一覧

SELECT
  *
FROM
  information_schema.statistics
WHERE 
  table_schema = "sandbox"
ORDER BY 
  table_name, index_name
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME NON_UNIQUE INDEX_SCHEMA INDEX_NAME SEQ_IN_INDEX COLUMN_NAME COLLATION CARDINALITY SUB_PART PACKED NULLABLE INDEX_TYPE COMMENT INDEX_COMMENT
def sandbox articles 1 sandbox ix_user_id 1 user_id A 2 NULL NULL YES BTREE
def sandbox articles 0 sandbox PRIMARY 1 id A 327540 NULL NULL BTREE
def sandbox dummy 0 sandbox PRIMARY 1 id A 0 NULL NULL BTREE
def sandbox schema_migrations 0 sandbox unique_schema_migrations 1 version A 2 NULL NULL BTREE
def sandbox users 0 sandbox PRIMARY 1 id A 2 NULL NULL BTREE

このデータの見方はそのうち

mithril.jsとCORSの話

個人用のツールとして、最近話題になり始めてるmithril.js使ってるんですが、いいですよこれ。 react.jsと同じようにVirtualDomサポートしつつ、その上でAPIが16しかない(ここ重要)という素晴らしいJavascriptMVCフレームワークです。

github.com

react.js少し使っていて、JS側にテンプレート書けるJSXはそこそこ気に入っていたんですが、 素のブラウザで普通には動かせないんで大分辛みを感じてました。 このmithrilなら、素のブラウザで動作しつつ、react.jsのいいところも使えて大変便利です。 ちなみに、JSXのようなものを提供する拡張もあるようです。MSXだったかな?

さて、このmithril、こんな感じでAjaxRequestを発行することができます。

var users = m.request({method: "GET", url: "/user"})
    .then(log)
    .then(function(users) {
        //add one more user to the response
        return users.concat({name: "Jane"})
    })

まあ、これまんま公式から引っ張ってきたやつですが、いわゆるスタンダードなJqueryと比較して、callbackがchainで書けたりできるのでめちゃ使いやすいです。 素晴らしい。

そんなm.requestを使って起こった問題について書いてみます。 ここからが本題。

Access-Control-Allow-Origin

今回ターゲットにしているサービスは、完全に自分と関係ないやつなので、まあ、こいつと遭遇することになるわけです。
"No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access."

最近のブラウザでは標準搭載されているCross-Domainの制限ですね。
今回は自分の作ったJSと、サービスの公開ドメインが異なっているので、このアクセス制限を食らってしまうわけですねー。

詳しい話はこのへん https://developer.mozilla.org/ja/docs/HTTP_access_control http://qiita.com/zaru/items/6e57ab83a8e0e40f2115



当然、mithril限定の問題ではなく、他のライブラリ使っても発生する問題なので、対応方法も豊富に公開されていました。ありがたい限りです( ・`ω・´)

必要な対応としては3つ

  1. Jsonp使う
  2. サーバー側でAccess-Control-Allow-Originを返してあげるようにする
  3. プロキシ経由させて、プロキシ側でサービスにリクエスト送りつつ、Access-Control-Allow-Originをクライアントに返す

今回の接続先は、自分が管理していないサービスなので、1、2では対応できません。当然ですね(´・ω・`) となると必然的に3の選択を取ることになります。

なぜ3が有効かというと、Cross-Origin Resource Sharingは、あくまでクライアントから通信するときの制限であるため、 3で強制的にAccess-Control-Allow-Originをレスポンスとして返して上げることで、この問題を防ぐことができます。


ちなみにjqueryであれば、jquery.xdomainajax.js というものを使えば簡単に解決可能です。やってることも同じかんじ。 とはいえ、Yahooapiを経由してリクエストぶん投げる方法で、Yahooさんの意向一つで使えなくなる怖さはあるので、今回は自力で対応しています。

今回は、ツール開発で使ってるcoffeeScriptをugilifyしてsourcemapをはき出すためにgulpを使っており、node.jsとは切っても切れない関係になっているので、node.jsでプロキシ作成することにしました。

node.jsでの実装はこんな感じ

リクエスト発行するHTMLと、サーバーのドメインが異なると、それだけで弾かれてしまうので、HTMLやjsもnodeで配信するようにしました。
これで、クライアントとサーバーが同一ドメインであることが満たされるので、問題なく動作するようになります。

before_destroy時点でdestroyされてしまう、dependentの罠

まずはこれを見て欲しい

# == Schema Information
#
# Table name: users
#
#  id   :integer          not null, primary key
#  name :string(255)
#

class User < ActiveRecord::Base
  has_many :articles, dependent: :destroy

  after_create do
    articles.import(%w"1 2 3 4 5".map{|text| Article.new(text:text)})
  end

  before_destroy do
    p articles.map(&:text)
  end
end
# == Schema Information
#
# Table name: articles
#
#  id      :integer          not null, primary key
#  user_id :integer
#  text    :string(255)
#

class Article < ActiveRecord::Base
  belongs_to :user
end

よくある1対多の関連を持ったモデルがありますよ、と。
さらに、dependent指定で、userが消えた時、関連するArticlesを消すようにしています。

userの方では、生成時にarticlesに複数の初期データを同時に作成するようにしています。
after_createのところ見てもらえば分かる通り、1〜5の文字を適当に突っ込んで5レコード作っています。 

ここまで前準備。

本題。。。。

さて、ここでarticlesを消す前に、他のテーブルに移すとか、ログにはき出すなりをやりたいとします。
当然dependent指定されているため、after_destroyではarticles拾えなくなるため、before_destroyで拾うとします。

user.rbみてもらうばわかる通り、before_destroyで標準出力に出してあげるようにしています。

User.create(id:13, name:1)
=> #<User id: 13, name: "test">

User.find(13).articles
=> [#<Article id: 26, user_id: 13, text: "1">,
 #<Article id: 27, user_id: 13, text: "2">,
 #<Article id: 28, user_id: 13, text: "3">,
 #<Article id: 29, user_id: 13, text: "4">,
 #<Article id: 30, user_id: 13, text: "5">]

User.find(13).destroy

>> []

ファッ!?

なぜか空配列が帰ってきてしまいます。 pry眺めてると先にarticlesのDeleteが走っていることがわかります。

ちなみに、こう書けば空配列にはなりません。

class User < ActiveRecord::Base
  before_destroy do # こちらの定義を先にした
    p articles.map(&:text)
  end
  has_many :articles, dependent: :destroy

  after_create do
    articles.import(%w"1 2 3 4 5".map{|text| Article.new(text:text)})
  end
end

>> ["1", "2", "3", "4", "5"]

なぜこのような挙動をするのか。
深くまで潜っていけば、has_manyにdependent: :destroyが指定されている場合、
lib/active_record/associations/builder/association.rbで以下のように、before_destroy時に関連するモデルを削除する処理が埋め込まれます。

def self.define_callbacks(model, reflection)
  add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
  Association.extensions.each do |extension|
    extension.build model, reflection
  end
end

さて、ここで思い出してもらいたい。
modelに対するcallbackは複数回指定することができます。

さっきのuser.rbにこれ追加します。

  before_destroy do
    p 1
  end
  before_destroy do
    p 2
  end

>> []
1
2

追加したbefore_destroyを逆にして再度実行すると…

[]
2
1

そうです、大体見えてみた。
先にbefore_destroyを定義した順番に実行されていくのです。

最初に記載していたコードを見てください。
has_manyの定義が先に書かれています。そのため、has_many内で追加されていたdependent: :destroyのbefore_destroyが先に実行されていたために、空配列になっていたのです(`・ω・´)

これで地味にハマりました。
でもrubyは上から順番に評価されていくので、原因が分かってさえすれば理屈はだいたいわかりますねー。

おわり。

肥大化するseeds.rbをうまい具合に管理する

seeds.rb中でif~elsifしたり、rakeタスクを環境分用意するなど、剛の者もいるようですが、それはあんまりにも辛みを予感させてしまいます。

個人的に楽でオススメなのが、環境毎のdirを切って、その下に実処理書いたファイル配置し、requireでごそっと開始させる方法。

root
 └ db
    ├ seeds.rb
    ├ seeds/
          ├ development/
          │ ├ users.rb
          │ └ ...~.rb
          ├ production/

こんな感じ。

規模が小さいなら、environmentごとのdir切らずに、直接ファイルおいても良いかも。

db/seeds.rbにはこんな感じの処理書いておく

seed_files_root="#{Rails.root}/db/seeds/#{Rails.env}"

Dir.glob("#{seed_files_root}/**.rb").each{|file| 
  require file  if FileTest.file?(file)
}

これによって、起動した時の環境に合わせたdirから、Seedするファイルが読み込まれて処理されるようになります。

というメモ。

Fabricインストール時に起きたDistributionNotFoundを解決する

OSX python2.7.5で起きたもの。

  • pipでインストール
  • インストールされていたparamikoは1.15.2

こんなエラー

Traceback (most recent call last):
  File "/usr/local/bin/fab", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 2603, in <module>
    working_set.require(__requires__)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 666, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 565, in resolve
    raise DistributionNotFound(req)  # XXX put more info here
pkg_resources.DistributionNotFound: paramiko>=1.10

最後の行に見て取れるように、単純に依存関係が解決できていない模様。 こういう時は、エラーに書かれているバージョンを入れると固い。

pip uninstall paramiko
pip install paramiko==1.10

これでOK( ・`ω・´)