tapを使って配列じゃなくてもeachする方法
処理対象がEnumerableな要素かそうでないかわからない時ってよくありますよね。
ActiveRecordなんかもそうですが、1要素の時は単体のオブジェクトがきて、複数要素の時はEnumerableが来るapiってわりとあります。(scanとかもそう。)
で、tapを使えばそれを解決できますよという話。
http://melborne.github.io/2012/10/29/rubys-new-control-structure-by-tap-and-break/
上記サイトがすべてを物語っていますが、tapの中でbreakを使うと、その値が返り値になることを利用します。
hoge.tap{|s| break [s] unless s.respond_to?(:each)}.each{|x| puts x}
#hogeは配列かどうかわからないが配列として扱いたい
まあ、tapを使わなくても普通に定義できます。
module Kernel
def wrap_a
self.respond_to?(:each) ? self : [self]
end
end
1.wrap_a.each{|x| puts x} # 1をeach
ActiveRecordのjoinsで全カラムを取得する方法 (Rails小ネタ)
ActiveRecordではjoinsメソッドを利用して、inner joinすることができる。
例:
Blog.joins(:article) #=> SELECT "blogs".* FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
ここで、問題になるのは"blogs".*である。articlesの情報はとれていない。
Includes Vs Joins in Rails - Nikhil Lingutla
よって、上記で解説されているように、.selectをチェーンしてやらないといけない。
極めて単純な解決策としては、以下のscopeを作成してやれば良い。
scope :joins_get_all_columns, ->(*tables) {
joins(*tables).select("*")
}
作成されるSQLは下記。
Blog.joins_get_all_columns(:article) #=> SELECT * FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
この方法には、同じカラム名があると、どちらかで上書きされる問題がある。
もう少し頑張って、下記のようなスコープを作成すれば、カラム名の重複は避けられると思われる。
scope :joins_get_all_columns, ->(*tables) {
select_stmt = [%{"#{self.table_name}".*}] # joinするテーブルは通常通りのカラム名
select_stmt << tables.map{|t|
ar = ActiveRecord::Base.const_get(t.to_s.classify.singularize)
table_name = ar.table_name
table_name_single = table_name.singularize
ar.attribute_names.map{|col|
%{"#{table_name}".#{col} as #{table_name_single}_#{col}} # "table名_column名"
}
}
joins(*tables).select(select_stmt.flatten.join(","))
}
作成されるSQLは下記。
Blog.joins_get_all_columns(:article) #=> SELECT "blogs".*,"articles".id as article_id,"articles".content as article_content,"articles".created_at as article_created_at,"articles".updated_at as article_updated_at FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
最後に、LEFT OUTER JOIN対応版。
scope :joins_get_all_columns, ->(*tables,outer) {
outer_join= outer && []
my_table = self.table_name
select_stmt = [%{"#{my_table}".*}]
select_stmt << tables.map{|t|
ar = ActiveRecord::Base.const_get(t.to_s.classify.singularize)
table_name = ar.table_name
table_name_single = table_name.singularize
outer_join << %{LEFT OUTER JOIN "#{table_name}" ON "#{table_name}"."#{my_table.singularize}_id" = "#{my_table}"."id"} if outer
ar.attribute_names.map{|col|
%{"#{table_name}"."#{col}" as "#{table_name_single}_#{col}"}
}
}
joins(outer_join || tables).select(select_stmt.flatten.join(","))
}
Blog.joins_get_all_columns(:article,false) #=> inner join
Blog.joins_get_all_columns(:article,true) #=> left outer join
Railsでアプリを作ってると、Railsをいじるのが楽しくなって脱線してしまうから困る。
Rubyでまとめて動的クラス定義
リファクタリングRubyエディションを読んでいたら、def_eachというイカしたメソッドがあった。まとめて、似たようなメソッド定義をするものだ。
ここで、まとめて似たようなクラス定義をする関数もあったらいいなと思ったので作ってみた。役立つか微妙だが*。
hierarchy階層に、super_classを親クラスとしたクラスが定義される。
def def_class_each(classes,hierarchy,super_class,&block)
hierarchy ||= Object
super_class ||= Object
classes.each do |clas|
next if hierarchy.const_defined? clas,false
c = hierarchy.const_set clas, Class.new(super_class)
c.class_exec(clas, &block)
end
end
こんな感じで使う。
def_class_each [:Hoge,:Fuga],nil,nil do |clas|
define_method("my") do |*args|
"This class is #{clas}."
end
end
puts Hoge.new.my #=> This class is Hoge.
puts Fuga.new.my #=> This class is Fuga.
*ActiveRecordでもクラス定義はクラスファイルを吐いていることから、あんまりクラス定義は動的にしないものなのかなぁとか思ったりもする。
redcar(Ruby)のオレオレ改造をしてみた。
https://github.com/redcar/redcar
便利なのだが、個人的に以下の不満があった。
(1) Ruby1.8系でしか動かない。Evalする時にRuby1.9系のHashの構文とか使えない
(2) 日本語ファイル名がNG。日本語ファイル(UTF-8)以外は文字化けする。
(3)ドラッグアンドドロップでファイルが読み込めない。
(4) 内部でEvalする機能のOutputがシェルに出たりしてイマイチ。あと、手軽にEvalしたい。
(5) クリップボード履歴機能があるが、RedcarにFocusがあるときしか履歴をためてくれない
(6) 前回状態(開いてたタブ)を復帰できない。
ということで、オレオレ改造をしてみた。
https://www.dropbox.com/s/62ge9h0gpetyzx4/redcar_v_ore.zip
ひどい改造の仕方なので、ソースは見ると辛い気持ちになるかもしれません。(テストもろくにしてない。例えば、1.9でlambda{}が動かなくなった箇所をProc.new{}に全置換したり..)
あと、Windowsでしか動きません!!さらに、Javaは32bitしか動きません。
64bitOSの方は、32bitのJavaの方にPathを通してください。(もしくは、JRubyの-d32オプション。試してないけど。)
JRubyの1.7系はインスト必須。redcar.batで起動できます。
前置きが長くてあれですが、以下の機能を追加しました。
(1) Ruby1.9系,2.0系で動く。(試していないが、1.8系では動かない)
(2) 日本語ファイルが読み込める。但し、保存すると強制的にUTF-8になる。
(3) ドラッグアンドドロップでファイルが読める。
(4) 簡単に内部でEvalできる。これ一押し!右クリック→Evalもしくは、EvalSelection(選択範囲)で、指定したコードをEvalできる。トップレベルでevalしてるので、ローカル変数も保持できる。irbの変化版みたいなもので、内部からredcarをいじくり回せる。
例えば、all_tab.each{|x| puts [x.title,x.document.path,x.text]*"\n"}
をEvalすれば、開いている全タブのタイトル、ファイルパス、テキストを表示できる。
(all_tabとtextは私が作った簡単なメソッド(エイリアス))
他に、Redcar::Top::OpenNewEditTabCommand.new.run
とすれば、新しいTabが開く。
ということで、色々と夢がひろがりんぐな機能となっている。
(5) 起動していれば、大体のコピーした履歴は残る。
(6) 前回開いていたファイルを次回起動時に開いてくれる。
(7) なんとなく不安定動きをする。いや、かなり不安定。
で、このオレオレRedcarの何がいいかというと、ある程度作ったスクリプトを、evalで試すみたいなことができる。irbだと対話形式なので、やりにくい場合に便利。
ということで、全体的にあんまりいい出来ではないけれど、結構頑張ったのでこっそりブログの中だけで公開します...。自分の環境固有のコードを入れてて、他の環境だと動かなかったりするかも。(デフォルトフォントをメイリオにしてたり。)
Excel VBA To Ruby Converter
手抜きプログラムシリーズ。
Excel VBAが吐き出したコードをRubyのWin32ole対応のコードに変換するスクリプトを作成した。やっぱ、ExcelやるならRubyでしょ的な。
https://www.dropbox.com/s/jw51apxtrgjle3k/vba2ruby.rb
基本的な文法(SubとかWithとかForとか)は変換できるが、LEFTとかMIDとかの関数の変換は面倒なので対応していない。
実装は、オープンクラスを結構している&超絶適当な正規表現置換でお察しな感じ。
簡単に説明すると、
・withはinstance_evalで解決。
・他構文は頑張って変換。(ここ参考。http://slingfive.com/pages/code/scriptConverter/)
・大文字変数は、snakecaseに変換。
・名前付き引数はHashに変換。
・全体を WIN32OLE.connect('#{path}').Application.instance_eval do |_| で囲むことで、Applicationオブジェクトのメソッドをグローバル化。
・後は、ほそぼそした対応。手前に、_.付けたり、()つけたり。
どんな感じになるかというと、
require './vba2ruby.rb'
WIN32OLE.convert_vba_to_ruby(s,"C:\\hoge.xls") #sはvbaコード
を実行すると、
Range("C9").Select
ActiveCell.FormulaR1C1 = "fd"
Range("C9").Select
With Selection.Font
.Color = -16776961
.TintAndShade = 0
End With
が
WIN32OLE.connect('C:\hoge.xls').Application.instance_eval do |_|
Range("C9").Select
ActiveCell().FormulaR1C1 = "fd"
Range("C9").Select
with Selection().Font do |_1|
_1.Color = -16776961
_1.TintAndShade = 0
end
end
となる。単純な変換とは行かなくて、()がついたり、_1がついたりしている。(大文字メソッドは括弧を抜いてしまうと定数と判断されてしまう...)
あと、connectなので、対象のエクセル(上記だと、C:\\hoge.xls)を開いてる状態で実行する必要あり。
eval(WIN32OLE.convert_vba_to_ruby(s,"C:\\hoge.xls"))で直接変換結果を実行できる。
postgreSQL RC 9.3 インストール
http://www.enterprisedb.com/products-services-training/pgdevdownload
に行く。
chmod 755 postgresql-9.3.0-rc1-linux.run
で権限を付けて、
./ postgresql-9.3.0-rc1-linux.run
すれば、Windowsのインストーラっぽいのが出てくるので、Nextを連打すればインスト完了。pgadmin3は自動的にインストールされている模様。
終わったら、拡張パッケージを入れるか聞いてくるので、phppgadminくらいは入れておく。
偉そうに書いてるけど、PostgreSQL使ったことないので、ご容赦ください。