views:

38

answers:

1

The question is just above the last code snippet. Thank you. (environment details are the end )

posts_controller.rb

class PostsController < ApplicationController  
def create
    @post = Post.new(params[:post])
    respond_to do |format|
      format.xml { render :xml => @post.to_xml(:include => [ :assets])}
end
end

posts.rb

class Post < ActiveRecord::Base
  has_many    :assets, :as => :attachable, :dependent => :destroy
end

asset.rb

class Asset < ActiveRecord::Base
  belongs_to :attachable, :polymorphic => true
  has_attached_file :data,
                    :url  => "/assets/:id",
                    :path =>":rails_root/assets/:id_partition/:style/:basename.:extension"
  def name
    data_file_name
  end

  def content_type
    data_content_type
  end

  def file_size
    data_file_size
  end
  end

now when we post this information

POST /posts.xml HTTP/1.1
Accept-Encoding: gzip,deflate
Accept: application/xml
Content-Type: application/xml
User-Agent: Jakarta Commons-HttpClient/3.1
Host: localhost:8080
Content-Length: 60

<post><body>postbody</body><title>post_title</title></post>

a post entry gets created and when I post this

POST /posts.xml HTTP/1.1
Content-type: multipart/mixed; boundary=---------------------------7d226f700d0
Accept: application/xml,text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.6.0_21
Host: 192.168.0.105:8080
Connection: keep-alive
Content-Length: 1710

-----------------------------7d226f700d0
content-disposition: form-data; name="post[title]"
Content-Length: 10
post_title
-----------------------------7d226f700d0
content-disposition: form-data; name="post[body]"
Content-Length: 8
postbody
-----------------------------7d226f700d0
content-disposition: form-data; name="post[assets_attributes][0][data]"; filename="C:/Users/mv288/files/1.txt"
content-type: application/octet-stream
ÿþ
sample file content
-----------------------------7d226f700d0
content-disposition: form-data; name="post[assets_attributes][0][data]"; filename="C:/Users/mv288/Pictures/1.txt"
content-type: application/octet-stream
ÿþ
sample file content
-----------------------------7d226f700d0

a new post gets created with 2 file attachments.

now the question is, I want to get the following HTTP post ( please notice the xml part before the file attachments) to also create a post with 2 attachments, with no additional changes ( to posts_controller or routes.rb). is that possible?

POST /posts.xml HTTP/1.1
Content-type: multipart/mixed; boundary=---------------------------7d226f700d0
Accept: application/xml,text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.6.0_21
Host: 192.168.0.105:8080
Connection: keep-alive
Content-Length: 1710

-----------------------------7d226f700d0
Content-type: application/xml; charset=UTF-8
Content-Length: 59
<post><body>postbody</body><title>post_title</title></post>
-----------------------------7d226f700d0
content-disposition: form-data; name="post[assets_attributes][0][data]"; filename="C:/Users/mv288/files/1.txt"
content-type: application/octet-stream
ÿþ
sample file content
-----------------------------7d226f700d0
content-disposition: form-data; name="post[assets_attributes][0][data]"; filename="C:/Users/mv288/Pictures/1.txt"
content-type: application/octet-stream
ÿþ
sample file content
-----------------------------7d226f700d0Blockquotetest

using jruby 1.5.2/jdk1.6, rails 2.3.4, paperclip-2.3.3 on windows 2007 - 64 bit

A: 

At least rails 2.3.4 does not do this automatically. One needs to write a multipart/related parser and register it, inside initializers/mime_types.rb

NOTE : Feel free to update the hard coded values( like main and attachment part prefix etc.,) in your copy.

This same strategy can be used for multipart/related content as well. We are still debating whether to use multipart/related or multipart/mixed in this example below.

Microsoft's notes on this subject. http://msdn.microsoft.com/en-us/library/ms527355(EXCHG.10).aspx

Mime::Type.register "multipart/mixed", :mixed

class ActionController::Request
  def initialize(env)
    Thread.current[:request]=self
    super
  end

end

class MultiPartParamsParser

  def main_part_name
   "main"
  end

  def attachment_part_prefix
    "my_company_attachment"
  end

  def content_type(main_part)
    # TODO ----
    :xml_simple
  end

  def content(main_part)
    # TODO implement this
     if main_part.is_a?(String) 
       main_part.gsub!("Content-Type: application/xml",'') # remove content type if it exists
       main_part.strip! # to remove any trailing or leading whitespaces
     else
      main_part[:tempfile].read
     end
  end

  def request
    Thread.current[:request]
  end

  def parse_formatted_parameters(data)
    multi_parts = Rack::Utils::Multipart.parse_multipart(request.try(:env))
    main_part   = multi_parts[main_part_name]
    data        = content(main_part)
    # TODO return an error if data is not found
    params = case content_type(main_part)
      when :xml_simple, :xml_node
        data.blank? ? {} : Hash.from_xml(data).with_indifferent_access
      when :yaml
        YAML.load(data)
      when :json
        if data.blank?
          {}
        else
          ret = ActiveSupport::JSON.decode(data)
          ret = {:_json => data} unless data.is_a?(Hash)
          ret.with_indifferent_access
        end
      else
        {}
    end
    process_attachments(params, multi_parts)
    params                 
  end

  def process_attachments(data, multi_parts)
    data.each do |key, value|
      value ||= key # when array value is nil
      if value.is_a?(Hash) or value.is_a?(Array)
        process_attachments(value, multi_parts)
      elsif value.respond_to?(:match) and value.match("^#{attachment_part_prefix}") and (attachment=multi_parts[value]) # there could Time,Numbers etc.., but we match only string.
        data[key] = create_uploaded_file(attachment) # TODO handle the scenarios for short strings
      end 
    end         
  end

  def create_uploaded_file (attachment)
    upload = attachment[:tempfile]
    upload.extend(ActionController::UploadedFile)
    upload.original_path = attachment[:filename]
    upload.content_type = attachment[:type]
    upload
  end
end

proc = Proc.new do |data|
  MultiPartParamsParser.new.parse_formatted_parameters(data)
end

ActionController::Base.param_parsers[Mime::Type.lookup('multipart/mixed')] = proc

then, you can post your message like this. Nesting gets automatically taken with no further changes to the models or controllers.

POST /posts.xml HTTP/1.1
Content-type: multipart/mixed; boundary=---------------------------###987612345###
Accept: application/xml,text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Cache-Control: no-cache
Pragma: no-cache
Connection: keep-alive
Content-Length: ##

-----------------------------###987612345###
content-disposition: name="main"
Content-Length: ##
<post><title>post_title</title><body>post_body</body>
    <assets_attributes type="array">
            <asset><data>my_company_attachment_0</data> </asset>
            <asset><data>my_company_attachment_1</data> </asset>
    </assets_attributes>
</post>
-----------------------------###987612345###
content-disposition: name="my_company_attachment_0"; filename="C:/Users/mv288/files/1.txt"
content-type: application/octet-stream
ÿþ
sample file content
-----------------------------###987612345###
content-disposition: name="my_company_attachment_1"; filename="C:/Users/mv288/Pictures/1.png"
content-type: image/png
ÿþ
sample file content
-----------------------------###987612345###
mv288