Friday, February 10, 2012

From file_column to paperclip

At the time ClassroomParent was started, the file_column plugin was the state of art for uploads. Not only did it integrate nicely with rmagick, but there was also a bridge for active_scaffold.

But file_column has been left behind by many excellent plugins/gems, most notably, paperclip.

In 2008, Mark Cornick wrote an excellent blog on how to migrate from file_column to paperclip. For the most part, I have followed his instructions, though I split his single migration into several. 


One major hurdle I had to overcome was that my file storage path is dynamically generated. This is so I can better segregate one school's assets from another. 


Looking at the paperclip documentation, they state that the attributes associated with has_attached_file can accept a lambda, so I tried this on the :path attribute:


  :path => lambda { |attachment| "/system/#{attachment.instance.sub_domain}/:class/:id/:basename.:extension"


This worked for the path value, but when I tried to access the url value, I got the following error:


NoMethodError: undefined method `gsub' for #<Proc:0x000001058f79a0>
    from /Users/user/.rvm/gems/ruby-1.9.2-p180@classroom_parent/gems/paperclip-2.5.2/lib/paperclip/interpolations.rb:33:in `block in interpolate'
    from /Users/user/.rvm/gems/ruby-1.9.2-p180@classroom_parent/gems/paperclip-2.5.2/lib/paperclip/interpolations.rb:32:in `each'



Looking at the paperclip code, I could see the offending gsub statement, and it did not look easy to work around it. 


However, there was a little note, that I don't believe is echoed in the paperclip gems readme, that states that a symbol can be passed to the path attribute that references an instance method. Moving my code from the lambda to a public private method, and using that method name as the value passed to the path attribute worked like a charm. 


  :path => :path_to_file


  def path_to_file
    return "/system/#{sub_domain}/:class/:id/:basename.:extension"
  end


private
  def sub_domain
    ...code to get to the sub_domain of the school
  end

   

European dates in ruby 1.9.2

Date parse in Ruby 1.9.2 expects that dates should be in the european format of dd/mm/yyyy. Unfortunately, this meant that my month and days became reversed.

Troyk posted a gist with a solution.

Create an initializer (ruby_date_parse_monkey_patch.rb) with the following:

# Date.parse() with Ruby 1.9 is now defaulting to the European date style where the format is DD/MM/YYYY, not MM/DD/YYYY
# patch it to use US format by default
class Date
  class << self
    alias :euro_parse :_parse
    def _parse(str,comp=false)
      str = str.to_s.strip
      if str == ''
        {}
      elsif str =~ /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{2,4})/
        year,month,day = $3.to_i,$1,$2
        date,*rest = str.split(' ')
        year += (year < 35 ? 2000 : 1900) if year < 100
        euro_parse("#{year}-#{month}-#{day} #{rest.join(' ')}",comp)
      else
        euro_parse(str,comp)
      end  
    end
  end
end

Thursday, February 9, 2012

Adventures in moving from Rails 2.3.14 to 3.0: ActionMailer changes

So many deprecation notices, so many things to fix, when it comes to mailers.

After getting some of my model specs to run without issue, I decided to focus on the my mailer specs, as I new there were many things that had to change.

In the related mailer_spec

Assert_select and assert_select_email instead of have_tag, have_text
The first mailer spec I ran, everything blew up, because I had been using have_tag and have_text as matchers. I could not figure out how to get them to work in Rails 3, so switched to using assert_select and assert_select_email.

However, in rails 3.0, assert_select_email has a couple of problems. First, it assumes that emails will be multipart. This wasn't true in my case. It also assumes that email.body returns a string, which it does not, it returns a Body object.

There is a fix in rails 3.1 for this, so I borrowed that code and created an initializer with the following:

module ActionDispatch
  module Assertions
    module SelectorAssertions
      # this is fixed in Rails 3.1
      # in 3.0, if the email has no parts, it would always return true
      # also, the body needed to be converted to a string, as that is what HTML::Document.new expects
      def assert_select_email(email = nil, &block)
        deliveries = email ? [email] : ActionMailer::Base.deliveries
        assert !deliveries.empty?, "No e-mail in delivery list"

        for delivery in deliveries
          for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
            if part["Content-Type"].to_s =~ /^text\/html\W/
              root = HTML::Document.new(part.body.to_s).root
              assert_select root, ":root", &block
            end
          end
        end
      end
    end
  end
end

Unlike the fix rails 3.1 fix (https://github.com/rails/rails/pull/2499), this also allows you to pass in an email object. Without this, if assert_select_email found more than one email in the deliveries list, and one of them didn't match your select criteria, this would fail, even if other emails in the deliveries list matched.

By passing in an email, you don't leave that to chance.

Changing all of the have_tag, with_tag, etc to assert_select statements was a bit of a pain, but it works well now. 

Change have_text to have_body_text

Move "deliver_" at the front of the mailer method call to ".deliver" at the end of the mailer method call

In the mailer.rb file

Change @body[:foo] = 'bar' assignments to @foo = 'bar'
To change the @body[:foo] = 'bar' assignment to @foo = 'bar', I used the following in TextMates Find in Project
Find: \@body\[:([a-z_1-9]*)\]
Replace: @$1
Use Regular Expressions: true

Adventures in moving from Rails 2.3.14 to 3.0: Has_many through


There have been many blogs about moving from Rails 2.3x to 3.0. Kir Maximov has a good one. And there is the Simone Carletti's blog which gives some pre-migration advice.

I am going to focus on the things that I encountered that I could not find mentioned elsewhere, and may be peculiar to my application.

Each day that I work on the migration, I am going to post what I encountered. Mostly, this will help me if I ever have to do another migration. And it keeps me off the streets.


Issue with has_many through
I have the following has_many through relationship
class Parent
  has_many :relatives
  has_many :students, :through => :relatives
end

class Relative
  belongs_to :parent
  belongs_to :student
end

In the parent class, I had the following named_scope:

named_scope :in_school_year, lambda{|s,y|
    {:include => {:students => {:homerooms => :classroom}}, 
    :conditions => ['classrooms.year = ? and classrooms.school_id = ?', y, s.id]}
  } 


In 2.3.x, this worked fine. But in 3.0, this created a query where the relatives table was not being joined to the students table, so was returning all students.

I changed the scope in the rails 3 version of my app to the following, and it worked fine.

scope :in_school_year, lambda{|s,y|
    {:include => {:relatives => {:student => {:homerooms => :classroom}}}, 
    :conditions => ['classrooms.year = ? and classrooms.school_id = ?', y, s.id]}
  } 

Perhaps it never should have worked in rails 2.3, but it did.