Rails 3 + JQuery-File-Upload + Nested Model

user531023 picture user531023 · Feb 20, 2012 · Viewed 7.5k times · Source

I've been searching for some examples, but have come up short:

I'm trying to implement JQuery-File-Upload on a project I'm working on, but am getting lost as to how to get it to work with nested attributes.

Quick overview:

2 Models:

Comment [has_many :attachments]
Attachment [belongs_to :comment]

Comment accepts_nested_attributes_for :attachments. Also - I'm using Dragonfly.

I've reviewed the Rails 3 guides on the JQuery-File-Upload site, but they assume it's a singular model, so it's all built around a form. Does anyone have any examples of their implementation or is there an existing tutorial that I haven't yet stumbled across?

I'm sure someone has had a similar issue... is JQuery-File-Upload to appropriate tool or should I look at something else?

Answer

Kyle Carlson picture Kyle Carlson · Jun 18, 2013

I just wanted to throw my answer in here as well as Stone's. I spent nearly two solid days getting this to work (Stone was right, it was a PITA!), so hopefully my solution will help someone. I did it just a touch different than Stone.

My app has Features (a comic, puzzle, text-column, etc) and FeatureAssets (individual comic panels/color versions, question & answer files for a specific crossword, etc). Since FeatureAssets are solely related to one Feature, I nested the models (as you'll see in my upload form).

The biggest problem for me was realizing that my params[:feature_asset] that was being sent to the server was actually an array of my uploader'd file objects, instead of just the one I was used to working with. After a bit of fiddling with iterating through each file and creating a FeatureAsset from it, it worked like a charm!

Hopefully I'll translate this clearly. I'd rather provide a bit too much information than not enough. A little extra context never hurts when you're interpreting someone else's code.

feature.rb

class Feature < ActiveRecord::Base
  belongs_to :user
  has_many :feature_assets

  attr_accessible :name, :description, :user_id, :image

  accepts_nested_attributes_for :feature_assets, :allow_destroy => true

  validates :name,    :presence => true
  validates :user_id, :presence => true

  mount_uploader :image, FeatureImageUploader
end

feature_asset.rb

  belongs_to :user
  belongs_to :feature

  attr_accessible :user_id, :feature_id, :file, :file_cache

  validates :user_id,     :presence => true
  validates :feature_id,  :presence => true
  validates :file,        :presence => true

  mount_uploader :file, FeatureAssetContentUploader

  # grabs useful file attributes & sends them as JSON to the jQuery file uploader
  def to_jq_upload
    {
      "file" => file,
      "file_name" => 'asdf',
      "url" => file.url,
      "delete_url" => id,
      "delete_type" => "DELETE"
    }
  end

feature_assets_controller.rb

  def create
    @feature = Feature.find(params[:feature_id])

    params[:feature_asset]['file'].each do |f|
      @feature_asset = FeatureAsset.create!(:file => f, :feature_id => @feature.id, :user_id => current_user.id)
    end

    redirect_to @feature
  end

And not that it probably helps that much, but my feature_asset_uploader.rb is below. It's pretty stripped down.

class FeatureAssetContentUploader < CarrierWave::Uploader::Base

  storage :file

end

features _form.html.erb (similar to Stone's, but not quite)

<%= form_for [@feature, @feature_asset], :html => { :multipart => true  } do |f| %>
  <div class="row" id="fileupload">
    <div class=" fileupload-buttonbar">
      <div class="progressbar fileupload-progressbar nofade"><div style="width:0%;"></div></div>
      <span class="btn btn-primary fileinput-button">
        <i class="icon-plus"></i>
        <span><%= t('feature_assets.add_files') %>...</span>
        <%= hidden_field_tag :feature_id, @feature.id %>
        <%= hidden_field_tag :user_id, current_user.id %>
        <%= f.file_field :file, :multiple => true %>
      </span>
      <button type="submit" class="btn btn-success">Start Upload</button>
      <button type="reset" class="btn btn-warning">Cancel Upload</button>
      <button type="button" class="btn btn-danger">Delete Files</button>
    </div>
  </div>

It doesn't have error handling or any of the niceties it should have, but that's the barebones version of it.

Hopefully that helps someone out there. Feel free to ask me if you have any questions!

Kyle