Tuesday, June 24, 2008

PaperClip and Rails 2.1

These are a couple of quick tips for using PaperClip 2.1.2 and Rails 2.1. I don't know if there is a difference with other version combinations.

For security reasons, you may want to store your attachment out of the public directory:
class DocumentVersion < ActiveRecord::Base
belongs_to :document
has_attached_file :version,
:path => ":rails_root/documents/:class/:id/:style/:basename.:extension"
...
end

To access this:
send_file(@document_version.version.path)


I found the directory defaults a little odd, so if you want to specify it:
class Picture < ActiveRecord::Base
has_attached_file :image,
:path => ":rails_root/public/images/pictures/:class/:id/:style/:basename.:extension",
:url => "/images/pictures/:class/:id/:style/:basename.:extension"
end


Note the :url parameter. If you set the path, you must set the :url accordingly. Of course, this only matters if you are going to call the url method.

Tuesday, June 17, 2008

Timestamped Migrations

<update>
As soon as I figure out how, I will be submitting a patch to Rails to make this an option. I would like to use something like:

config.active_record.timestamped_migrations = true

This was suggested in a comment by hardbap referencing the announcement of this post. I agree with idea, but think the default should be false and users can set to true (as in my example) if they want this change. We'll see how that goes... :)

Even though I botched the process, I have submitted a ticket.
</update>



Alright, so I really don't like this feature, the anti-collision benefit is outweighed by the annoyance factor.

So, here's where open source code rocks and why Ruby is my language of choice.

require "rails_generator"

class Rails::Generator::Commands::Base
protected
def current_migration_number
Dir.glob("#{RAILS_ROOT}/#{@migration_directory}/[0-9]*_*.rb").inject(0) do |max, file_path|
n = File.basename(file_path).split('_', 2).first.to_i
if n > max then n else max end
end
end

def next_migration_number
current_migration_number + 1
end

def next_migration_string(padding = 3)
"%.#{padding}d" % next_migration_number
end
end



I just created a file called classic_migrations.rb in config/initializers and added the above code. I haven't run it through all the various scenarios yet, but for a simple migration setup, it works fine.

It should work for everything else, after all, there is nothing inherently different between the timestamped version number and the "old" version number. They are both just numbers representing a sequence.

For the collision issue, here's a simple idea.

Monday, June 2, 2008

Generators and Timestamped Migrations

I've never liked the idea of timestamped migrations, still don't. So, not to my surprise, this was the first (and only) thing that bit me when testing the Lockdown generators against the latest release of Rails (2.1).

What happened? Well, the Lockdown generator generates 5 migrations, and contrary to all the speed talk surrounding Ruby, it does this in less than a second. (Yes - I know Ruby isn't super fast, but it gets the job done.)

What does this mean? Well, all migrations have the same "number" and therefore don't work. Lovely. Just lovely.

So here's what you need to add to your Rails::Generator to get around this issue:
if Rails::VERSION::MAJOR >= 2 && Rails::VERSION::MINOR >= 1
class Rails::Generator::Commands::Base
protected
def next_migration_string(padding = 3)
sleep(1)
Time.now.utc.strftime("%Y%m%d%H%M%S")
end
end
en

I'm grateful for Rails and to the Rails community. They have done a lot of really good stuff.

I only have one question, couldn't timestamped migrations have been an option? I'm not the only one who disliked the concept from the beginning.

Note: I'm not expecting anyone from the Rails team to have even heard of this blog so it's a rhetorical question.