Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asset pipeline cannot return @variables while using ruby coding in .js.erb in Rails

I am following the https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails#jquery-file-upload-callbacks Tutorial to use s3_direct_upload on local development environment. There's some javascripts which set the fileupload function for each file fields. It all works great if I simply include the codes right below the form, but it just doesn't work if I put the code in a js.erb file under app/assets/javascripts/. The error is:

ActionView::Template::Error (undefined method `url' for nil:NilClass
  (in /s3_direct_upload_gem_test/app/assets/javascripts/s3_direct_upload.js.erb)):
    18:
    19: <%= @s3_direct_post.url %>
    20:
    21: <%= javascript_include_tag :s3_direct_upload %>
  app/assets/javascripts/s3_direct_upload.js.erb:14:in `block in singleton class'

As you may see, line 19 from the above code is used to print out the @s3_direct_post.url. It was correctly printed when I include all the javascript in the file. If I trace the line 14 in the s3_direct_upload.js.erb, it is the url line:

fileInput.fileupload({
  fileInput:       fileInput,
  url:             '<%= @s3_direct_post.url %>',
  type:            'POST',
  autoUpload:       true,
  formData:         <%= @s3_direct_post.fields.to_json.html_safe %>,

It seems like that for some reason, the javascript file under the assets folder (in the asset pipeline) is compiled separately and so @s3_direct_post, which is set in the Controller is not set here.

Of course I can always put those in <script> at the viewer file but it is not quite elegant. Is there any way to separate page-specific js coding and solve the problem above?

like image 583
Quin Avatar asked Jul 16 '14 15:07

Quin


2 Answers

All JS files within /assets/javascript are part of the asset pipeline. In production the asset pipeline is compiled beforehand when your Rails app is deployed (e.g not on each request). This is why @s3_direct_post.url is nil.

I agree that injecting the whole JS code in the view is less than ideal and not very elegant. In the past I have come up with approaches that take inspiration from JS frameworks like Google Analytics, where only 2-3 lines of JS are placed in the HTML:

/* /assets/javascripts/s3_direct_upload.js */
window.MyApp = {};
window.MyApp.config = {};

window.MyApp.config.getS3Url = function() {
  if(typeof(window.MyApp.config._s3Url) == ‘undefined’) {
    throw “No S3 URL configured”;
  }
  return window.MyApp.config._s3Url;
};

window.MyApp.config.setS3Url = function(url) {
   window.MyApp.config._s3Url = url;
}

// ...
$('#upload-button').on('click', function(){
  fileInput.fileupload({
    fileInput:       fileInput,
    url:             window.MyApp.config.getS3Url(),
    type:            'POST',
    autoUpload:       true
  })
});

Then the view only needs to reference the Config API you've created:

<script type=“text/javascript”>
  window.MyApp.config.setS3Url('<%= @s3_direct_post.url %>');
</script>

However if you're really determined not to have any JS in the views. You could load the configs via a dynamic JSON request:

class JSConfigsController < ApplicationController
  def index 
    configs = {
      's3URl' => @s3_direct_post.url
      # etc
    }

    respond_to do |f|
      f.json do 
        render json: {config: configs} # => {"config": {"s3Url": "http://the_url"}}
      end
    end
  end
end

Then you can load all the configs via ajax by requesting /js_configs.json. However this approach requires a bit more care due to the asynchronous nature of Ajax. E.g you have to be careful to not call JS functions that rely on configs until the Ajax request has finished being retrieved.

like image 183
Pete Avatar answered Nov 03 '22 01:11

Pete


finally I work around this problem by using another s3 related gem: s3_file_field and it just works well even without heavy tweaking. unfortunately I can't find all related information through my study on solving this problem.

I hope this help!

like image 32
Quin Avatar answered Nov 02 '22 23:11

Quin