File Uploads via Merb inside a Rails Application : Part 2 of 3
[If you want to start at the beginning check out Part 1]
I understand that you are angry, after all Luke’s post on RailSpikes had more information than Part 1 and didn’t purport to get it running inside Rails. Just bear with me, we’ll cover a bit more info previously available and we’ll move into some concrete code.
First, let’s get Merb and Rails in sync. I happen to be using a STI for Assets and one of those assets is of type FileAsset. So I need to get those Rails models into Merb. We’ll share the models to try and keep it DRY.
I have a model in Merb called
<rails_app>/merbuploader/dist/app/models/file_upload.rb
class FileUpload < ActiveRecord::Base
end
require DIST_ROOT + '/../../app/models/asset.rb'
require DIST_ROOT + '/../../app/models/asset_file.rb'
Here are those two rails files in for reference (recall I am using S3)
<rails_app>/app/models/asset.rb
class Asset < ActiveRecord::Base
end
<rails_app>/app/models/asset_file.rb
class AssetFile < Asset
has_attachment :storage => :s3,
:s3_access => :private,
:max_size => 10.megabytes
...
<additional_methods_here>
...
end
There, that was easy. Now onto the controller:
<rails_app>/merbuploader/dist/app/controllers/file_uploader.rb
class FileUploader < Merb::Controller
def create_asset_file
@asset = AssetFile.new(params[:asset_file])
@asset.save ? success=@asset.name : success=false
redirect "#{request.env['HTTP_REFERER']}&success=#{success}"
end
...
<additional_methods_here>
…
end
I redirect back to the requesting URL and pass in a success param, so people can keep uploading more files.
Here’s what the upload form looks like, this file is part of RAILS.
<rails_app>/app/views/<controller>/<whatever_you_call_your_upload_form>.rhtml
<% form_for :asset_file, asset, :url => '/file_uploader/create_asset_file', :html => { :multipart => true } do |form| %>
<%= form.hidden_field :type, :value => params[:type] %>
Name: <%= form.text_field :name, :size => 25 %>
Upload: <%= form.file_field :uploaded_data, {:size => 25} %>
<%= submit_tag "Submit" %>
<% end %>
Three things to note here:
- The submission URL is
/file_uploader/create_asset_file, as with Rails this routes to the Controller/Action, this just happens to be for Merb. - I have an additional field so people can name the file, the rest is standard stuff.
- The :uploaded_data is the file info itself that Merb uses in the following way:
# A file upload will have a hash of params like this:
# :filename => File.basename(filename),
# :content_type => content_type,
# :tempfile => <Tempfile>,
# :size => File.size(body)
If you are doing this locally, (this works on Windows… SHH! Stop screaming!) you can simply do:
:url => 'http://127.0.0.1:4000/file_uploader/create_asset_file'
This will route those requests to your local Merb running on port 4000. Sweet!
Let’s get attachment_fu and Merb to play nice. First you’ll just need to copy your
Good, cause when when Merb includes your asset_file.rb (or whatever you called it) it will look to Merb’s :has_attachment plugin, not the one in Rails.
We’ll sync up Merb and Rails then edit the attachment_fu files. Here’s the first edit;
<rails_app>/merbuploader/dist/conf/merb_init.rb
puts "merb init called"
require 'active_record'
ActiveRecord::Base.verification_timeout = 14400
ActiveRecord::Base.logger = MERB_LOGGER# For attachment_fu plugin
RAILS_ROOT = DIST_ROOT
ENV[’RAILS_ENV’] = MERB_ENVrequire DIST_ROOT+"/app/controllers/application.rb"
# Dir[DIST_ROOT+”/plugins/*/init.rb”].each { |m| require m }#Needs to be before the models
Dir[DIST_ROOT+”/app/controllers/*.rb”].each{ |m| require m }
Dir[DIST_ROOT+”/app/helpers/*.rb”].each { |m| require m }
Dir[DIST_ROOT+”/app/models/*.rb”].each { |m| require m }
Dir[DIST_ROOT+”/app/mailers/*.rb”].each { |m| require m }
Dir[DIST_ROOT+”/lib/*/lib/*.rb”].each { |m| require m }
Dir[DIST_ROOT+”/lib/*/bin/*.rb”].each { |m| require m }#Get Database Config
puts "Connecting to database..."
# Use the RAILS app db file
# conn_options = YAML::load(Erubis::Eruby.new(IO.read(”#{DIST_ROOT}/../../config/database.yml”)).result)
ActiveRecord::Base.establish_connection conn_options[”#{MERB_ENV}”]#Get Environment File
require "#{DIST_ROOT}/conf/environments/#{MERB_ENV}"
What are we interested in here? Well these two lines were added near the top:
RAILS_ROOT = DIST_ROOT
ENV['RAILS_ENV'] = MERB_ENV
and also
Dir[DIST_ROOT+"/plugins/*/init.rb"].each { |m| require m }
needs to be higher up the chain or your Model with the attachment_fu plugin complains.
And lastly we get Merb to use our Rail’s database.yml (DRY!). Notice the new path:
("#{DIST_ROOT}/../../config/database.yml")
Without stopping lets head straight for the attachment_fu plugin in the merb plugins folder!
First file on our list…
<rails_app>/merbuploader/dist/plugins/attachment_fu/init.rb
require DIST_ROOT+"/plugins/attachment_fu/lib/geometry.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/db_file_backend.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb"
require DIST_ROOT+"/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb"
Add these to the top of the file, commente out the images_science line if your windows box has issues with it (if you do you’ll need to remove ImageScience from the @@default_processors list in attachment_fu.rb). We need to manually include the required files for the plugin since we don’t currently have that Rails magic.
Next up is a small change to:
<rails_app>/merbuploader/dist/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb
@@s3_config_path = base.attachment_options[:s3_config_path] || (RAILS_ROOT + '/../../config/amazon_s3.yml')
Find @@s3_config_path and point it to the amazon_s3.yml file in your rails app, like so (more DRYness). You’ll recall we set RAILS_ROOT = DIST_ROOT so we take it from there.
And finally (I commented out the original uploaded_data= method):
<rails_app>/merbuploader/dist/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb
# For MERB
def uploaded_data=(file_data)
my_file = file_data.tempfile if !file_data.nil? and !file_data.tempfile.nil?
return nil if my_file.nil? || my_file.size == 0
self.content_type = file_data.content_type if file_data.respond_to?(:content_type)
self.filename = file_data.filename if respond_to?(:filename)
if my_file.is_a?(StringIO)
my_file.rewind
self.temp_data = my_file.read
else
self.temp_path = my_file.path
end
end
So here we are still using uploaded_data as ‘the file’ but merb breaks that down, so we can extract from there, specifically file_data.tempfile is the file we want to upload.
Again here is what we should expect:
# A file upload will have a hash of params like this:
# :filename => File.basename(filename),
# :content_type => content_type,
# :tempfile => <Tempfile>,
# :size => File.size(body)
You can go ahead and extract more info if you need to…
You should now be able to get the whole thing working locally, using 'http://127.0.0.1:4000/<merb_controller>/<merb_action>‘ as your upload url/action. Don’t for get to cd into the <rails_app>/merbuploader dir and run merb start.
Well that was a juicy piece of goodness! You should be slightly less angry now, as you should be able to run the Merb uploading locally. I’ll take this opportunity to slip out the back… Next post will deal with Apache mod_rewrite to allow Apache to intercept the upload calls and pass them directly to Mongrel without bothering Rails. We’ll also take a look at adding a task to two to Capistrano’s deploy.rb to automate starting up Merb in Part 3.
Disclaimer: This may not be the only or even the best way to do things. Some of this is uglier that I would like, so if anyone has suggestions please post them! Thanks!
Related Articles
6 Comments so far
Leave a reply
Pretty soon you will need to update this article as DIST_ROOT is getting the axe in 0.4.0 in favor of MERB_ROOT
@Steven: Thanks for the heads-up, I’m keen to see the 0.4.0 release!
Thanks Steven - I’m eagerly awaiting part 3 - have got rails and merb playing nice locally by using separate ports, but would love to see how you got them to live on the same host.
It’s heeeeeere!
http://www.merbivore.com
@Alex - I hope to get the last post up by Thanksgiving…
@Steven - Sweet! I’ll have to upgrade and mod these posts.
I love you and your pretty coding face.
Any major changes with ActiveRecord 2.x and Merb 0.4.x?