はじめましてとWebAppTheme

はじめましてー。

めしおです。

今までPHPばっかいじってましたが、去年末位からRailsをいじるようになりました。

基本的にはプログラムなことを書いてこうかと思います。
でも、ブログの書き方よくわかんね。

そんな最初の題材はrails3で使用したweb-app-themeでネームスペースで切ってネストさせたmodelを使用しようとしたらエラーになったので勝手にソースを上書きしたことについて。

最近では、Twitter Bootstrapが流行っているそうですが、案件でweb-app-themeを使用していたのでそっち。

環境

まずは環境から。
ちょっと古めですが。

ruby 1.9.2p290
gem 1.8.10
web-app-theme (0.8.0)
Rails 3.1.3

ハマったこと

普通、

rails g web_app_theme:themed users

とかやるとscaffoldで作成したテンプレートにテーマを自動で加えてくれるみたい。

でも、
下記のようにモデルをネームスペースで切っている場合、

rails g web_app_theme:themed master/users master/user

エラーになる。

/gems/web-app-theme-0.8.0/lib/generators/web_app_theme/themed/themed_generator.rb:80:in `const_get': wrong constant name Master::User (NameError)
/gems/web-app-theme-0.8.0/lib/generators/web_app_theme/themed/themed_generator.rb:80:in `columns'


「Master::User」なんて定数ないよと怒られているらしい。
でも、modelは作成したから、存在するはずなんだけどなー…。
googleさんに聞いてgithubで同様の悩みを抱えてるっぽいのを見つけたが、解決しておらず。
仕方ないからソースを見てます。

原因

原因は、themed_generator.rbのcolumnsメソッドにあるconst_getの部分らしい。
調べてみるとどうやらconst_getはネストした定数はエラーになるらしい。(「::」が含まれるやつ?)

# themed_generator.rb

def columns
begin
excluded_column_names = %w[id created_at updated_at]
Kernel.const_get(@model_name).columns.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type)}
rescue NoMethodError
Kernel.const_get(@model_name).fields.collect{|c| c[1]}.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type.to_s)}
end
end

対策

ネストした定数を取得する為には以下のようにすると取得できる模様。

def toplevel_const_get(name)
name.split(/::/).inject(Object) { |o, c| o.const_get(c) }
end

それじゃー、web_app_themeのthemed_generator.rbも書きかえちゃえと。
でも、
既存のソースをそのまま書きかえるのは嫌なので、themed_generator.rbでクラスを再オープンして上書きする。

module WebAppTheme
class ThemedGenerator

def columns
begin
excluded_column_names = %w[id created_at updated_at]
if /::/ =~ @model_name
model = @model_name.split(/::/).inject(Object) { |o, c| o.const_get(c) }
model.columns.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type)}
else
Kernel.const_get(@model_name).columns.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type)}
end
rescue NoMethodError
Kernel.const_get(@model_name).fields.collect{|c| c[1]}.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type.to_s)}
end
end
end
end


これで再度「rails g web_app_theme:themed master/users master/user」を実行。

すると成功!

んで、ブラウザで「/master/users/」ページを確認してみると…



エラー…

エラー原因は

<% @master/users.each do |master/user| -%>

とか

<%= master/user.id %>

とか

<%= link_to "#{t("web-app-theme.show", :default => "Show")}", master_user_path(master/user) %> |

とか。

ホントは「master_user」にならなきゃいけないのに「master/user」になっちゃってる。

これの原因は、themed_generator.rbのresource_nameメソッドでunderscoreを使用してるから。

def resource_name
@model_name.underscore
end

これを書きかえたメソッドを、さっき再オープンしたクラスに追記。
ここを上書きしたことでどんな影響があるかは知りません。

んで、
最終的に以下のようなソースをthemed_generator.rbに追記したことになる。

module WebAppTheme
class ThemedGenerator
protected
def resource_name
@model_name.underscore.gsub(/\//, '_')
end

def columns
begin
excluded_column_names = %w[id created_at updated_at]
if /::/ =~ @model_name
model = @model_name.split(/::/).inject(Object) { |o, c| o.const_get(c) }
model.columns.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type)}
else
Kernel.const_get(@model_name).columns.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type)}
end
rescue NoMethodError
Kernel.const_get(@model_name).fields.collect{|c| c[1]}.reject{|c| excluded_column_names.include?(c.name) }.collect{|c| Rails::Generators::GeneratedAttribute.new(c.name, c.type.to_s)}
end
end
end
end

これでもform画面とかでエラーになるんだが、もう知らない。
そんなところは、テンプレートが生成されてからちょこちょこ直せばいーや。
一番の目的は、ネストしたmodelを持っていてもrailsコマンドでテーマに沿ったテンプレートをある程度自動生成してくれることなので、オッケー。

お疲れ様でした。