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

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 %>'

Related

Named route helper in javascript

If I'm doing some ajax stuff, I can get where a form 'normally' goes by grabbing its 'action' attribute:
'Sign Up': ->
post_url = $('#form').attr 'action' #=> users/sign_up (for example)
However, if I have this in my routes file:
post 'users/sign_up', to: 'users#create', as: :user_create
I'm wondering why I can't just do this:
'Sign Up': ->
post_url = <%= user_create_path %>
While erb tags are supported, I get this error when using a named route:
undefined local variable or method 'user_create_path'
Just wondering why this is happening... I mean using the named route helpers could be excellent. Although it may be less confusing to grab it from the form. Still think rails / coffeescript gem should implement this functionality.
The named routes helpers are probably not being included outside a controller/view.
You can access them in Rails.application.routes.url_helpers.user_create_path.
You could also use a gem like js-routes to do the job for you.

What does xxxx_path mean in ruby on rails

I try to add new controller and model use name foo and foos_controller, hope foos_path can redirect. Doesn't work.
A origin code here (working):
href="<%= contacts_path %>"
After I add new controller and model follow name convention I try use the same (Not working):
href="<%= foos_path %>"
And this contacts_path is not defined anywhere else in rb project.
what does xxxx_path mean and how to use it?
Rails follows convention to handle roots of application
when we execute this command
rails g scaffold foo
it generates routes along with your model, controller and views.
it generates a line in routes.rb as
resources :foo
this line makes you access all the actions of your controller
for example:
foos_path: # redirects you to the index page of your foos controller
new_foo_path: # redirects you to the create page of your foos controller etc.,
please go through this link for reference: http://guides.rubyonrails.org/routing.html
If you go to your terminal and type rake routes, it will list your currently defined routes. On the left side you'll have their prefix. For example, contacts might route to the index action in ContactsController.
The path suffix is a way of referring to those routes inside your code.
If foos_path is giving you an error, that means you either have not yet defined that route for the resource, or you have but it is called something else (if you defined it manually with the as: option).
More info can be found in the Rails Guide for Routing.
You'll be best reading up on this documentation
Path Helpers
Basically, the path_helpers of Rails are designed to help you define routes for your links etc in the most efficient way possible.
The bottom line is the path helper will take routes defined in your config/routes.rb and then allow you to call them dynamically - IE:
#config/routes.rb
resources :photos #-> photos_path
The path names are typically from your controllers, allowing you to route to the various actions inside them. As Rails is built around being a resourceful structure, it will by default create routes for the "standard" controller actions:
link_to
In order to use the path helpers effectively, you'll be best using the rake routes command in cmd, or by simply typing an invalid url into your app's address bar
I notice you're using the standard HTML <a href=""> tag in your question. You'll be much better suited to using the link_to helper of Rails:
<%= link_to "text", your_path %>

Possible to use ERB/slim template instead of EJS (JST) after Ajax update?

I've seen a lot of questions around this topic but no definitive solutions. I have a Rails 3.2 app that leverages ERB/slim and Coffeescript/EJS/Backbone. I inherited this codebase so some of these peripherals are a little over my head.
Problem
My venue view has a section that displays tips that were submitted to the venue. The list of tips has a "sort" function facilitated by JavaScript. The associated CoffeeScript file has event listeners for clicks on links for "recent" and "popular". On click, the JS does some work and utilizes Rails scopes to resort the list. I'm building out this tips list to include a little more data, specifically including the Rails helper time_ago_in_words. The original code updated the div containing the tips using a JST/EJS template in the Javascripts/templates asset directory.
These are the specific problems I encounter:
If I add .erb to the file chain (after updating the EJS interpreter to look for a different evaluation and interpolation pattern as to not conflict with ERB), I can evaluate basic Ruby expressions. The partial, however, fails to have access to the same tip variable that the JavaScript references. I know this is because Rails is server side and JS is client side. Consensus seems to be no, but is there a way to get that data to Rails?
While basic Ruby expressions can be evaluated (Time.now, for example), Rails helpers such as time_ago_in_words fail, complaining that the method is undefined for the class, though I am absolutely passing in the proper date object. Can Rails/helpers not be evaluated in this .EJS.ERB chain?
I can circumvent all of these problems if there is a way to reference the original ERB/slim partial used on load after the sort is performed. Is this possible? Right now the Coffeescript file called uses render and JST to call on the EJS template. Is it possible to somehow reference the original partial but with the updated sort?
Here's the relevant code.
##show.html.erb (venue)##
#tips
p.sort.span4
| Sort by:
= link_to 'Recent', '#', class: 'selected', id: 'sort-tips-recent'
| |
= link_to 'Popularity', '#', id: 'sort-tips-popularity'
#tip-list
= render resource.tips.by_recent
##tip_list_view.js.coffee##
class CS.TipListView extends Backbone.View
el: "#tip_module"
events:
"click #sort-tips-recent": "sortByRecent"
"click #sort-tips-popularity": "sortByPopularity"
initialize: ->
console.log("ERROR: View must be initialized with a model") unless #model
#model.bind('reset', #render)
render: =>
tips = #model.map (tip) -> JST['templates/tip'](tip: tip)
#$('#tip-list').html(tips.join("\n"))
sortByRecent: (e) ->
e.preventDefault()
#model.enableSortRecent()
#updateSelectedFilter('recent')
sortByPopularity: (e) ->
e.preventDefault()
#model.enableSortPopularity()
#updateSelectedFilter('popularity')
updateSelectedFilter: (sort) ->
#$('p.sort a').removeClass('selected')
#$("p.sort a#sort-tips-#{sort}").addClass('selected')
##tip.jst.ejs.erb (called from tip_list_view.js.coffee after sort change##
<div class="tip">
<p class="tip-author">
<$= tip.get('user').username $>
</p>
<p class="tip-timestamp">
Eventually, time goes here
</p>
<p class="tip-likes pull-right">
<$= tip.upvoteCountText() $>
</p>
<p id="<$= tip.domId() $>">
<$= tip.get('text') $>
<$ if(tip.get('can_upvote')) { $>
upvote
<$ } $>
</p>
<div>
Definitely at a loss here - any and all help is greatly appreciated. Let me know if there is any other detail or code I can provide for background. Thanks in advance!
With your ejs.erb, Rails is generating the template, but has no idea what you will use it for. Then your Backbone app is fetching the tip from Rails via ajax, and populate the model accordingly. Then it's your backbone app that populates and renders the template. Rails cannot chime in here, at least not in this configuration where backbone expects json and then renders the template. Everything is happening in the front-end.
The only thing you can do is ask Rails to give you the result of your helpers inside the json. I am guessing you have a Tip.rb model and it should implement:
include ActionView::Helpers::DateHelper
...
def as_json(options = {})
{
:name => self.name,
... (all the model attributes that you want to expose)
:date_age => time_ago_in_words self.created_at
}
end
That being said, I would def use a client side solution, also because: http://rails-bestpractices.com/posts/105-not-use-time_ago_in_words

Rails static html template files in the asset pipeline and caching in development mode

I'm building a website using AngularJS and Rails. The HTML files that I'm using for templates are stored under /app/assets/templates and each time I update a route or change something inside of a nested partial inside of a template I need to "touch" the highest level file in the /app/assets/templates directory for the html file I'm changing.
So if I have a page "edit.html" which loads a partial "_form.html" then whenever I update a route or change something in _form.html I need to make sure that edit.html is touched.
This is annoying and very finicky. Is there any way to inform the asset pipeline/sprockets to avoid caching for the app/assets/templates directory?
The best solution I've found to this is not to use the asset pipeline for HTML template files.
Instead make a controller called TemplatesController and create only one action.
Then map all template URLs to that using a route such as:
get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/ }
Then move all the template files into app/views/templates
Then inside the controller, setup the following:
caches_page :page
def page
#path = params[:path]
render :template => 'templates/' + #path, :layout => nil
end
This way all of your template files will be served from the controller and then will be cached into public/templates. To avoid cache problems, you can create a timestamp path into the template route so that your cached files are delivered with a version:
get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }
This way you can have a new timestamp each time you upload the website and you can store the templates folder anywhere you like. You can even store the templates folder on S3 and have an assets URL for that. Then wherever your template files are addressed, you can use a custom asset method:
templateUrl : <%= custom_asset_template_url('some/file.html') %>
Where:
def custom_asset_template_url(path)
"http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end
Then just make the asset redirect to the Rails server if it's not found and it will be generated. Or all template files can be pre-generated once uploaded.
There's a much (much!) better way to deal with this.
<%= path_to_asset("template_name.html") %>
That will return a fully working file from the asset pipeline, which can use ERB, etc. It's undocumented, but it's a part of sprockets / the asset pipeline.
To my mind, several things are needed here:
The templates should be namespaced, so people templates go in the app/views/people/templates directory
The templates are entirely static, so no before filters should be called.
The templates should be cached, making them very fast.
Here's a possible solution using a Rails concern:
# Allows static content to be served from the templates
# directory of a controller
module HasTemplates
extend ActiveSupport::Concern
included do
# Prepend the filter
prepend_before_filter :template_filter, only: [:templates]
# Let's cache the action
caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
end
# required to prevent route from baulking
def templates;end
# Catch all template requests and handle before any filters
def template_filter
render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
rescue ActionView::MissingTemplate
not_found layout: 'blank'
false
end
end
Notice we are returning the template in a prepended filter. This allows us to return the static content without hitting any other filters.
You can then create a route, something like this:
resources :people do
collection do
get 'templates/:template' => 'people#templates', as: :templates
end
end
Your controller becomes simply:
class PeopleController < ApplicationController
include HasTemplates
end
Now any file placed in the /app/views/people/templates can be served at speed from a url.
Expanding on RandallB's answer a bit; this is mentioned explicitly in the documentation on the Asset Pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Note that you have to append the extension .erb to your .coffee file to have this work. (e.g., application.js.coffee.erb)
You can try gem js_assets (https://github.com/kavkaz/js_assets).
This allows you to function asset_path in javascript code that emulates a similar method of sprockets.
Cleanest solution is to precompile your html assets using ejs and serve them as other javascript files.
No http query
minification
Possibility to pass js data to you templates to make them dynamic (ejs is a port from underscore template and basically
behave like erb for javascript)
It basically work like that :
#in you gemfile
gem 'ejs'
#in app/assets/javascripts/templates/my_template.jst.ejs
<p>my name is <%= name %> !<p>
#in your application.coffee
#= require_tree ./templates
JST['templates/my_template'](name: 'itkin')
=> '<p>my name is itkin !<p>'

Can I use CoffeeScript in the views executed on render.js?

What do I need to do so that I can use CoffeeScript in the Rails JS views? For example:
def index
format.js { render :layout => false }
end
What would I need to do in order for Rails to use index.js.coffee?
Johnny's answer is correct. If you look at the pull request linked to from the CoffeeBeans page, you have dhh saying
Once we have a fast, clean implementation, it's welcome in core. 3.2 is a more likely target, though.
I briefly talked with Sam Stephenson and Josh Peek about this at Railsconf, since this was a missing feature people had asked me about after my CoffeeScript talk. After all, Rails 3.1 is pushing CoffeeScript as a default pretty hard; it seems odd that there are places where pure JS has to be used. Sam's reaction was that this wouldn't be efficient, because you'd have to fire up the CoffeeScript compiler on every page request, even in production. That's because code like
<%= coffee_script_tag do %>
alert "coffee script is #{verb}!"
<% end %>
creates an ERB interpolation (not a CoffeeScript interpolation—unfortunate that both use the same syntax), potentially yielding a different string of CoffeeScript code on every request. And there's no way to tell, from the coffee_script_tag implementation, whether the given code is going to be the same every time (i.e. whether there's an ERB interpolation or not).
Now, the CoffeeScript compiler is very fast, but compiling to JavaScript is still going to add a little extra time to each request. So the Rails team is hesitant to encourage the practice.
For the sake of efficiency, and to avoid the ambiguity between ERB interpolations and CoffeeScript interpolations, you should probably keep your CoffeeScript somewhere (perhaps as a .coffee file in the same directory as your view) and compile it to JavaScript by hand.
It's not yet supported in 3.1. You will need to use Coffeebeans.
Update: It is now supported in rails 3.2
This is now working in Rails 3.2. For example, I have a resource named book. This resource has a file at app/views/books/index.html.erb with the following:
<%= link_to 'test me', new_book_path(color: 'blue'), remote: true %>
Then I have a file at app/views/books/new.js.coffee at ≈ with the following code:
test = ->
'this is a test'
console.log test()
console.log( "<%= params[:color] %>" )
I see:
'this is a test'
'blue'
in my browser console.
if you dont want to install coffeebeans, heres a sort of quick and dirty way to do it by hacking into the erb outputter a bit :
<%
require 'coffee-script';
def coffee_script; CoffeeScript.compile yield '' end %>
<script type="text/javascript">
<% compiled = coffee_script do |_erbout|%>
->
console.log 'works! this part is coffeescript!'
<% end %>
<% _erbout.concat compiled %>
</script>

Resources