twitter gemのupdate_with_mediaで”The IO object for media must respond to to_io”エラー

スポンサーリンク

Ruby ライブラリの twitter gem を使って、URLを開いて画像とテキストを同時に投稿するコードを書いていて遭遇したエラーが、表題の “The IO object for media must respond to to_io” です。gem twitter が投げる例外メッセージで、update_with_media の引数として渡すIOオブジェクトに to_io メソッドがない場合に発生します。

先日作っていた Twitter bot の続きの作業中でした。
RailsでTwitter botを作成 | EasyRamble
https://github.com/sferik/twitter

— 環境 —
rails 4.1
ruby 2.1.2
twitter 5.13.0

【お知らせ】 英単語を画像イメージで楽に暗記できる辞書サイトを作りました。英語学習中の方は、ぜひご利用ください!
画像付き英語辞書 Imagict | 英単語をイメージで暗記
【開発記録】
英単語を画像イメージで暗記できる英語辞書サービスを作って公開しました
スポンサーリンク

表題エラーが発生したコード(省略版)

当初以下のようなコードを書いていまして、media_url には画像ファイルのURLを渡す。

lib/tasks/twitter.rake

この時、media_url の画像ファイルのサイズが10kb以下と小さい場合に、表題の例外エラーが発生します。

この理由は、ruby の open-uri の open メソッドが、対象ファイルが10kb以下の場合は Tempfile ではなくて StringIO のオブジェクトを返し、gem twitterの update_with_media が to_io メソッドを持たない StringIO オブジェクトを受け付けないため。

以下に情報があります。open-uri と twitter gem のソースも読んだけど、実際そうなってました。

開いたファイルオブジェクトは StringIO もしくは Tempfile ですが…
if size of image is less than 10kb then open(photo_url) will give you StringIO object, if size is more then 10kb then – File object which is saved in tmp/ folder. File object respond’s to to_io method and StringIO object doesn’t.

StringIO は IO オブジェクトのように扱える文字列。open-uri の open が10kb以下の場合に StringIO を返すのは、サイズが小さい場合はわざわざ tempfile としてファイルに書き出さずに、オンメモリで処理するためでしょうかね。

irb でも確認。

ということで、10kb以下の小さい画像ファイルは StringIO オブジェクトになるので、このままでは update_with_media で投稿できない。

StringIOの場合、Tempfileのオブジェクトを作成(上手くいかず)

そこで、StringIO じゃなくて Tempfile のオブジェクトを作成すれば良いだろうと考えました。

StringIO to Tempfile? – Ruby Forum

あたりを参考にして、open したファイルが StringIO オブジェクトで返った場合、Tempfile のオブジェクトを作成し、それを update_with_media に渡す以下のようなコード(省略版)を書いた。

しかし tempfile の生成までは確認できたのですが、今度は次の例外エラーが発生して、またしても投稿できず…

投稿の際に tempfile が読みこめないのだろうか… 原因はよく分からず、もう1つの代替案を思いついたので深追いはしませんでした。

【追記 2016/12/06】
rince さんから、

tf.write(media.read)
の後に
tf.rewind
を追加してあげるとうまく行きそうです。
 

というコメントを頂きました。情報ありがとうございます。tempfile を使う方法で上手くいかない場合は、ご参考にされてください。
【追記ここまで】

open-uri の OpenURI::Buffer::StringMax を変更するモンキーパッチ

open-uri のソースを読んだところ、OpenURI::Buffer::StringMax = 10240 と設定してあります。これが10kb以下の場合に、open が StringIO オブジェクトを返すための設定値、10240バイト = 10kb。

https://github.com/ruby/ruby/blob/trunk/lib/open-uri.rb

385行目辺り。その以降でサイズが StringMax を超える場合、tempfile オブジェクトを生成するようになってる。

ということで、以下を参考にして OpenURI::Buffer::StringMax の設定値を 0 に変更するモンキーパッチを行う。

Twitter, update_with_media, and the nasty Twitter::Error::Unauthorized (Could not authenticate you) issue – Nobody Listens Anyway

モンキーパッチで OpenURI::Buffer::StringMax の設定値を変更したコード

このコードで無事に10kb以下の画像ファイルでも tempfile を生成するようにでき、update_with_media で投稿できました。

あまり乱用はしたくない方法ですけど、苦労した割には最終的な変更はモンキーパッチのための2行追加だけという…。プログラミングではまった時って、まあこんなもんです。まだまだRuby力が足りません。

スポンサーリンク
私は以下の本で Ruby を覚えました。メタプログラミングRubyは入門を超える内容で難しめです。
 
スポンサーリンク
  • 2件のコメント
  • rince

    ちょうど同じ問題に引っかかって参考にさせていただきました。
    ありがとうございます。

    > StringIOの場合、Tempfileのオブジェクトを作成(上手くいかず)

    tf.write(media.read)
    の後に
    tf.rewind
    を追加してあげるとうまく行きそうです。

    • taka

      rince さん、コメントありがとうございます。
      あ〜なるほどです。rewind 使うと良いのですね。情報ありがとうございます!

Leave Your Message!