2010/10/07

nested_attributes で error_messages_for のエラーメッセージがうまくローカライズされない。

Just trying to enable correct i18n for error_messages for using nested_attributes. Used this patch but moved to an initializer instead.

Rails 3.0.0.beta4 をいじっているときのこと。。。

nested_attributes を使って親モデルと子モデルを同時に編集する際、error_messages_for を使ってバリデーションのエラーメッセージを吐き出してるんですが、なぜか子モデルのエラーだけがローカライズされない。
例:
class User < ActiveRecord::Base
  has_many :posts
  accepts_nested_attributes_for :posts

  validates_presence_of :name
end

class User < ActiveRecord::Base
  belongs_to :user

  validates_presence_of :title

end
で、
Posts title は必須です
名前は必須です
みたいに出力されてしまう。
運よくAnton Bangratzさんがパッチを書いてくれていた。Anton さんありがとうー!!
でも、パッチするのは嫌だったので、config/initializer/errors_i18n.rb と適当なファイルを作って、下記のように。。。
ActiveModel::Errors.module_eval do

  def full_messages
    full_messages = []

    each do |attribute, messages|
      messages = Array.wrap(messages)
      next if messages.empty?

      if attribute == :base
        messages.each {|m| full_messages << m }
      else
        # attr_name = attribute.to_s.gsub('.', '_').humanize
        attr_name = convert_name(attribute.to_s) 
        attr_name ||= attribute.to_s.gsub('.', '_').humanize        
        attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
        options = { :default => "%{attribute} %{message}", :attribute => attr_name }

        messages.each do |m|
          full_messages << I18n.t(:"errors.format", options.merge(:message => m))
        end
      end
    end

    full_messages
  end

  def convert_name(name)
    default = nil
    if name =~ /(.+)\.(.+)/
      base_name = $1
      attribute = $2
      base = ActiveSupport::Dependencies.constantize(base_name.camelize)
      if (base.respond_to? :model_name)
        default = "#{base.model_name.human} #{base.human_attribute_name(attribute.to_sym, :default => attribute.humanize)}"
      end
    end
    default 
  end  
  
end
因みに、ActiveModel::Errors::ClassMethods.module_eval do では Name Error になった。なぜだ?

2010/08/07

simple_captcha + Windows で no encode delegate for this image format error

Rails 3 用の simple_captcha plugin にアップグレードしたら動かなくなりました。 ログを見ると
StandardError (Error while running convert: convert: no encode delegate for this image format `'C:/Users/Me/AppData/Local/Temp/simple_captcha,4020,0.jpg'' @constitute.c/WriteImage/1114.
Paperclip を Windows で動かそうとしたときのエラーと同じように、シングルクオートが問題なのか?
実際、問題の convert コマンドの ' を " に変えてマニュアル実行したら動きました。

ならば。。。
vendor/plugins/simple_captcha/lib/simple_captcha/image.rb (line 72)
# params << "label:#{text} '#{File.expand_path(dst.path)}'"
params << "label:#{text} \"#{File.expand_path(dst.path)}\""
vendor/plugins/simple_captcha/lib/simple_captcha/image.rb (line 64)
# params << "-gravity 'Center'"
params << "-gravity \"Center\""
で解決

2010/08/05

Paperclip を Rails 3 + Windows で動かすニャンコ

一日ハマッタので備忘録


今まで動いていたアプリを 2.3.8 から Rails 3.0.0beta4 にアップグレードしようとしたときのこと。
参考までに、今回の Gemfile
gem 'rmagick', :require => 'RMagick' 
gem "aws-s3", ">= 0.6.2",:require => "aws/s3"
gem "paperclip", ">= 2.3.3", :git => "git://github.com/thoughtbot/paperclip.git"

ところが、Paperclip が動かない。。。。 Image C:/Users/Me/AppData/Local/Temp/stream,4508,0.jpg is not recognized by the 'identify' command. だそうな。 identify (ImageMagick) が見つからないらしいので、チェックする。
$ which identify
/c/Program Files/ImageMagick-6.5.6-Q8/identify
こちらに対処法があったので、その通りに config/development.rb に追加
Paperclip.options[:command_path] = "/c/Program Files/ImageMagick-6.5.6-Q8/"
ところが undefined method `exitstatus' for nil:NilClass と。。。orz
/c/Program Files/ImageMagick-6.5.6-Q8/identify -format %wx%h "C:/Users/Me/AppData/Local/Temp/stream,5880,0.jpg[0]"
をアクセスしようとして、エラっている模様。じゃあ、コマンドプロムプトでマニュアル実行してみる。
> "/c/Program Files/ImageMagick-6.5.6-Q8/identify" -format %wx%h "C:/Users/Me/AppData/Local/Temp/stream,5880,0.jpg[0]"
> 指定されたパスが見つかりません。
なるほど。でも windows 風だとOK。
>"c:\Program Files\ImageMagick-6.5.6-Q8\identify" -format %wx%h "C:/Users/Me/Ap
pData/Local/Temp/stream,5880,0.jpg"
> 500x499 
なので、config/development.rb を次のように編集
Paperclip.options[:command_path] = "c:\\Program Files\\ImageMagick-6.5.6-Q8\\"
なおった!!ばんざ~い!

あと、念のためにこちらのスレッドにある変更も・・・
bundler の gem ディレクトリの各ファイルを編集(僕の場合は C:\Users\Me\.bundle\ruby\1.8\bundler\gems\paperclip-XXX-master\lib\paperclip)
因みに、インストールに失敗した別のバージョンの Paperclip gem が残っていてエラっていたこともあるので、綺麗にするように。 command_line.rb
    def shell_quote(string)
      return "" if string.nil? or string.blank?
      # string.split("'").map{|m| "'#{m}'" }.join("\\'")
      string.split("'").map{|m| "\"#{m}\"" }.join("\\'") 
    end
thumbnail.rb
    def transformation_command
      scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
      trans = []
     # trans << "-resize" << "'#{scale}'" unless scale.nil? || scale.empty?
     # trans << "-crop" << "'#{crop}'" << "+repage" if crop
       trans << "-resize" << "\"#{scale}\"" unless scale.nil? || scale.empty?
       trans << "-crop" << "\"#{crop}\"" << "+repage" if crop
      trans
    end

2010/9/2 Hosoku


It turns out adding this would be enough. Create a file initializers/paperclip.rb with the following contents.

if RUBY_PLATFORM == 'i386-mingw32'
  module Paperclip
    def self.quote_command_options(*options)
      options.map do |option|
        option.split("\"").map{|m| "\"#{m}\"" }.join("\\\"")
      end
    end
  end
end

2010/08/02

Setting http expires in .htaccess

YSlow gave me an "E" on one of my websites so I started tweaking things a little. One of the things that made a big difference was setting http expiration headers.

In my .htaccess I added...
ExpiresActive On
ExpiresByType text/javascript A604800
ExpiresByType text/css A604800
ExpiresByType image/gif A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresActive On
ExpiresDefault "access plus 30 days"

2010/07/29

Find out when Daylight Savings Time (DST) starts and ends using Ruby (on Rails)

Recently encountered a requirement to find out whether a particular date falls within daylight savings time. After banging my head on the wall for a few hours trying to figure it out, I found that the ruby TimeWithZone object makes this quite simple.
@mytime = Time.zone.now.in_time_zone("London")
# Creates a TimeWithZone instance (instead of just a Time instance)

@mytime.period.local_start = 2010-03-28T02:00:00+00:00 Local Standard Time 
# This is when daylight savings time starts

@mytime.period.local_end = 2010-10-31T02:00:00+00:00 Local DST 
# This is when dst ends

---------------------------------- 
@sometime = Time.parse('2010-3-27 01:00:00').in_time_zone("London") 
# 2010-03-26 16:00:00 +0000 
@sometime.period.std_offset 
# 0 
@mytime.period.local_after_start?(@sometime) 
# false 
@mytime.period.local_before_end?(@sometime) 
# true 
---------------------------------- 
@sometime = Time.parse('2010-3-29 05:00:00').in_time_zone("London") 
# 2010-03-28 21:00:00 +0100 
@sometime.period.std_offset 
# 3600 << Note that std_offset now holds the offset time for daylight savings in seconds
@mytime.period.local_after_start?(@sometime) 
# true 
@mytime.period.local_before_end?(@sometime) 
# true 
---------------------------------- 
@sometime = Time.parse('2010-12-29 05:00:00').in_time_zone("London") 
# 2010-12-28 20:00:00 +0000 
@sometime.period.std_offset 
# 0 
@mytime.period.local_after_start?(@sometime) 
# true 
@mytime.period.local_before_end?(@sometime) 
# false 

2010/07/10

Sometimes I feel like killing Aptana...

I'm probably doing something stupid, but always seem to lose my project files in Aptana. "You attempted to go into a closed project" it tells me. Well, Aptana, if you show me the folder list I can probably open the project so I can go into it. But no, you have to make this such a challenge every time. The solution for me, it turns out, is to
  1. Go to my Ruby Explorer
  2. Right click on the empty window
  3. Select "open project"
  4. Open whatever project Aptana is insisting I want to get into.
  5. Go back to the Project Tree
  6. Keep clicking the "Go Up" icon to reveal the entire workspace
Another 20 minutes spent tearing my hair out...

2010/07/01

collection_select の値を rjs に渡す

備忘録
<%= f.collection_select(:something , Company.find(:all), :id, :name, 
  {},
  {:onchange => remote_function(:url => {:action => "change_vendor"}, :with => "'id='+this.value")}
 )%> 

:with で含めた値がパラメータとして渡されます。