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

There's an echo in my head

日々のメモ。

Rails 4.1ではモジュール配下のクラス名をunderscoeするときにacronymの設定を考慮してくれない

ActiveSupport::Inflector.underscore does not respect acronym inflections within a module · Issue #17193 · rails/rails · GitHub の件。

具体的な影響としてはたとえば次のように、SKUという単語をacronymとして設定しても、namespaceでモジュールを切ったらそれが反映されない。

# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'SKU'
  inflect.acronym 'SKUs'
end

# config/routes.rb
Rails.application.routes.draw do
  namespace :admin do
    resources :skus
  end
end

# app/controllers/admin/skus_controller.rb
class Admin::SKUsController < ApplicationController
  def index
    render text: "ok"
  end
end

GET /admin/skus
#=> ActionController::RoutingError (uninitialized constant Admin::SKUsController)

この例ではルーティングのadmin/skusから"Admin::SKUsController"っていう定数名を取得するところまでは問題ないんだけど、そこからconst_missingをフックしてファイルを動的に読み込もうとしたときに、ここ"Admin::SKUsController".underscoreが"admin/sk_us_controllerを返すために適切なファイルのパスが取得できず、結果としてAdmin::SKUsController`を定義できないために失敗してしまう。

対処法としては、定数名とファイル名の対応付けが事前にできていればいいのだから、次のようにautoloadしておけばよい。 ((eager_autoloadで包んだほうがいいかも?))

# config/application.rb とか
module Admin
  autoload :SKUsController, Rails.root.join("app/controllers/admin/skus_controller")
end

ちなみにこの件、4.1では修正されないみたいだ。

追記 2014-11-17

autoloadでコントローラの対応付けをするだけでは足りず、さらにviewのディレクトリとの対応付けをする必要もあった。

もうめんどくさくなったので、次のように4.2.xで入りそうなパッチをあてた。

# lib/fix_underscore.rb
#  https://github.com/rails/rails/blob/861b70e92f4a1fc0e465ffcf2ee62680519c8f6f/activesupport/lib/active_support/inflector/methods.rb からコピー
require "active_support/inflector/methods"

if Rails::VERSION::MAJOR == 4
  if Rails::VERSION::MINOR < 2
    module ActiveSupport
      module Inflector
        def underscore(camel_cased_word)
          return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
          word = camel_cased_word.to_s.gsub(/::/, '/')
          word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
          word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
          word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
          word.tr!("-", "_")
          word.downcase!
          word
        end
      end
    end
  else
    ActiveSupport::Deprecation.warn "Patch for ActiveSupport::Inflector#underscore is included since Rails 4.2"
  end
end

# config/application.rb
require File.expand_path("../../lib/fix_underscore", __FILE__)
このブログに出てくるコードスニペッツは、引用あるいは断りがない限りMITライセンスです。