I have a simple controller with a show and a index method. routes.rb has a resources :foo entry and when I go to localhost:3000/foo I get an missing template error even though I have an index.json.jbuilder file in the correct folder which is easily enough shown when I go to /foo.json
Shouldn't rails see that there isn't a html template for this particular view and use the json template automatically?
I am using rails 4.2.
Going to localhost:300/foo will by default use the :html format. Since you don't have the corresponding html view, you'll get the missing template error. If you want the route to default to :json you can specify it when defining the route
resources :foo, defaults: {format: :json}
From official documentation:
The next part of the message contains a hash. The :locale key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English - or "en" - template. The next key, :formats specifies the format of template to be served in response. The default format is :html, and so Rails is looking for an HTML template. The final key, :handlers, is telling us what template handlers could be used to render our template. :erb is most commonly used for HTML templates, :builder is used for XML templates, and :coffee uses CoffeeScript to build JavaScript templates.
This is why Rails can't find your .json.jbuilder view. So, in your case you can pass correct Accept HTTP header or override request format:
before_filter :default_request_format
def default_request_format
request.format = :json
end
or specify it in routes as described #Bart Jedrocha above.
Related
I am trying to use a different .erb file instead of default application.erb, I followed as its stated in https://guides.rubyonrails.org/layouts_and_rendering.html#finding-layouts
I have created a route in routes.rb as
get "/vue/v1/" => "vue#vue"
a controller as vue_controller.rb and its content is
class VueController < ApplicationController
layout "vue"
def vue
end
end
and in my view/layouts/, I also have created a file as vue.html.erb so that the controller can use it. The content of vue.html.erb is as simple as <%= javascript_pack_tag 'application' %> but whenever I go to "/vue/v1/", It only gave me error as
VueController#vue is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
what you are doing wrong is in your controller, you don't have a view for your vue action. Try adding a erb file in your views/vue
Update
This was a legacy app I inherited, and I found out that the previous developers had removed the rack code that converted browser POST requests into PUT/PATCH based on the _method param that Rails adds to your forms.
# config/application.rb
# This is the line that caused the problem...
config.middleware.delete ::Rack::MethodOverride
Once I removed that line and restarted the server, things worked as expected.
Original Question
When I post a Rails form using the standard resources in the routes file, it raises a route not found error when I'm trying to update an existing record:
No route matches [POST] "/admin/lookups/record_types/1"
The model is namespaced as app/models/lookups/record_type.rb
# model file
module Lookups
class RecordType < ApplicationRecord
# ...
end
end
# form in view file
<%= form_with model: #record_type, scope: :record_type, url: [:admin, #record_type], local: true do |form| %>
<%= form.text_field :value %>
<% end %>
# Request being sent
POST admin/lookups/record_types/1
{ record_type: { _method: "patch", value: "value" } }
# in routes .rb
namespace :admin do
namespace :lookups do
# Does not work
resources :record_types
# Works when explicitly written out
post "record_types/:id", controller: record_types, action: :update
end
end
When I explicitly write out the POST request in the routes.rb file, it works as expected.
I know that Rails is actually POSTing the request and using the _method hidden attribute to map the routes file. However, something isn't converting that request properly.
It's an application I inherited, and at one point it was exclusively an JSON API (no direct UI), so I'm wondering if there was something removed that converted the Rails _method param to the proper controller? I don't know what that would be, though.
This is the output of my rake routes:
admin_lookups_record_types
GET /admin/lookups/record_types(.:format)
admin/lookups/record_types#index
POST /admin/lookups/record_types(.:format)
admin/lookups/record_types#create
new_admin_lookups_record_type
GET /admin/lookups/record_types/new(.:format)
admin/lookups/record_types#new
edit_admin_lookups_record_type
GET /admin/lookups/record_types/:id/edit(.:format)
admin/lookups/record_types#edit
admin_lookups_record_type
GET /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#show
PATCH /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
PUT /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
DELETE /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#destroy
The problem seems to be the use of the scope argument to the form_with method.
If you take a look at the routes you'll note that the route to the update action uses PUT or PATCH whereas the route to the create action uses POST.
As the documentation of the FormHelper module states:
in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the form will be set to POST and a hidden input called _method will carry the intended verb for the server to interpret.
But nesting the special _method key inside record_type breaks this mechanism. Is the scope really necessary? I'd try removing it, it should work fine without it. The correct HTTP verb to use would be PUT or PATCH. Adding an additional POST route breaks the regular Rails structure without any real gain.
I have a jbuilder file to make a special json output for an object.
My routes has the resource added in as a collection block and going to the URL with .json at the end works fine. How do I force the json format in that method only? I know you can set respond_to :json in the controller overall but not for a single method. How do i do that?
this , :defaults => {:format => 'json'} after the get 'xxx' in the collection do block does it
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>'
An identical question has been asked before
AssetTagHelper::image_path outside views
but the solution does not work for Rails 3.
I have a requirement where I am returning some data using JSON format and part of the data needs to return the full path of the image. I am building the response in the controller and calling render :json. I am also specifying an asset_host in the environment.rb so I need a way to include that in the returned data from inside a Rails controller.
You can use view_context in your controller when doing 'view' tasks like generating links. The good thing about it is you don't have to include the view helpers in your controller.
e.g. in your controller you can create a variable which will be a html link with link_to or url_for if you just want the link, 'only_path' option set to false should give you absolute url.
link = view_context.url_for(:action => 'login', :controller => 'members', :only_path => false)
Hope this helps.