tkm9

自分向け技術メモ(?)

(続き)railsで、model 直下に rbファイルと、同名のディレクトリがある際に挙動がおかしい

こないだ書いたこの記事の続き。

おさらい

※前回からモデル構造ちょっと変えてます。

Model

以下の3モデルを用意。

  • app/models/foo.rb (Foo)
  • app/models/bar.rb (Bar)
  • app/models/foo/bar/baz.rb (Foo::Bar::Baz)
Controller

1つコントローラを用意。
アクションは2つ。indexindex2 アクション。

class FooController < ApplicationController
  def index
    Foo::Bar::Baz
  end

  def index2
    Bar
    Foo::Bar::Baz
  end
end

問題

development モードにて、先に Bar が読み込まれていると、Foo::Bar::Baz が autoload されない。
つまり、index2 アクションでエラーになる。

  • rails起動
    • index にアクセス -> OK
  • rails起動
    • index にアクセス -> OK
    • 続いて、index2 にアクセス -> OK
  • rails起動
    • index2 にアクセス -> uninitialized constant Bar::Baz
解決策

Bar が読み込まれる前に Foo::Bar::Baz を読み込む。

$ vi config/initializer/const.rb
require_dependency File.expand_path('../../../app/models/foo/bar/baz.rb', __FILE__)

これで問題ないように思えたが、しかし...

全然解決してなかった話

開発してるなかで再度同じエラーを見かけるようになった...。
最初再現パターンがよくわからなかったので放置してましたが、わかりました。

こうです。

ソースコードを書き換えるとダメみたいですね...

どういうことか

railsActiveSupport::Dependencies の機能にて、自動で読み込んでない定数(クラス)を探して読み込んでくれます。
ActiveSupport::Dependencies で読み込んだ定数は、設定によりますが、developmentモードのデフォルトではソースコードの更新が検知されると全てクリアされます。

前回、config/initializers に ActiveSupport::Dependencies がうまく読み込んでくれないクラスを手動で require_dependency させることで解決を試みましたが、こいつもあわせてクリアされてしまい、かつ、config/initializers は起動時にしか読み込まれないため、ソースコードを更新したら結局同じ状態となってしまっていました。

策1

設定変更

以下を development.rb に設定する。

config.reload_classes_only_on_change = false

こちらを参考に。

ファイルの変更検知の設定をする。
依存関係のあるファイルに変更があった場合に、クラスを再読み込みするようになる。
config.cache_classesが「true」の場合は、無視される。

http://railsdoc.com/config#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%A4%89%E6%9B%B4%E6%A4%9C%E7%9F%A5%E3%81%AE%E8%A8%AD%E5%AE%9A%28config.reload_classes_only_on_change%29

とりあえずエラーは出なくなった。
いまいち挙動をちゃんと調べてません。依存関係のあるファイルってなにかな。
false にすると、この機能ができる前のrails的な挙動なのかな?これも確認せねば...

策2

config/initializer/const.rb を更新

内容を以下に変更します。

Rails.application.config.to_prepare do
 require_dependency File.expand_path('../../../app/models/foo/bar/baz.rb', __FILE__)
end

参考元

Rails.application.config.to_prepare を使えばproduction環境なら最初の一回だけ読んでくれて、development環境ならリクエストの度に読み込む、というのを実現してくれます。
今回のソースで言うと、以下のようになります。
config/initializers/initialize_adapter.rb

Rails.application.config.to_prepare do
  ExternalServiceAdapter.configure do |config|
    config.api_key = 'my_api_key'
  end
end
http://qiita.com/ryonext/items/4becf67f8ceadf8f4a53

これがいいかも。これもエラーは出なくなりました。
でもこれもちゃんと検証できてないです。

railsで、model 直下に rbファイルと、同名のディレクトリがある際に挙動がおかしい - tkm9 で適当な記事書いちゃったので取り急ぎポスト。

色々中途半端なのでたぶんまた書く。

postgresql でファイル保存時 string contains null byte といわれる

string contains null byte ってでたよ

ちょっと試したいことがあり postgreSQL 9.3 を rails 4.2.0 、ruby 2.1.5 から使ってみました。
で、バイナリファイル保存時に以下のエラーが表示されました。

ArgumentError - string contains null byte:
  activerecord (4.2.0) lib/active_record/connection_adapters/postgresql/oid/bytea.rb:8:in `type_cast_from_database'
  activerecord (4.2.0) lib/active_record/type/binary.rb:26:in `changed_in_place?'
  activerecord (4.2.0) lib/active_record/attribute.rb:54:in `changed_in_place_from?'
  activerecord (4.2.0) lib/active_record/attribute_methods/dirty.rb:74:in `attribute_changed_in_place?'
  (snip)

migrationファイルはこんな感じ。

def change
  create_table :resources do |t|
    t.binary :data, :null => false
  end
end

調べてたら ged / ruby-pg / issues / #198 - ArgumentError: string contains null byte — Bitbucket こちらのページのコメント欄に色々書いてありました。

  • rails のバグで rails-4.2.1 で修正されるよ
  • pg(ポスグレのアダプタ)の pg-0.17.1 使うといいよ
  • でも pg-0.17.1 は ruby 2.2 対応してないかも

きっとこんなことを言っているはず

pg バージョン下げる

自分の pg のバージョン確認。

$ bundle exec gem list | grep pg
pg (0.18.1)

これを 0.17.1 にするべく Gemfile 書き換えて bundle install して、

bundle install --path vendor/bundle
$ bundle exec gem list | grep pg
pg (0.17.1)

無事 pg のバージョンが 0.17.1 になりました。

別のエラーがでる

pg のダウングレードにて string contains null byte のエラーは出なくなりました。
しかし、別のところでエラーが発生しています。
エラー内容をメモするの忘れたのですが、DB保存は問題なく、そこからデータを取得する際にエラーになっていました。

別のエラーの解消

postgresql.conf に bytea_output = 'escape' の設定を追加し、エラーは解消しました。

bytea_output はバイナリデータのエスケープの方法を指定するオプションです。
postgreSQLバージョン8系までは 'escape' の指定となっていましたが、バージョン9からは 'hex' が指定可能で、かつ、デフォルトが 'hex'とのことです。

bytea型の扱い

PostgreSQLではバイナリデータを扱う方法として、ラージオブジェクトの仕組みや、bytea型というデータ型が用意されています(バイナリデータをPostgreSQLには格納せず、使用しているOSのファイルシステムを使うという選択肢も勿論あります)。

bytea型の値はpg_escape_bytea関数でエスケープ・クォートする、というのがPostgreSQLの古くからの方法ですが、バージョン9.0以降では、このようなescape書式に加え、hex書式が追加になっています。これに伴いデフォルトの設定もhexに変更になっている上、エスケープ後のサイズもpg_escape_byteaを使ったescape書式に比べずいぶんコンパクトで効率的なようです。

http://lets.postgresql.jp/documents/tutorial/with_php/against_sql_injection/escape_quote/pg_escape_xxxx

これも rails (pg?)側でそのうち対応になるのかな?

railsで、model 直下に rbファイルと、同名のディレクトリがある際に挙動がおかしい

状況

環境:ruby 2.1.5p273 / rails 4.2.0

以下の3つのモデルを配置する。

  • app/models/user.rb
  • app/models/excel.rb
  • app/models/excel/user.rb

まず、ActiveRecordUser モデルが1つ。
ほか2モデルは、エクセルでデータ投入機能をもたせたいのでそれ用のモデルとして Excel モデルと、users テーブルへのインポート用の Excel::User モデルを用意しました。

クラス名は autoload されるよう順に

としました。

で、development の console にて以下のような挙動になっちゃう

$ bundle exec rails c

> User
=> User

> Excel::User
(irb):2: warning: toplevel constant User referenced by DataImport::User
=> User

> ::Excel::User
(irb):3: warning: toplevel constant User referenced by DataImport::User
=> User

Excel::UserUser になってるよ!

実際のコードはもっとややこしいんだけど、上記で再現するからいったんこれで調査を進めることにする。

問題

  • Excel::User がなぜか User になってしまう.
    • Excelモデルは大丈夫
    • ちなみに productionモードだと大丈夫
    • あと、User -> Excel::User の順ではなく、Excel::User -> User の順に読み込めば問題無いっぽい
    • Foo::User というクラスも追加したんだけどこっちは大丈夫だった. つまり、app/models 直下に rbファイル、ディレクトリが同名で置いてあるときに発生していそう

解説しているページを見つけた

定数の自動読み込みと再読み込み — Rails ガイド こちらに説明がありました。
以下、該当部分の引用です。

10.6.2 修飾済み参照

以下の例について考察します。
# app/models/hotel.rb
class Hotel
end
 
# app/models/image.rb
class Image
end
 
# app/models/hotel/image.rb
class Hotel
  class Image < Image
  end
end

Hotel::Imageは実行パスに依存するので、この記法にはあいまいさが生じます。

前述のとおり、RubyはHotelとその先祖の定数を探索します。app/models/image.rbが読み込まれているがapp/models/hotel/image.rbが読み込まれていない状況になった場合、RubyはImageをHotel内ではなくObject内で探索します。

$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
Image # これはHotel::Imageではない

Hotel::Imageを評価するコードは、(おそらくrequire_dependencyを使用して) app/models/hotel/image.rbを事前に読み込み済みの状態にしておく必要があります。

ピンポイントで抜き出したけど全編にわたりとても詳細な説明があり大変参考になります。
あわせて読みたいRailsガイドを読む(定数の自動読み込みと再読み込み) - Qiita

とりあえずの対応

config/initializers/ にこんなファイルを置きました。

require_dependency File.expand_path('../../../app/models/excel/user', __FILE__)

ひとまずはこれで大丈夫そうです。

TODO

ちゃんと記事を読み込んで自分用メモをポストする!

rails 4.2 起動に手こずる

ちょっと確認したいことがあったから、さくっと Gemfile 作って bundle install して rails new して、

bundle exec rails s

で、サーバ起動させて、FireFox でアクセスしてみたら、

正常に接続できませんでした

とかでた。

chrome でも試してみたけど

このウェブページにアクセスできません

とまあ、同じでした.

どうやら 4.2 から、localhost 以外からアクセスさせたい場合は、起動時に

bundle exec rails s -b 0.0.0.0

こうしないといけないみたい.


リリースノートにも書いてある!
Ruby on Rails 4.2 Release Notes — Ruby on Rails Guides

んもう!検討はずれのとこばっか調べてた

rack の変更によるものらしい

Default host to localhost when in development mode. · 28b0144 · rack/rack · GitHub

久しぶりに ruby やろうとして Gem::RemoteFetcher::FetchError がでた

ブログも作ったし色々がんばるかと思ってrbenvで最新ruby入れてbundler入れようとしたらエラーでた

$ gem install bundler
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError)
    SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (https://rubygems.global.ssl.fastly.net/quick/Marshal.4.8/bundler-1.7.12.gemspec.rz)

CentOS 6.5、ruby 2.1.5 です

で、
ruby on rails - bundle install fails with SSL certificate verification error - Stack Overflow ここに書いてある方法試したけど結局だめで、virtualboxで仮想環境でやってたんだけど、virtualboxを最新にupdateしたらいけました

そんなオチか
2時間くらい無駄にしてしまいました

...と思ったけどほんとにそうなのかな?
virtualbox との関連がちょっとわかんない

ほかにも色々やってたから、その他のどれかの対応が効いたのかな?
でもvirtualbox verUP 直前までは確かにダメだったんだよな

うーん、しょっぱい