Menu
Alfredo Motta
  • Home
  • Newsletter
  • Articles
  • About me
  • Events
  • Books
  • Research
  • Learn to code in London
Alfredo Motta

Upload video files with progress bar using Rails, Paperclip and Javascript

Posted on May 24, 2015May 18, 2022

Rails does not help much when dealing with AJAX uploads by means of external JS libraries. I recently came across a case where a user on www.codementor.io was struggling to use JQuery-upload-file to upload a video to a Rails backend. The main reason to use the library was the progress bar feature, something that is missing in the Rails world. In this blog post I’ll show you how to implement the functionality in the simplest case possible.

The interface looks as follows:
Screen Shot 2015-05-24 at 10.54.59

The view looks like follow:

Ruby
1
2
3
4
5
= form_for @movie do |m|
  = m.text_field :title, placeholder: 'Title'
  = m.text_area :description, placeholder: 'Description', rows: '3'
  = m.file_field :video
  = m.submit 'Upload', id: 'fileUpload', data: { disable_with: 'Uploading' }

As you can imagine there is a Movie model in the backend ready to be created. The data: { disable_with: ‘Uploading’ } bit will disable the button when we submit our form. The actual form submission will be handled by jquery-upload-file.

On the JS code this is what we need now:

JavaScript
1
2
3
4
5
$(document).ready(function() {
var uploadObj = $("#movie_video").uploadFile({
    url: "/movies",
    multiple: false,
    fileName: "movie

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
",
    autoSubmit: false,
    formData: {
      "movie[title]": $('#movie_title').text(),
      "movie[description]": $('#movie_description').text()
    },
    onSuccess:function(files,data,xhr)
    {
      window.location.href = data.to;
    }
  });
 
  $("#fileUpload").click(function(e) {
    e.preventDefault();
    $.rails.disableFormElements($($.rails.formSubmitSelector));
    uploadObj.startUpload();
  });
});

This is far from perfect, but bear with me for the sake of this example. Javascript is populating the formData with the form parameters which will arrive to the backend together with the video. When the user click the submit button we do the following: (i) we avoid the default form submission with e.preventDefault() and (ii) we disable the button for multiple submissions with $.rails.disableFormElements($($.rails.formSubmitSelector));

The create action in the controller looks as follows:

Ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
  def create
    @movie = Movie.create(movie_params)
    @movie.user = current_user
 
    if @movie.save
      render :json => {
        :status => :redirect,
        :to => movie_path(@movie.id)
      }.to_json
    else
      render 'new'
    end
  end

A gotcha to keep in mind is that we do not use redirect_to here because we want the JS code to handle the redirection explicitly. This is why we return the new path in the JSON response. The create action is taking the parameters from the request and attaching the video to the model using the paperclip and paperclip-av-transcoder gem.

This is an example of parameters that we receive in the controller:

Ruby
1
2
3
4
5
6
[1] pry(#<MoviesController>)> params
=> {"movie"=>
  {"video"=>
    #<ActionDispatch::Http::UploadedFile:0x007fb1d00f31b8
     @content_type="video/quicktime",
     @headers="Content-Disposition: form-data; name=\"movie

Ruby
1
2
3
4
5
6
7
"; filename=\"TestVideo.mov\"\r\nContent-Type: video/quicktime\r\n",
     @original_filename="TestVideo.mov",
     @tempfile=#<File:/var/folders/fj/68q9tlrj4h30xpc_xxhbb4h80000gn/T/RackMultipart20150523-36995-mrhxma.mov>>,
   "title"=>"Cool video",
   "description"=>"This is a great video indeed"},
"controller"=>"movies",
"action"=>"create"}

The model will have the following (magic) code to attach the video:

Ruby
1
2
3
4
5
6
7
  has_attached_file :video, styles: {
    :medium => {
      :geometry => "640x480",
      :format => 'mp4'
    },
    :thumb => { :geometry => "160x120", :format => 'jpeg', :time => 10}
  }, :processors => [:transcoder]

Finally the controller simply returns a JSON object with the url for the next page where the user should be redirected after the upload is done. The JSON is picked up by the onSuccess() callback of the the jquery-upload-file library. At this point a simple window.location.href will do the trick.

Hope it is useful,
Enjoy

Disclaimer: This blog post covers the fastest way to have a progress bar for file uploads working on Rails. However I highly encourage you to allocate time for more configurable and maintainable solutions likes this one http://www.sitepoint.com/asynchronous-file-uploads-rails/

15 thoughts on “Upload video files with progress bar using Rails, Paperclip and Javascript”

  1. Bruno paulino says:
    June 4, 2015 at 3:07 am

    that is o good trick. It works for simple things. That solution solved my problem. Thank you.

    Reply
    1. mottalrd says:
      July 25, 2015 at 1:56 pm

      Thanks Bruno! Glad you liked it

      Reply
  2. Gleydson says:
    October 8, 2015 at 6:05 pm

    Thanks 🙂 It’s solved my problem 😀

    Reply
    1. mottalrd says:
      October 9, 2015 at 6:35 am

      I am glad it did!

      Reply
  3. Sebastián Palma says:
    April 30, 2016 at 7:38 pm

    And how can I show the video, as video_tag?

    Reply
  4. mottalrd says:
    May 1, 2016 at 9:15 am

    Yes Sebastián, you can use the Rails video_tag method to embedd the video in the page.

    Reply
  5. Rui Queiros says:
    May 1, 2016 at 3:15 pm

    I don’t understand… You use: $(‘#movie_description’) but I don’t find that id anywhere in the view. Where did you put it?

    Also, if my upload needs to be associated with an user, how can the app know which user is currently logged in? I had a hidden field to put there the member_id but that’s not the best way for sure :X

    Reply
    1. mottalrd says:
      May 5, 2016 at 6:28 am

      Hi Rui,

      You use: $(‘#movie_description’) but I don’t find that id anywhere in the view. Where did you put it?

      By convention the Rails form_for generates this id for the view. It is weird that you don’t find it. Are you using the same code I posted?

      Also, if my upload needs to be associated with an user, how can the app know which user is currently logged in? I had a hidden field to put there the member_id but that’s not the best way for sure :X

      Usually to keep track of the current user you rely on external gems like Devise. If you don’t want to go the gem way you can always keep the user_id in the session object of Rails. Check http://stackoverflow.com/questions/12719958/rails-where-does-the-infamous-current-user-come-from

      Let me know if I can help further!

      Reply
  6. daorren says:
    September 8, 2016 at 5:25 am

    Thank you for your tutorial first
    However, I come cross a problem that nothing happens, when I click submit button .
    I think the problem is that the upload ajax form created by the plugin just doesn’t submit properly, but I don’t know how to solve it.
    Any help would be appreciated

    Reply
    1. mottalrd says:
      September 10, 2016 at 2:30 pm

      Hi daorren, there is any code that you can share to see what could be the issue?

      Reply
      1. daorren says:
        September 11, 2016 at 11:31 am

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        # pins_controller.rb
        def create
            @pin = current_user.pins.build(pin_params)
            if @pin.save
                render :json => {
                  :status => :redirect,
                  :to => pin_path(@pin.id)
                }.to_json
            else
                render 'new'
            end
        end
        # _form.html.haml
        = simple_form_for @pin, html: { multipart: true } do |f|
            = f.input :image, input_html: { class: 'form-control'}
            = f.input :title, input_html: { class: 'form-control'}
            = f.input :description, input_html: { class: 'form-control'}
            = f.button :submit, class: "btn btn-primary", id: 'fileUpload', data: { disable_with: 'Uploading' }
         
        :javascript
            $(document).ready(function() {
                var uploadObj = $("#pin_image").uploadFile({
                    url: "/pins",
                multiple: false,
                fileName: "pin[image]",
                autoSubmit: false,
                formData: {
                  "pin[title]": $('#pin_title').text(),
                  "pin[description]": $('#pin_description').text()
                },
                onSuccess:function(files,data,xhr)
                {
                  window.location.href = data.to;
                }
              });
         
              $("#fileUpload").click(function(e) {
                    //e.preventDefault();
                $.rails.disableFormElements($($.rails.formSubmitSelector));
                uploadObj.startUpload();
              });
            });
         

        Above are all my code, only the model changes from your movie to this pin.
        f.input :image, input_html: { class: 'form-control'} This statement create a simple input, but I can see a rather complex div in HTML through Chrome Devtool.
        So I conclude that the plugin initial statement works. And my problem is that when I click the submit button, nothing happens. No request incoming in my rails log, only the button being disabled.

        Reply
        1. daorren says:
          September 11, 2016 at 11:35 am

          Made a mistake

          1
          2
          e.preventDefault();
           

          line 38 is not commented actually

          Reply
          1. mottalrd says:
            September 20, 2016 at 7:05 am

            Hi daorren,

            have you checked if using a normal form instead of simple_form changes anything?

            I see nothing obvious in there, but the problem is definitely in the frontend.
            Hope this helps,
            Alfredo

          2. daorren says:
            October 6, 2016 at 1:55 pm

            I have done mainly 2 things to make things work

            First, instead of using ‘m.file_field :video’
            I just use a empty div, for the plugin to do its work

            1
            2
            <div id="movie_video">video</div>
             

            Secondly, making some changes to the variables in js
            we shouldn’t use formData, or it would always be empty, because it’s called when the dom is loaded. Instead we should use dynamicFormData, as the project maintainer mentioned when he answer others’ questions
            and we should use val() instead of text() in form

            1
            2
            3
            4
            5
            6
            7
            8
            dynamicFormData: function(){
                    var data ={
                      "movie[title]": $('#movie_title').val(),
                      "movie[description]": $('#movie_description').val()
                    }
                    return data;
            },
             

  7. Faisal says:
    October 25, 2016 at 3:38 pm

    what to write in URL if I ‘m doing this for create/update

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Answer the question * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Tweets by mottalrd

Recent Comments

  • mottalrd on Not So Random Software #21 – Technical debt
  • mottalrd on Not So Random Software #24 – Climate Change Tech
  • mottalrd on A/B Testing, from scratch
  • mottalrd on A/B Testing, from scratch
  • Karim on A/B Testing, from scratch
©2023 Alfredo Motta | Powered by SuperbThemes & WordPress