Get the asset url without fingerprint in Rails - javascript

Using javascript_url we can get the url of the asset:
<script src="<%= javascript_url 'company_widget' %>"
token="<%= current_user.token %>"
class="ofri-company-widget"
></script>
However, the javascript_url returns the url with the fingerprint:
domain.com/assets/company_widget-<fingerprint>.js
It seems this get cached forever, and whenever we need to make changes in the script, the third-parties using this script will have to reload it.
I noticed that accessing the file without fingerprint works too:
domain.com/assets/company_widget.js
Is there a way to tell javascript_url to not add the fingerprint? Or is there another better solution in this context?

http://guides.rubyonrails.org/asset_pipeline.html#turning-digests-off
You can turn off digests by updating config/environments/development.rb to include:
config.assets.digest = false

What you really need to do is to return your javascript file in controller action, so have control over the cache for this particular file.
In your controller action you should do something like:
def company_widget
response.headers["Expires"] = 1.day.from_now.httpdate
expires_in 1.day, public: true, must_revalidate: true
format.js do
asset = open(ActionController::Base.helpers.asset_url('company_widget.js', host: host))
send_data asset.read, :type => asset.content_type
end
end
def host
request.protocol + request.host_with_port
end
Then you can point to controller action and be sure that you have control over what is returned.
<script src="http://example.com/company_widget.js"></script>
Expires header is older way of setting cache, while expires_in Rails method will set Cache-Control header which is the best way to manage your cache. It takes precedence over Expires.
Additionally you can take a look on a great article about caching: https://www.mnot.net/cache_docs/

Related

Rails update element based on AJAX request?

I've been reading a lot about Rails and AJAX and 5.1 Unobtrusive javascript. It explains a lot about responding to Rails version of AJAX calls with a .js file for example.
However what im wanting to do isn't serving up an entire .js file, it's simply updating an element after a <% link_to %> POST request. From my understanding setting remote: true submits it as a AJAX request.
Essentially I have a "Post" which a user can like via a linked Like button. This sends a POST request to the "Post" controller which updates a post to liked and adds a like to the post.
Unfortunately to see the effects of the post being liked (Which is simply that the link changes color as well as the font-awesome icon) you need to refresh the page. I basically want it to update without needing refresh.
I "think" based off what i've read I need to make a respond do and respond via .js to the request with a .js file in the view I want to update (for instance if the controller action is called "like", maybe a like.js.erb file in the view im updating?). But I don't want to serve an entire new page..or would this simply just run the .js?
Then I could do something like $('i.fa-icon#id').style.color = "blue" or something? (Im assuming I can send data from the controller to the .js.erb file?). Not sure the best way to do this, don't rails elements a lot of times have some sort of data-attribute or something (Im still a beginner at this).
Your description is quite correct!
Opposed to the other answer, you don't even need a event listener but as you said you want to have a respond_to in the controller.
So starting from the html:
# post/index.html.erb
<div id="like-button">
<%= button_to "Like this post", post_path(#post), remote: true %>
</div>
Note, that when you use a button_to helper it'll be a POST request by default.
If you click it, it'll go to the controller#update, which you want to change to this:
#posts_controller.rb
...
def update
#post.save
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js # <-- will render `app/views/posts/update.js.erb`
end
end
Note: the format.html is rendered when JS is disabled.
Now in the scenario that JS is enabled, it executes the app/views/posts/update.js.erb file. It can look like this:
const likeButton = document.getElementById('like-button');
likeButton.innerHTML = '<%= j render "posts/liked-link", post: #post %>';
What is the last line doing? Of course, you can change the style directly with the JavaScript, but you can also render a new partial - and this you will create in a new html file:
# app/views/posts/liked_link.html.erb
<div id="like-button ">
<p>"You liked this post!" </p>
</div>
I just changed the link/button to ap now, but of course you can do whatever you want.
Hope that makes sense :)
Not sure if I understand the question, but if you want to update like button:
What you want to do is to add an event listener to the button, and when clicked it makes a POST request to whatever route handles the likes(with the correct parameters) and your controller should respond with the like object (or whatever in the database gets stored). Have your post request on success method to grab the like button and change it to whatever you want it to look like
$(“#like-btn”).click(function(){
Rails.ajax({
url: "/some/url/to/like/controller",
type: "post",
data: [your post data],
success: function(data) { $(`#${ data[“btn-name”] }`).attr(“color”, “blue”; }
})
}
You can stick this script right in the bottom of the html page
You don’t have to do it exactly like this, just giving you an idea of how to set up the pattern of having JavaScript and Ajax handle the post request and updating of the frontend instead of using html buttons

Respond with *.js.erb using nonce strategy for CSP

I'm implementing a CSP using rails 5.2.1 content security policy DSL. I've got my policy set to something like:
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.connect_src :self
#...
policy.script_src :self
end
# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
I also have <%= csp_meta_tag %> in my application.html.erb
At this point I need to add a nonce: true flag to any inline scripts for these to satisfy the policy. I've done this and it works as expected. However, I'm having trouble maintaining existing AJAX style functionality. For example, I have something like (note the remote: true):
# index.html.erb
<%= link_to create_object_path, id: "#{object.code}",method: :post, remote: true do %>
<button type="button">Create object</button>
<% end %>
In my controller
def create
#object = current_user.object.create
respond_to do |format|
if #object
format.js
else
redirect_back
format.html
end
end
end
In my *.js.erb file
$("#<%= #object.service.id %>").text("Added!");
The object is successfully created but I believe the policy is blocking the above "Added" success message that I add to the DOM. I have not seen any errors in the console so I'm not sure where to go from here.
My understanding in this scenario is script tags are temporarily inserted with the contents of the *.js.erb file and these script tags do not contain the nonce. Or, it is a mismatch.
I've been stuck on how to troubleshoot from here. Any guidance here is much appreciated even if different architectural pattern for sending data to client is the way forward. Thanks in advance.
I ran into a similar issue. In my case, it didn't refuse to run the js.erb file itself but rather scripts in templates nested within that file through the use of render. So, this answer may have limited utility to your specific case. That said, I did try to reproduce your issue using Rails version 6.1.1 and couldn't.
However, even if you get past the initial hurdle of getting just your .js.erb file to run, you can still run into the issue of nested scripts: if your .js.erb file renders a template that contains a script tag. That script won't run because the request from which it originated assigns it a new nonce, which won't match the nonce in the meta tag.
So, to those coming here from a search engine as I did, here's the general strategy I pursue to get async embedded JS working with CSP for that nested case and assuming the .js.erb file itself runs. Using your case as an example:
Send the nonce along in the AJAX request. I suppose you won't get around writing some custom JS to send the request. Something like:
document.getElementById('<%= object.code %>').addEventListener('click', e => {
e.preventDefault(); // So we don't send two requests
fetch('<%= create_object_path %>', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({
nonce: document.getElementsByName('csp-nonce')[0].content
})
});
});
This sends the nonce from the meta tag to the server in the form of a nonce parameter.
You may need to remove remote: true from your link for this to work. And of course, this script will itself need to be 'nonced' or else it won't run!
Assign the nonce to a #nonce instance variable in the controller:
#nonce = params[:nonce]
Wherever you render scripts, do:
<%= javascript_tag nonce: #nonce || true do %>
...
For those wondering how to get the same to work with their existing asynchronous forms:
Add this form field: <%= hidden_field_tag :nonce %>
On form submit, assign the nonce from the meta tag to the hidden field:
document.getElementById('id_of_submit_button').addEventListener('click', async e => {
document.getElementById('nonce').value = document.getElementsByName('csp-nonce')[0].content;
});
In this case, you don't want to prevent the default behavior on the event because you want the form to submit.
Then continue with step 2 above (assigning the nonce to a controller instance variable).
I hope as a general strategy this is useful to some. And I hope it can serve as inspiration for how to get the .js.erb file itself to run.
UPDATE: Of course, for your specific (but limited) use case, you could simply return the object's service id as part of some JSON object you return to the client instead of rendering a .js.erb template. I say "limited" because this won't work for people who really need to render templates.
If you did want to render your .js.erb file, I suspect something like this could work for your case as well, where instead of checking whether the HTTP_TURBOLINKS_REFERRER header is present, you check for request.xhr?. Just know that starting in newer Rails versions, remote: true doesn't set the requisite header for request.xhr? to work anymore. But since you're on 5.2.1, it may work for you.

Rails and AJAX remote: true what else is required?

I'm confused about remote:true in Rails forms, I thought some Javascript was required to make it asynchronous but this just seems to break my page.
Here is a really simple index.html.haml that includes a partial to show all appointments:
%h1 Calander
%h2 AppointmentsController
%h3 Make a new appointment
= form_for #appointment, remote: true do |f|
= f.text_field :title
= f.text_field :appt_time
= f.submit 'Make appointment'
#appointments
=render 'appointments'
Here is the previously mentioned partial:
-#appointments.each do |a|
%h3= a.title
%p= a.appt_time
Controller methods for index and create:
def index
#appointments = Appointment.order('appt_time ASC')
#appointment = Appointment.new
end
def create
#appointmet = Appointment.create(appointment_params)
redirect_to :root
end
Now this works fine. I can add a new appointment, hit submit and the new appointment shows up without the page refreshing, I think because I have included remote: true. So do I need to add anything else to handle the request? Am I violating best practices by not including something to handle this request and relying entirely on remote: true?
Nothing more required unless you want some callback after ajax call. You did not break any conventions. You can read this document to get ride of confusion.
Let's take a step back.
Web applications can respond to different request formats. Rails has built-in format handling.
So a request might ask for index via HTML, which response with an HTML file. It might also request index via JSON, XML, PDF or even JavaScript.
Whenever you add remote: true you are telling your form make a POST request via JS instead of HTML.
In your views you will have a bunch of HTML.ERB files. These views are request responses.
So to handle a JS request to index, you will need a app/views/appointements/index.js file.
This will be sent as the response to the request and the browser will know what to do with a JS response.
In index.js you can write JS that will be executed once the response is received.
You can also load partials into the page.
For example:
# app/views/appointements/index.js
$('#appointements').html('<%= j render "appointements" %>')
Which will render the partial content as a JavaScript string for the response.
http://guides.rubyonrails.org/working_with_javascript_in_rails.html

Rails: Passing controller variable to JS using ERB slows page?

There are two ways I know of to pass variables defined in a controller (/action) to JS...
The official way is
.js.erb:
var banana = "<% #banana %>"
Another way (that I'm currently using is)
.html.erb
<span id="banana-variable" style="display:none"><% #banana %></span>
.js
var banana = $("#banana-variable").html()
This js file is loaded on multiple actions/views across the controller. It makes sense to me to not use a .erb extension: users cache it the first time they hit any action/view in the controller. They then won't have to download different versions of the file when they browse to different pages. Am I right?
Yes, are right. The javascript will be cached on the client's browser.
Still you want to send data on the 'js' or script files you can use this gem called Gon.
http://railscasts.com/episodes/324-passing-data-to-javascript
I recommend you use gem 'gon', which is thoroughly introduced in RailsCast. It makes your controller cleaner. Your method will make it more troublesome if you're trying to pass an array or hash to js.
I think your issue is that you are using <% %> which will execute the code, instead of <%= %> which will execute the code and render the result back into the template.
With Gon you can access the page only after the html page is loaded. From what I understand it uses web page as a proxy to transfer data from rails server to javascript.
But if you want to use the variable at any point of time you want, you can do an ajax(post) request from javascript to the rails controller and retrieve the value as json.
Example:
AJAX Request:
$.ajax({
type: "POST",
url: <PageURL>,
async: false,
dataType: 'application/json',
success: function(data){
data = JSON.parse(data);
},
});
your "data" will contain the returned value.
On the controller side it should be:
Controller:
def <PageURL>
render :json => {:result => <value_you_want_to_return>}
end
Do not forget to define the controller's path in routes.rb file.

Is it possible to parse Ruby instance variables in coffee script asset files?

I am attempting to pass in instance variable from my rails app to the associated coffeescript file, but it currently does not seem to be parsing. What am I missing?
locations.js.coffee.erb
$.ajax
url: "/map_groups/<%= #id %>.json"
type: "get"
dataType: "json"
async: false
success: (response) ->
exports.response = response
locations_controller.rb
def index
#id = (params[:id]) ? params[:id] : 1
#locations = Location.all
end
But this is the error showing up in the console:
Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost:3000/map_groups/.json
Is there something I can do to get the instance variable to parse?
note:
I know the variable exists, because it is being passed to the view.
edit: What I am trying to do
Most of my data is being sent via JSON, and I have created a custom route in order to let the coffeescript know what json data to pull:
get "/locations/map-group/:id", controller: :locations, action: :index, as: :map_group
If you refer back to my controller - you will see that if a user visits plain old /locations the ID defaults to 1. Otherwise, the id is whatever gets specified in the URL. The coffeescript file needs to pull data relevant to that ID through an AJAX call. How can I tell the coffeescript what that ID is?
What I would do instead
I would strongly recommend not using Ruby instance variables to generate CoffeeScript, if you can avoid it. I would suggest using a library like this to handle the use case you are considering:
https://github.com/railsware/js-routes
# Configuration above will create a nice javascript file with Routes object that has all the rails routes available:
Routes.users_path() // => "/users"
Routes.user_path(1) // => "/users/1"
Routes.user_path(1, {format: 'json'}) // => "/users/1.json"
Routes.new_user_project_path(1, {format: 'json'}) // => "/users/1/projects/new.json"
Routes.user_project_path(1,2, {q: 'hello', custom: true}) // => "/users/1/projects/2?q=hello&custom=true"
Routes.user_project_path(1,2, {hello: ['world', 'mars']}) // => "/users/1/projects/2?hello%5B%5D=world&hello%5B%5D=mars"
This plus HTML5 data-* tags would help you pass along the identifying information you need in your JavaScript:
http://html5doctor.com/html5-custom-data-attributes/
For example:
<div id="my_awesome_location" data-location-id="<%= #location.id %>">...</div>
Then load your ajax like this:
id = $("#my_awesome_location").data('location-id')
$.ajax
url: Routes.map_group_path(id) #=> "/map_groups/#{id}.json"
type: "get"
dataType: "json"
...
How to do it anyways
However, if you absolutely must use ERB style tags in your CoffeeScript, you can do it by using coffee.erb file extension:
http://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets
2.3.3 JavaScript/CoffeeScript and ERB
If you add an erb extension to a JavaScript asset, making it something such as application.js.erb, then you can use the asset_path helper in your JavaScript code:
$('#logo').attr({
src: "<%= asset_path('logo.png') %>"
});
This writes the path to the particular asset being referenced.
Similarly, you can use the asset_path helper in CoffeeScript files with erb extension (e.g., application.js.coffee.erb):
$('#logo').attr src: "<%= asset_path('logo.png') %>"
Why you probably don't want to do it that way
The reason I would suggest using the above library versus straight ERB is the tight coupling that you have between controller/views and assets.
It also means that you have to have your who entire app pre-loaded before you can do asset compilation, so you'll have that bite you in the behind if you try to deploy to a PaaS like Heroku.
https://devcenter.heroku.com/articles/rails-asset-pipeline
The app’s config vars are not available in the environment during the slug compilation process. Because the app must be loaded to run the assets:precompile task, any initialization code that requires existence of config vars should gracefully handle the nil case.
Basically, if you change your controller even a little bit, you risk breaking your assets. It's best to keep them as separate units and to introduce a mediation layer that handles what you are trying to solve.
As Farley Knight says, yes you can, but please don't for the reasons he stated.
What has worked best for me is hidden fields with the data inside my ERB file. Then since you're using JQuery, just use idField.val() in your url for the $.ajax call.
Hope that helps.

Categories

Resources