Never use Turbolinks to load a specific route - ruby-on-rails

I know that I can use the data-no-turbolink attribute to ensure that a link (or many links) don't use turbolinks, but is there any way to ensure that a specific page will never be loaded via turbolinks?
I have a particular page that is broken when loaded via turbolinks, and instead of employing some hacks to fix this, I'd rather that this page just always load in the usual fashion.
I know I can add data-no-turbolink to all links going to this page, but then I'd have to remember to do this to every subsequent link to this page, which is pretty hacky.
Is there a better way?

To modify all new and existing URLs to remove turbolinks to a specific URL, read below:
Since you're wanting to always use "data-no-turbolinks" when linking to a specific page via link_to, you can modify the method to check the URL of its destination and set the data-no-turbolinks attribute.
Modify the link_to helper (be sure to replace ROUTE_url and ROUTE_path with the correct variables from your routes:
# application_helper.rb
def link_to(name, path, options = {})
no_turbolink_routes = [ROUTE_url, ROUTE_path] # Add URLs/PATHs here
if no_turbolink_routes.include? path
options['data-no-turbolinks'] = 'true'
end
super(name, path, options)
end

You can add the following code to your application's helper file. It will check the controller and action being called on the page, then return an HTML attribute if it matches.
# application_helper.rb
def no_turbolinks
"data-no-turbolinks='true'".html_safe if controller_name == "your_controller" && action_name == "your_action"
end
Then, in your application's HTML:
<body <%= no_turbolinks %>>
Ideally, this would only load on the specific page that you want it to; disabling turbolinks for that one page.

Related

Ruby on Rails : conditionally load scripts?

Hey,
I want to be able to put all of my pages' scripts in my application layout, and load each one for the appropriate page.
I came across this [question][1], and I thought I can put the scripts inside of partials and do it the same way that is indicated in the mentioned question, however I'm trying to find the best way for scripts.
Would it be something like :
yield :scripts if condition
or
unless condition
content_for scripts do
end
end
or any other 'best' way ?
Note: Each page has its own script saved under the same name of the page.
Solved
I relied on the recommendations of #damien at the comments with the link he provided to come up with something like this:
In my helpers:
def javascript_exists?(script)
script = "#{Rails.root}/app/assets/javascripts/#{[params[:controller], params[:action]].join('_')}.js"
File.exists?(script) || File.exists?("#{script}.coffee")
end
In my application layout:
<%= javascript_include_tag [params[:controller], params[:action]].join('_'), :media => "all" if javascript_exists?([params[:controller], params[:action]].join('_')) %>
So to each controller, and to each action in the controller, the appropriate .js file gets included if it exists.

Rails erb templating, link element visibility to action in yield?

In application.html.erb file, can I link HTML elements visibility to actions that are inside the <%= yield %> tag ?
For example if I have a header element and want to display a text there if <%= yield %> is displaying a specific action?
You can use the current_page? helper to determine if you're on a specific page (as identified with the given path parameters, etc.) and branch accordingly.
Or, if you need a more broad stroke you can use controller_name and/or action_name to get the current controller's name and the current action's name and branch accordingly.
Lastly, you can inspect params[:controller] or params[:action], which are sometimes more revealing than the above.

getScript jQuery path not working / Ruby on Rails

I've got a jQuery function that is called after a doubleclick on a list item.
app/assets/javascripts/tile/tile.js
$('#list > li').dblclick(function(){
// styling
$(this).toggleClass('liked');
// pass ID to controller
var movie_id = $(this).attr("data-id");
$.getScript("/likes.js");
});
Next to applying some new formats to said item my main goal is to make a database entry from my like controller. In this Railscast the index action from their comments controller gets called with this simple line.
$.getScript("/comments.js");
Additionally some JavaScript gets called from a index.js.erb file.
My first problem with understanding the example code from Railscasts is how they define the action. If I wanted to call the action createLike from my likes_controller how would I call it?
Secondly, my attempts so far have all failed because both the JavaScript file doesn't load and the action doesn't get called aswell.
Somehow I sense that I've messed up with the paths. Where should I locate the JavaScript files that should get called with the getScript function?
Files
app/assets/javascripts/likes/index.js.erb
console.log("Test");
app/controllers/likes_controller.rb
class LikesController < ApplicationController
protect_from_forgery
def index
Like.create(:user_id => current_user.id, :item_id => params[:id])
end
end
I believe the execution issue can be solved by moving index.js.erb from
app/assets/javascripts/likes/index.js.erb
to
app/views/likes
This is where Rails looks for templates to render (your script shouldn't be served by the asset pipeline). Rails tackles this through convention - your app automatically routes /likes to the index action.
If you want a more informative route, use the Rails routing guide to generate a new route and match it to the create_likes action in the Likes controller. Then,
$.getScript("/create_likes.js")
will know where to look
You can define action in controller like that:
class LikesController < ApplicationController
# another code
def createLike
# your action code
end
# another code
end
And you can call action like /likes/createLike.
In the folder PATH_TO_APP/app/views/likes create a file createLike.html.erb - there is will be a createLike view
Javascript files must be in the folder /PATH_TO_APP/public/javascripts
And best way to include javascript file is a javascript_include_tag like:
<%= javascript_include_tag "tile/tile.js" %>
tile.js file must be is into the /PATH_TO_APP/public/javascripts/tile directory.
And if you want to get javascript files with jQuery, you must put them in public/javascripts directory and call $.getScript('/javascripts/likes.js'); - there is an example.
P.S. I advise to look at getting started guide
The behavior you're wanting is different than what that specific Railscasts is addressing. It is specifically focused on the retrieving of new comments as they are created, without a page refresh. That is why you are running into issues following this guide.
First you will need to make sure you have a resources :likes in your config/routes.rb. From your code excerpt it looks like you are associating a like with a movie so make sure you make the route nested inside your resources :movies call. In the end your routes should look something like this:
resources :movies do
resources :likes
end
For the controller piece you will need to add a 'create' action to your controller. Assuming that your Movie model has_many :likes this is a simple version of what your action should look like:
def create
movie = Movie.find(params[:movie_id])
movie.likes.create(user_id: current_user.id)
end
You will also need to change your javascript code to make a post instead of a get request. That's because the http method is how Rails differentiates between a create and an index request as they both use the same url path (e.g. /comments.js). You will also need to have the url reflect that it's a nested resource within a movie. Here is modified version of your JS code with that change:
$('#list > li').dblclick(function() {
// Cached jquery this selector.
$this = $(this)
// pass ID to controller
var movie_id = $this.data('id');
$.post('/movies/' + movie_id + '/likes.js', function() {
$this.toggleClass('liked');
});
});
In regards to your .js.erb file, as stated by others, it should be placed in your app/views folder. However, due to your regular JS handling the logic you don't need to have it all.
This is just one strategy but there are quite a few other ways to handle JS interaction with Rails. If you want an example of using a js.erb (js.coffee in this case) view file you can take a look at this implementation. In that case all that is handling the click event is a link_to with the remote: true option which delegates it the jquery-ujs adapter.
Hope that helps!
This might not be close to your answer but I use $.getscript() to load those js/css files that i need once my page has rendered,which in turn improves the performance and reduces the page load time.This is the code I have used in my erb files.My shop_for_free_module.js resides in app/public/javascripts
<script type="text/javascript">
jQuery(document).ready(function($){
//this gives your protocol-http
var protocol = this.location.protocol;
//this gives your domain name-myshopptinsite.com
var host = this.location.host;
var initial_url = protocol + "//" + host;
$.getScript(initial_url + "/javascripts/eshop_js/shop_for_free_module.js");
});
</script>
...hope it helps.
try this out
Question: My first problem with understanding the example code from Railscasts is how they define the action. If I wanted to call the action createLike from my likes_controller how would I call it?
Answer:
class LikesController < ApplicationController
def create_like
Like.create(:user_id => current_user.id, :item_id => params[:id])
end
end
in routes.rb file
get '/create_like.js' => 'likes#create_like'
Question: Secondly, my attempts so far have all failed because both the JavaScript file doesn't load and the action doesn't get called aswell.
Anaswer: move app/assets/javascripts/likes/index.js.erb
code to
app/views/likes/create_like.js.erb
you need to pass item_id to
getScript method
$(document).ready(function() {
$('#list > li').dblclick(function(){
// styling
$(this).toggleClass('liked');
// pass ID to controller
var item_id = $(this).attr("data-id");
$.getScript("/create_like.js?item_id=" + item_id);
});
});

Rails path-helpers doesn't work in js.coffee.erb

In my Rails 3.2 app (Ruby 1.9) I get following error when using path helpers in Coffeescript.
undefined local variable or method `new_user_session_path'
In my partial _usermenu.html.haml that works fine:
= link_to t('user.login'), new_user_session_path
In my app/assets/javascripts/metamenu.js.coffee.erb that throws above error:
$.get("<%= new_user_session_path %>")
Isn't it possible to use x_path and x_url helpers in coffeescript erb's?
This is because you are not within the view context inside of your assets. Adding an erb extension to the file doesn't change this, it simply allows you to evaluate embedded ruby.
If this is a one-off scenario, your best bet is to simply use the string itself.
$.get("/sign_in")
If you really wanted to you could create a partial that output a script tag that output your helper methods into js variables and access them that way.
# in your layout
<%= render 'url_helpers' %>
# in app/views/layouts/_url_helpers.html.erb
<script>
window.new_user_session_path = "<%= new_user_session_path %>";
# add more if necessary
</script>
# in your coffeescript
$.get(#new_user_session_path)
Also worth keeping in mind that this will obviously never work for member routes where your passing an instance of a model to the url helper as that is definitely not available to coffeescript. Remember, in production assets are precompiled so you can't use anything dynamic. For that you can only really rely on setting up actions in your controller to respond to JS calls.
Old post, but still accessible from Google.
In rails 4 (and certainly at least 3 too) you can use the route helpers to insert your js files easily:
assets/javascript/my_file.js.coffee.erb
<% self.class.include Rails.application.routes.url_helpers %>
window.index_route = '<%= index_path %>'

Ruby on Rails form page caching including authenticity_token

I have a simple Ruby on Rails form which includes an authenticity_token. Unfortunatly, I missed that when you page cache this page then the Authenticity Token becomes invalid. I'm glad I figured it out however.
How do you solve caching in such a case?
As Matchu posted, you could implement point two from this post (same link he posted, but found via my Googling as well). This adds a dependency on JavaScript, which may or may not be something you want.
Alternatively, you could look into Fragment Caching. This allows you to cache certain portions of a page, but still generate the dynamic portions (such as forms with authenticity tokens). Using this technique, you could cache the rest of the page, but generate a new form for every request.
One final solution (but the least favourable), is to disable the authenticity token for that specific action. You can do this by adding the following to the beginning of the controller generating that form:
protect_from_forgery :except => [:your_action]
You can also turn off protect_from_forgery for the entire controller by adding the following to the beginning:
skip_before_filter :verify_authenticity_token
It doesn't seem to be a well-solved problem. Point two on this blog post describes how to accomplish the task by using jQuery, but that introduces a Javascript dependency. Weigh your options, I suppose.
You could render a custom tag in the cached markup and replace it with the form rendered on every request.
module CacheHelper
# Our FORM is deeply nested in the CACHED_PARTIAl, which we
# cache. It must be rendered on every request because of its
# authenticity_token by protect_from_forgery. Instead of splitting up the
# cache in multiple fragments, we replace a special tag with the custom
# form.
def cache_with_bla_form(resource, &block)
form = nil
doc = Nokogiri::HTML::DocumentFragment.parse( capture { cache("your_cache_key",&block) } )
doc.css('uncachable_form').each do |element|
form ||= render(:partial => 'uncachable_form', :resource => resource)
element.replace form
end
doc.to_html
end
end
And in your view, you just render an empty uncachable_form tag.
<%- cache_with_bla_form resource do %>
# cachable stuff..
<uncachable_form />
# more cachable stuff
<%- end %>
Yes, this can be considered as a Hack, but it won't loosen forgery protection, needs no JS, and decrease the performance gain from caching just a bit. I think someone implemented a similar pattern as a Rack Middleware.
I followed Niklas Hofer's general solution, but I found that his implementation did not match the exact semantics of the Rails cache helper. Namely, it attempted to return the cached HTML from the helper, rather than writing it to the buffer using safe_concat, which is what the Rails helper does.
The Rails helper usage is like this:
- cache do
= something
Whereas his solution required this syntax:
= cache_with_updated_csrf do
= something
For consistency, I would prefer that these work the same way. Hence I used this syntax:
- cache_form do
= something
Here is my implementation. It will also skip caching when caching is disabled, like the Rails helper does.
module CacheHelper
# Cache a form with a fresh CSRF
def cache_form(name = {}, options = nil, &block)
if controller.perform_caching
fragment = fragment_for(name, options, &block)
fragment_with_fresh_csrf = Nokogiri::HTML::DocumentFragment.parse( fragment ).tap do |doc|
doc.css("input[name=#{request_forgery_protection_token}]").each { |e| e['value'] = form_authenticity_token }
end.to_html
safe_concat fragment_with_fresh_csrf
else
yield
end
nil
end
end
As a more general solution, you could also replace all cached authenticity_tokens with the current ones:
module CacheHelper
def cache_with_updated_csrf(*a, &block)
Nokogiri::HTML::DocumentFragment.parse( capture { cache(*a,&block) } ).tap do |doc|
doc.css("input[name=#{request_forgery_protection_token}]").each { |e| e['value'] = form_authenticity_token }
end.to_html.html_safe
end
end
And use = cache_with_updated_csrf do instead of - cache do in your views. Kudos to Bernard Potocki for the idea.

Resources