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

asset-pipeline, javascript, ruby-on-rails

Solution

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.

Problem

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?

Original source