Rubyにpackage_privateを導入しようとしたら
Rubyのメソッドの可視性はpublic, protected, privateの3種類です。そこに同一パッケージ(イコール同一gem?)からのみ呼べるpackage_privateを導入してみましょう。
結論から言うと断念しました。
以下のような場合は簡単です。
class String def nya "#{self}にゃ" end package_private :nya end
privateなどでもお馴染の引数付きでpackage_privateを呼んだ場合です。これは指定されたメソッドを同一パッケージから呼ばれたか判断するメソッドで包んで、同一パッケージからの呼び出し(ここではcallerで判断しました)じゃない場合はNoMethodErrorを投げる、でおっけー。
ではなぜ断念したか。以下のようなケースだと雲行きが怪しくなってきます。
class String package_private def nya "#{self}にゃ" end private def pyon "#{self}ぴょん" end end
この場合、privateが呼ばれたら、package_privateを解除したいわけです。ってことはprivateの定義も上書きしなくちゃいけない?
で、本格的に断念したのが以下のケース。
class String package_private end class String def nya "#{self}にゃ" end end
この場合のnyaメソッドはデフォルトの可視性であるpublicで定義されるべきなのですが、package_privateを解除する術が思い付かず、断念って感じです。ここらへんからはCの世界なので、CRubyのコードも読んでみたのですが、組み込むのはなかなかしんどそう。うむー。
追記(2013-04-16 08:10)
と、こんな記事を書いておいたら、これを読んだ@kyubingから神に等しきコードが飛んできました。
最初パッと見たところ、2度目のclass Nekoでpackage_privateが有効になりっぱなしにならないかな〜と思いましたが、そこらへんがRubyの妙のようで。
Rubyはインスタンスメソッドが追加されると、Module#method_added(method_name)がコールされます。そのときのバックトレースは以下のような感じになるみたいです。
ファイル名:メソッドが定義された行:in `<class:クラス名>' ファイル名:クラスの定義が開始された行:in `<main>' # mainかはクラスを定義した場所に依る
Class.newやclass_evalを使うともう少し違うバックトレースになりますが、それでも基本的に「何行目でクラスの定義が開始されたか」という情報は取れます。
と言うことは、完全に断念したというケースでも判断が付きそうですね! すごい! @kyubing様々です。また改めてコードにしてみようと思います。
Rubyで任意のメソッドの呼び出しを記録するgemを作った
タイトルの通り。
インストールは:
$ gem install peeek
で。処理としては対象のメソッドを呼び出し記録用のメソッドで包んでいるだけです。使い方はREADME.mdなりをご覧ください。が、一部書き漏らしていることがあり、それをここに書いておきます。
peeekコマンド
一応コマンドが付属しており、コマンドラインからでも簡単に試すことが出来ます。
$ peeek -v
peeek-1.0.2
$ peeek -H'Kernel#puts' -e 'puts "Hello World"'
Hello World
Kernel#puts from main with "Hello World" returned nil in -e at 1
ちょっと試したいときに便利。かも。
何に使うんだーって言われると何かに使えるかもね、としか答えられない思い付きgemなんですが、Pull Requestなどがあればどしどしください。
httpstatusコマンドで、HTTPのステータスコード(
一般的なWeb Programmerならば、HTTP Status codeはすべて暗記していると聞きました。
しかし、僕は初心者なので、なかなか覚えきれていないので、HTTPのステータスコードをさがすのに便利なツールを用意しました。httpstatus.erlです。インストール方法は:
$ wget https://gist.github.com/takkkun/5002968/raw/569c3e3ea98d1cd8c17a9529c12ac8a88c97350a/httpstatus.erl $ chmod +x httpstatus.erl
です。escript用に書いてあるのでコンパイルとか必要ありません。
$ ./httpstatus.erl 4 400: Bad Request 401: Unauthorized 402: Payment Required 403: Forbidden 404: Not Found 405: Method Not Allowed 406: Not Acceptable 407: Proxy Authentication Required 408: Request Timeout 409: Conflict 410: Gone 411: Length Required 412: Precondition Failed 413: Request Entity Too Large 414: Request-URI Too Large 415: Unsupported Media Type 416: Request Range Not Satisfiable 417: Expectation Failed 418: I'm a teapot 422: Unprocessable Entity 423: Locked 424: Failed Dependency 425: No code 426: Upgrade Required 428: Precondition Required 429: Too Many Requests 431: Request Header Fields Too Large $ ./httpstatus.erl 40 400: Bad Request 401: Unauthorized 402: Payment Required 403: Forbidden 404: Not Found 405: Method Not Allowed 406: Not Acceptable 407: Proxy Authentication Required 408: Request Timeout 409: Conflict $ ./httpstatus.erl 400 400: Bad Request $ ./httpstatus.erl Not 304: Not Modified 404: Not Found 405: Method Not Allowed 406: Not Acceptable 416: Request Range Not Satisfiable 501: Not Implemented 505: HTTP Version Not Supported 510: Not Extended
以下コード。
#!/usr/bin/env escript main([Query|_]) -> case matcher(Query) of {ok, Matcher} -> print(lists:filter(Matcher, http_statuses())); {error, {Message, Position}} -> io:format("Error: ~s at ~p character~n", [Message, Position]) end; main([]) -> print(http_statuses()). matcher([C|_] = Query) when $0 =< C andalso C =< $9 -> case re:compile("^" ++ Query) of {ok, Regexp} -> {ok, fun({Code, _}) -> re:run(Code, Regexp, [{capture, none}]) =:= match end}; {error, ErrorSpec} -> {error, ErrorSpec} end; matcher(Query) -> {ok, fun({_, Message}) -> string:str(Message, Query) > 0 end}. print(HttpStatuses) -> lists:foreach(fun({Code, Message}) -> io:format("~s: ~s~n", [Code, Message]) end, HttpStatuses). http_statuses() -> [ {"100", "Continue"}, {"101", "Switching Protocols"}, {"102", "Processing"}, {"200", "OK"}, {"201", "Created"}, {"202", "Accepted"}, {"203", "Non-Authoritative Information"}, {"204", "No Content"}, {"205", "Reset Content"}, {"206", "Partial Content"}, {"207", "Multi-Status"}, {"208", "Already Reported"}, {"300", "Multiple Choices"}, {"301", "Moved Permanently"}, {"302", "Found"}, {"303", "See Other"}, {"304", "Not Modified"}, {"305", "Use Proxy"}, {"307", "Temporary Redirect"}, {"400", "Bad Request"}, {"401", "Unauthorized"}, {"402", "Payment Required"}, {"403", "Forbidden"}, {"404", "Not Found"}, {"405", "Method Not Allowed"}, {"406", "Not Acceptable"}, {"407", "Proxy Authentication Required"}, {"408", "Request Timeout"}, {"409", "Conflict"}, {"410", "Gone"}, {"411", "Length Required"}, {"412", "Precondition Failed"}, {"413", "Request Entity Too Large"}, {"414", "Request-URI Too Large"}, {"415", "Unsupported Media Type"}, {"416", "Request Range Not Satisfiable"}, {"417", "Expectation Failed"}, {"418", "I'm a teapot"}, {"422", "Unprocessable Entity"}, {"423", "Locked"}, {"424", "Failed Dependency"}, {"425", "No code"}, {"426", "Upgrade Required"}, {"428", "Precondition Required"}, {"429", "Too Many Requests"}, {"431", "Request Header Fields Too Large"}, {"449", "Retry with"}, {"500", "Internal Server Error"}, {"501", "Not Implemented"}, {"502", "Bad Gateway"}, {"503", "Service Unavailable"}, {"504", "Gateway Timeout"}, {"505", "HTTP Version Not Supported"}, {"506", "Variant Also Negotiates"}, {"507", "Insufficient Storage"}, {"509", "Bandwidth Limit Exceeded"}, {"510", "Not Extended"}, {"511", "Network Authentication Required"} ].
最近のMeCabの使い方
MeCabはC/C++で書かれた形態素解析を行うライブラリなのですが、ちょっと疑問に思うところがあって、以下のようなコードを書きました。
require 'MeCab' mecab_options = '-Owakati' 1000.times do |n| begin tagger = MeCab::Tagger.new(mecab_options) # do something with tagger rescue => e raise "failed at #{n + 1} times, [#{e.class}] #{e.message}" end end
マシンスペックに依りますが、大体例外が発生します。MeCab::Taggerのインスタンスが破棄されているにも関わらず! 僕のMacBook Air(メモリ2GB)で試したところ、"failed at 70 times, [RuntimeError] "と、大体70回MeCab::Tagger.newを呼んだところで落ちます。例外の内容は不明。
プロセスどうなってんのと見てみると、メモリを13GBほど消費してました。ひどい。多分mallocに失敗とかそんなところでしょうか?
どうやらMeCab 0.99からマルチスレッドに対応したようで、本家のドキュメントのコード例も変わってました。要はマルチスレッドでMeCab::Taggerのインスタンスをもりもり作る場合はMeCab::Modelのインスタンスを作り、そこからMeCab::Taggerのインスタンスを生成しろよ、ってことみたいです。もしMeCab::Taggerのインスタンスを直接生成すると、そのたびにMeCab::Modelのインスタンスが生成されるので、メモリを圧迫し、死亡、という流れのようですね。
ということでMeCab 0.99をインストールしている環境では以下のように書けば大丈夫みたいです。
require 'MeCab' mecab_options = '-Owakati' mecab_model = MeCab::Model.create(mecab_options) 1000.times do tagger = mecab_model.createTagger # do something with tagger end
ちなみにMeCab::Taggerをひとつしか生成しないような環境であれば、MeCab::Taggerから直接インスタンスを生成しても構いません(コード例でもそう書いてある)。
ErlangでTwitterのUserStreamを受け取る
以前もErlangでTwitter Streaming APIを使うといったエントリを書いたのですが、いかんせん情報が古すぎます。UserStreamではなく、素のStreaming APIなのはともかく、認証がベーシック認証だったりします。
その割にはどうやら最近参照されているらしい。http://naoyat.hatenablog.jp/entry/2012/01/04/220639やhttp://d.hatena.ne.jp/siritori/20120312/1331503357には以前のエントリのURLが貼られているようで。いや、なんかすみません。
ということで、ちゃんと動くかつOTPで書き直してみました。erlang-oauthに依存しています。
-module(userstream). -author("Takahiro Kondo <heartery@gmail.com>"). -export([start/5, start/6, start_link/5, start_link/6, stop/1]). -behavior(gen_server). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {id, processor}). start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) -> start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []). start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) -> Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret], gen_server:start(?MODULE, Args, Options). start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) -> start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []). start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) -> Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret], gen_server:start_link(?MODULE, Args, Options). stop(Server) -> gen_server:cast(Server, stop). %% callback functions init([Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret]) -> Url = "https://userstream.twitter.com/2/user.json", Consumer = {ConsumerKey, ConsumerSecret, hmac_sha1}, Options = [{sync, false}, {stream, self}], case oauth:post(Url, [], Consumer, AccessToken, AccessTokenSecret, Options) of {ok, Id} -> {ok, #state{id = Id, processor = Processor}}; {error, Reason} -> {stop, {http_error, Reason}} end. handle_call(_, _, State) -> {noreply, State}. handle_cast(stop, State) -> {stop, normal, State}. handle_info({http, {Id, stream_start, Headers}}, #state{id = Id, processor = Processor} = State) -> send(Processor, {start, Headers}), {noreply, State}; handle_info({http, {Id, stream, <<"\r\n">>}}, #state{id = Id} = State) -> {noreply, State}; handle_info({http, {Id, stream, Part}}, #state{id = Id, processor = Processor} = State) -> send(Processor, {stream, Part}), {noreply, State}; handle_info({http, {Id, {error, Reason}}}, #state{id = Id, processor = Processor} = State) -> send(Processor, {error, Reason}), {stop, {http_error, Reason}, State}. terminate(_, #state{id = Id, processor = Processor}) -> send(Processor, stop), httpc:cancel_request(Id). code_change(_, State, _) -> {ok, State}. %% private functions send(To, Message) -> To ! Message.
ちゃんと動くかは確認しましたが、process_flagとかは呼んでいないのでそこらへんは適当に。gen_serverですので、ちゃんと設定すればそこまで手こずることなくsupervisor treeに組込めるかと思います。
本当はuserstreamモジュールをさらにビヘイビアにして、handle_status/2, handle_favorite/2とかで各イベントをハンドリングできるようにするといいんですが、それをやるとちょっと複雑になるので、そこまではやりません。
ちなみに使い方はこんな感じで。
-module(example). -author("Takahiro Kondo <heartery@gmail.com>"). -export([start/0, stop/1]). start() -> Processor = spawn(fun() -> process() end), ConsumerKey = "Your consumer key", ConsumerSecret = "Your consumer secret", AccessToken = "Your access token", AccessTokenSecret = "Your access token secret", userstream:start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret). stop(Pid) -> userstream:stop(Pid). process() -> receive {start, Headers} -> io:format("Start: ~p~n", [Headers]), process(); {stream, Part} -> io:format("Stream: ~p~n", [Part]), process(); {error, Reason} -> io:format("Error: ~p~n", [Reason]), process(); stop -> io:format("Stop~n") end.
$ erl -s inets -s ssl > {ok, Pid} = example:start(). ここにUserStreamからの応答が表示される(example:process/0で標準出力に吐き出してるため) > example:stop(Pid). Stop >
OTPを使いつつ複雑すぎない書き方をしてみました。必ずしもOTPを使う必要はないですし、メリットばかりでもないんですが、アプリケーションがある程度複雑になってきたら使った方が良いかなと思います。それこそ書き捨てのコードでは不要でしょうが、あのプロセスが動いて、こっちであーでどーで、とかで頭のリソース割かれるならOTPを学ぶ価値はアリかなと。
余談ではありますが、余力があれば自作のtwitterモジュールをGitHubにでもあげておきたいもんですね。それにはTwitterのREST APIはもちろん、先述したUserStream用ビヘイビアも書いてはあるんですよ。ただ随分前からメンテナンスをサボってるので、REST APIが古過ぎるという感じで…… なんか一から書いた方が早そう。
autotest-twitterでブヒる
最近とあるgemを書きながら付随するgemを書いてて優先すべきそれが中々進まない昨今ですこんばんは。
で、恥ずかしながらテストファーストってあんまりやったことなくて、今それを実践しながらの開発をしています。使っているツールはRSpecなんですが、コマンドひとつでテストできるとは言っても、今度はそのコマンドを実行するのがめんどくさい。ひたすら怠惰な生き物ですね。
そういう生き物たちにうってつけなのが当然あって、それのひとつにautotestってのがあります。しばらくはautotest + autotest-growlで開発してたんですが、家で使ってるマシンはMac、職場で使ってるマシンはUbuntuなんですね。Macには当然Growlをインストールしてるんですが、UbuntuとなるとGrowl以前の問題です。なので「Twitterにテストの結果をツイートして、あとは各OS向けのTwitterクライアントから通知すればいいんじゃね」と思い至りました。というわけでautotest-twitterです。まあ後からよく調べたらautotest-growlはLinuxにも対応してましたけどね。ちくしょう。
使い方
README読めと言いたいところですが、GitHubに置いてあるのがいい加減なのでアレです。何がアレってテストを書いていないところですよね。まあとりあえずテストの対象となるアプリケーションなりライブラリが置いてあるディレクトリに.rspecを作り:
--format nested --color
を、.autotestに:
require 'autotest-twitter' Autotest::Twitter.configure do |config| # ツイートするアカウントのアクセストークンを設定 config.consumer_key = 'your consumer key' config.consumer_secret = 'your consumer secret' config.oauth_token = 'your access token' config.oauth_token_secret = 'your access token secret' # ラベル。アプリケーションの名前とか config.label = 'any application' # テストの結果に応じてアイコンを変えられるので、そのアイコンが # 置いてあるディレクトリ # - missing.png: テスト自体がない場合のアイコン # - failed.png: テストに失敗した場合のアイコン # - pending.png: ペンディングが存在する場合のアイコン # - passed.png: テストに成功した場合のアイコン config.image_dir = 'path/to/icons' # テストの結果に応じたツイートの内容。$で始まるのは変数 # - $label: config.labelで設定した内容 # - $all: テストの全件数 # - $failed: 失敗したテストの件数 # - $pending: ペンディングしてるテストの件数 config.missing_messages = ['$labelのテストが存在しないよ'] config.failed_messages = ['$labelのテストに失敗したよ。$all件中$failed件がダメみたい'] config.pending_messages = ['$labelのテストに$pending件のペンディングがあるよ'] config.passed_messages = ['$labelのテストに成功したよ! $all件あったみたいだね'] end
を、Gemfileに:
source 'https://rubygems.org' gem 'autotest' gem 'autotest-fsevent' gem 'autotest-twitter', :git => 'git://github.com/takkkun/autotest-twitter.git'
こう。で:
$ bundle --path vendor/bundle
でもしてautotest諸々をインストール。後は:
$ bundle exec autotest
でテストを開始。後はファイルに変更があるたびにテストが走り、結果に応じてツイートされるはずです。config.image_dirを設定してればアイコンも変わります。ちなみにRSpecでしか試していませんし、とりあえず動いてるっぽいってことしか確認してないのであしからず。
ちなみに僕は @Shinobu_DD で試していました。まるでアイコンセットのような画像(TVアニメ偽物語の一部でしょうが)があったので。でもまあ「$labelで$pending件ペンディングがあるようじゃな。お前様の生き様が垣間見えるの。かか」とか打ってると頭抱えたくなりますし、いざブヒろうにも全然テンション上がらないのであんま向いてなかったようです。ていうか元々そういうのじゃないし!
まあでもブヒろうと思えばブヒれるので、テストがコケたらツンツンされたり、テストが通ったらデレデレされたりして、「今日も開発がんばりましゅううう」とか言ってればいいんじゃないですかね。
あとさっき思い付いたんですけど、ツイートするアカウントを自分のアカウントにし、passed.pngをいつも使ってるアイコン、failed.pngをとてつもなく恥ずかしいアイコンにすると面白いんじゃないかと思います。はやくテスト通さないとエラい思いをするハメになるというマゾい開発が出来ていいんじゃないかナーーー。
Exporterでエクスポートされる関数の挙動を変更する
ひょんなところにこんなモジュールがあRuby。
package Hoge; use strict; use warnings; use base qw/Exporter/; our @EXPORT = qw/hoge/; sub hoge { print "Hoge::hoge called\n"; } 1;
このHogeモジュールのhoge関数を呼び出すためにこんなコードを書Chrome。
use strict; use warnings; use Hoge; hoge; # Hoge::hoge called
いろんな事情が絡んで、Hoge::hoge関数の挙動を変更したEthernet。
use strict; use warnings; use Hoge; { no warnings 'redefine'; local *Hoge::hoge = sub { print "anon called" }; hoge; } hoge; # Hoge::hoge called
一度目のhogeでanon calledと出力したいのだけど、これはHoge::hoge calledと出力されちゃWindows*1。
Exporterを使うと、エクスポート対象となるパッケージ(main)に指定された関数がコピーされているので、元の関数(Hoge::hoge)を書き換えてももはや手遅Rails。
でもどの名前空間にあるhogeが呼ばれるか分からない時もある訳Debian。コンパイル時に名前を解決するためにエクスポートしているけど、実際呼び出す時は必ず書き換えるようにしたりする訳Delphi。
ということでこうすれば良いんじゃないNode.js。
package Hoge; use strict; use warnings; use base qw/Exporter/; our @EXPORT = qw/hoge/; sub hoge { _hoge(@_) } sub _hoge { print "Hoge::hoge called\n"; } 1;
use strict; use warnings; use Hoge; { no warnings 'redefine'; local *Hoge::_hoge = sub { print "anon called" }; hoge; # anon called } hoge; # Hoge::hoge called
間接的に呼べば問題ないようDarwin。関数呼び出しのオーバヘッド増えるけど見ないふLisp。
おっPython。
*1:悔しい
単数形/複数形の変換ルールを独自に定義する
必要に迫られたので探してました. 案の定用意されたメソッドで好き勝手できるようになってました.
Rails御用達のActiveSupportの場合.
require 'active_support' require 'active_support/inflector' # Railsは自動で取り込んでくれるだろうけど, ActiveSupportを単体で使う場合は取り込んでくれないみたいです ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'octopus', 'octopi' end ActiveSupport::Inflector.inflections.irregular 'octopus', 'octopi' # コレでも一緒
イレギュラーなケース以外にもいろいろと定義できるので, ActiveSupport::Inflector::Inflectionsのドキュメントなり読みましょう.
Sequelもモデル名とテーブル名の変換に同様の仕組みを用いてる.
require 'sequel' Sequel.inflections do |inflect| inflect.irregular 'octopus', 'octopi' end
Emacsからrakeを呼び出してみた
最近「プログラミングしてないなぁ……」とか思っちゃったので, 試しにTwitterのUser Streamsを使ったプログラムを書こうとしたのですが, ErlangからUser Streamsが使えなかったので*1別のプログラムに逃避した次第です, こんにちは. id:siritoriごめん.
で, 何作ろうかと考えてたら, 「そういえばEmacsからrake呼び出したいなぁ. あるのかなぁ. ありそうだよねぇ」とふと思い立った. 調べると, testタスクを実行するのはあっても, その他タスクを実行するのがなかったので, ちょっとだけ作ってみた.
(defvar rake:output-buffer "*rake output*") (defun rake:invoke () (interactive) (let ((task-suggestions (rake:task-suggestions))) (if task-suggestions (rake:run (completing-read "rake " task-suggestions)) (message "Not found target")))) (defmacro rake:deftask (task) (list 'defun (prog1 (intern (concat "rake:run-" task))) '() '(interactive) (list 'rake:run task))) (defmacro rake:task (task) (list 'lambda '() '(interactive) (list 'rake:run task))) (defun rake:task-suggestions () (reverse (reduce #'(lambda (suggestions line) (if (string-match "rake \\([^ ]+\\)" line) (cons (match-string 1 line) suggestions) suggestions)) (split-string (shell-command-to-string "rake -T") "[\r\n]+") :initial-value nil))) (defun rake:run (task) (call-process-shell-command (format "rake %s" task) nil rake:output-buffer))
使い方は.emacsとかに:
(require 'rake) (global-set-key "\C-cr" 'rake:invoke) (global-set-key "\C-cRd" (rake:task "default")) ;; C-c R dでdefaultタスクを実行する (rake:deftask "test") ;; testタスクを実行する関数rake:run-testを定義し, (global-set-key "\C-cRt" 'rake:run-test) ;; C-c R tでrake:run-testを呼び出す
な感じで. 一応タスク名を補完するようにしてあるけども, それをちょっとがんばったくらいで他がガタガタ. 後々ちゃんとさせる.
Lispは不慣れなので, 「この書き方よりもこっちの方がいいよ」とかあったら突っ込んでいただけると嬉しいです.
自分用メモ
- タスク名の一覧をキャッシュさせる(rake -Tの呼び出しに微妙に時間がかかるため). Rakefile(*.rake)のパスと更新時刻, タスク名一覧を持っておけばよさげ
- となると, Rakefile(*.rake)の探索をrakeと合わせておかなければならない
- *rake output*バッファにどんどん書き込まれるので, rake:runするたびにクリア
- キーバインドの設定を簡潔に記述できるようにする
- 直前に実行したタスクを記憶しておく. コレはRakefile(*.rake)ごとに記憶した方がいい気がする
あとなんかあるかなぁ……
*1:「使えたよ」という方がいたら教えていただけると嬉しいです
ファイルの非同期アップロードを実装してたら, IEでいっぱい怒られたよ
すげー怒られたので, 逆ギレのごとくここにご報告いたします. 気分は先生に怒られた小学生.
「同一ドメインなのにiframeのdocumentを取得出来ませんよ!?」「うっせーなー」
非同期アップロードはform要素のtarget属性を任意のインラインフレームにして実装するのが定石だと思います. なので結果は指定したインラインフレームの中にあるため, documentを取得しないと結果がわかりません. ちなみに取得方法は:
var iframe = document.getElementById('iframe-id'); iframe.onreadystatechange = function() { // IEはiframe要素でloadイベントが発生しないらしい(IE8だと普通に発生してたけど) if (iframe.readyState == 'complete') { var doc = iframe.contentWindow.document; // contentWindowを経由して取得 } };
でしょうか. IE8で試してたので, contentDocumentもありましたが.
当然異なるドメインであれば, Same origin policyが適用され, アクセスが拒否されるのですが, 同じドメインでもアクセスが拒否されました.
で, どうやらレスポンスのステータスコードが200以外だと, documentプロパティを参照したときに「アクセスが拒否されました」と言われるようです. エラーならステータスコードを適切な内容にしたいのですが, それは許されないようですね. 先生厳しすぎ.
とりあえず回避するにはステータスコードを200にしなければいけないので:
status 200
って書いておきました. あ, サーバー側にはSinatra使ってたので.
「アップロードするファイルが選択されていませんよ!?」「あー, だりぃ」
アップロードするファイルを選択するためには, <input type="file" />を設けなければいけません. で, 問題なのはコイツのvalue属性. どうやらJavaScriptから設定するコトが出来ず, 閲覧者の操作によってでしか設定出来ないようです. 先生頭固すぎ.
実装としては元々あるフォームをコピーしてたのですが, そんなコトする必要もないと思い, 元々あるそのフォームのtarget属性をインラインフレームにして対処しました*1.
「アップロードしたファイルがJPEGじゃありませんよ!?」「帰りたーい」
アップロードできるファイルはJPEG, GIF, PNGのいずれかだったのですが, なぜかIEでJPEGをアップロードしても不正な形式扱い.
実装としてはMIMEタイプで見てたので, image/jpeg, image/gif, image/pngのいずれかだったら正しい形式と判断してたのですが, IEからJPEGをアップロードすると, MIMEタイプはimage/pjpeg…… pjpeg(・∀・`)?
なんか独自実装らしいです. 先生自由すぎ.
とりあえず許可するMIMEタイプにimage/pjpegを追加して事なきを得ました.
以上, 小学生のご報告でした.
*1:なぜわざわざフォームのコピーを作成してたかは謎