Rails 6.1.4 Deprecation warning: Rendering actions with '.' - ruby-on-rails

I'm receiving a deprecation warning when running rails test. That warning being below. Any help is appreciated in identifying what I'm doing incorrectly.
(Edit: Side note, the render MUST break and return from the current controller call. I attempted to use ApplicationController.render(...) in place of the current render call, but that did not return from the controller call and I was receiving errors/warnings of :no_content rendered.)
Warning:
DEPRECATION WARNING: Rendering actions with '.' in the name is deprecated: actions/action_success.json (called from update at /<path>/app/controllers/table_base_controller.rb:39)
The code throwing the warning is specifically this call to render within a controller:
render('/actions/action_success.json', locals: {
view: action.lookup_view('default'),
action: action,
area: current_area,
account: current_account
})
I've tried taking off the .json as directed (also tried adding template: <path>, tried file: <path>), however, I receive this error in the test console:
Error:
TableControllerTest#test_Admin_should_update_via_loan_table:
ActionView::MissingTemplate: Missing template actions/action_success with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder]}. Searched in:
* "/<path>/app/views"
app/controllers/table_base_controller.rb:39:in `update'
app/controllers/application_controller.rb:79:in `with_account'
test/controllers/table_controller_test.rb:14:in `block in <class:TableControllerTest>'
The file in question (path: app/views/actions/action_success.json.jbuilder):
# frozen_string_literal: true
json.status 'success'
json.status_code 200
json.messages action.messages
if view
json.result do
json.partial! view.to_s, result: action.result, locals: { area: area }
end
else
json.result action.result
end

Deprecating partial names that include a . was done to prevent ambiguity in parsing the partial name. We should explicitly state formats instead of including them in the partial name we pass to render.
Without the format in the string you pass in, you need to ensure the formats the render is expecting includes the format you're using, in this case json, which is not one of the default formats.
You can send it in as an option (and make the partial name an option as well) like this:
render(
partial: '/actions/action_success',
formats: [:json],
locals: {
view: action.lookup_view('default'),
action: action,
area: current_area,
account: current_account
}
)

Related

Why does a Missing Partial error occur occasionally when the file exists?

While running Cucumber tests, from time to time it will fail intermittently. The error is:
*** ActionView::Template::Error Exception: Missing partial items/_fields with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml]}. Searched in:
* "/my/path/to/application/app/views"
* "/my/path/to/rvm/gems/ruby-2.1.5/gems/kaminari-0.16.3/app/views"
* "/my/path/to/rvm/gems/ruby-2.1.5/gems/devise-3.5.4/app/views"
Yes, the partial file does exist in /my/path/to/application/app/views/items/_fields.html.haml
The kicker is that sometimes the test will pass, other times it will fail. I am running on a Red Hat 5 machine.
So to debug this, I decided to throw in a begin/rescue.
.fields
- begin
= render partial: 'items/fields', locals: {f: f}
- rescue Exception => e
= byebug
- if f.can_modify?(:object_a)
= render partial: 'layouts/object_a_field', locals: {f: f, field: :object_a}
- else
.field#object_a
= render partial: 'layouts/attribute', locals: {label: 'Object A', value: f.object.object_a_id ? f.object.object_a_number : 'Not Assigned'}
.field#name
- if f.can_modify?(:name)
= f.label :name
= f.text_field :name, {class: 'inputbox large_field', readonly: f.cannot_modify?(:name)}
.smltxt (short description of the item)
- else
= render partial: 'layouts/attribute', object: f.object, locals: {field: :name}
For those curious what's in the partial items/fields:
.field
- if f.can_modify?(:name)
= f.label :name, 'Item'
= f.text_field :name, {class: 'inputbox uppercase', maxlength: 16, readonly: !f.object.identifier.new_record?}
.smltxt (character identifier)
- else
= render partial: 'layouts/attribute', object: f.object, locals: {field: :name, label: 'Item'}
When it passes, I obviously don't hit the byebug, but when it fails, I do! Which allows me to play with it in a Rails Console type environment.
So I decided to run that specific command:
(byebug) render partial: 'items/fields', locals: {f: f}
And I get:
*** ActionView::Template::Error Exception: Missing partial items/_fields with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml]}. Searched in:
* "/my/path/to/application/app/views"
* "/my/path/to/rvm/gems/ruby-2.1.5/gems/kaminari-0.16.3/app/views"
* "/my/path/to/rvm/gems/ruby-2.1.5/gems/devise-3.5.4/app/views"
So I thought I'd remove the locals to see if anything changed:
(byebug) render partial: 'items/fields'
And I get:
*** ActionView::Template::Error Exception: undefined local variable or method `f' for #<#<Class:0x0000002a907610>:0x00000027beae48>
So obviously it's now magically finding the partial, and it knows that it is missing a local variable f.
Update 1
I added the rest of the view that is calling the partial above for clarity.
In an attempt to troubleshoot, I also took the contents of items/_fields.html.haml and placed it where I had the render partial: 'items/fields', locals: {f: f}... then the test passed, which means that the other partials in the file didn't have an issue either. So it doesn't seem like it could be related to the content of the file.
Update 2
It seems by adding the #javascript tag to our very first Cucumber feature often fixes this issue. Somehow having the browser loaded before the other tests, improves the chances of it passing. Makes no sense to me, maybe someone else has an idea about this?
Update 3
It has been discovered today that the error is not related to Cucumber at all. The same error was found in production. The web interface showed a Missing Partial error, but for a file that did indeed exist. After restarting the Passenger server with touch tmp/restart.txt and then the error went away. So it's still very intermittent.
Update 4
I added the content of the partial that seems to be missing randomly.
How would I go about discovering why Rails cannot find a partial that is actually there?
This error usually happens when the format of the request is different and there is no template that can respond to it example format.js, format.html etc. The request will only work if the template items/_fields.html.haml is a it will only be correct if the request format is html
I think you can rename your file to items/_fields.haml if you want to use to respond it any request format.
I had a similar issue but with another gem for handlebars. It is usually because of caching.
Try in your cucumber setup setting
ActionView::Base.cache_template_loading = false
If the above works you DO have a caching issue that will occur on production.
There is an open issue for caching not being enabled in dev mode here: https://github.com/indirect/haml-rails/search?utf8=%E2%9C%93&q=cache_template_loading
I strongly believe upgrading to Ruby 2.3.1 solved this for us. I am not certain what may have changed. When we were investigating this, my colleague and I thought we found a line in the Rails source code that was rendering partials differently based on the Ruby version.
I'm having trouble finding that now, but I still wanted to post that the problems seems to have vanished ever since we upgraded to a newer Ruby.
I think you could try specifying the format and handler of the partial you're rendering:
= render(
partial: 'items/fields',
formats: [:html],
handlers: [:haml],
locals: {f: f}
)
The most commonly used content types in Rails are :js, :json, and :html. If you are using Rails UJS, you can specify the content type in the link or form elements. An example would look like this:
<%= form_for #task, remote: true, data: {'type' => :json} %>
...
In our controller, we will respond accordingly:
def create
respond_to do |format|
format.json { render json: { status: 200, html: render_to_string(partial: 'form') }.to_json }
end
end
Now if your form partial looks like this:
tasks/_form.html.erb
Rails will raise an ActionView::Template::Error Exception: Missing partial. You specified the content type to be json, so it was searching for:
tasks/_form.json.erb
Now if you changed the form to:
<%= form_for #task, remote: true, data: {'type' => :html} %>
...
And controller like this:
def create
respond_to do |format|
format.html { render text: { status: 200, html: render_to_string(partial: 'form') } }
end
end
You can even render json in html content type response:
def create
respond_to do |format|
format.html { render json: { status: 200, html: render_to_string(partial: 'form') }.to_json }
end
end
And then in javascript:
JSON.parse(data);
The above scenerio would work fine because it will be searching for an html file extension in the call to render. That's the important part. The call to render is what raises the error. When you use render, you must make sure you have a template or partial that matches the content type.
One solution mentioned above is to remove the extension. But unless you are responding to multiple formats, it seems a bit overkill. My recommended solution is to look at what the error message is telling you it expects. In your case it was expecting html:
:formats=>[:html]
Try accessing the file from your terminal to see whether it's available or not.
If you can't find it from the terminal, go to your app folder in your text editor and delete it manually.
After that, use the terminal to create it again.
It appears the file is not readable even though it is visible in your text editor, but not visible when accessing it from the terminal.
For some weird reason, this works.
Note: Same applies to folders

rails 3.2 with jbuilder swallowing errors, instead rendering missing partial

I'm using Rails 3.2.21 with JBuilder.
I have an example where I'm using an a JBuilder partial inside of a js.erb file to pre populate some fields:
var orderData = <%= raw render :partial => 'orders/orders', formats: [:json], handlers: [:jbuilder], locals: {orders: #orders} %>;
I have a weird problem where if an error is thrown in the jbuilder template, it renders a missing template error. so
If _orders.json.jbuilder looks like this
json.array! orders do |order|
json.someProperty order.a_missing_property
end
I get this:
ActionView::Template::Error (Missing partial orders/orders with {:locale=>[:en], :formats=>[:js, :html], :handlers=>[:erb, :builder, :coffee, :haml, :jbuilder, :riif]}
But if there is no error, this renders properly.
Any idea how my error is getting swallowed?
Update
I've created a demo app here: https://github.com/earnold/error-demo
If you load home/index you get a missing template error. If you comment out the bad line in the template, you render the template normally. What I am trying to do is make sure that errors aren't swallowed, but instead are shown on the page.
I was able to get an answer on Github here: https://github.com/rails/jbuilder/issues/40
Short version: this can be remedied by monkey patching Rails. Put this in an initializer and you're good to go.
if Rails.env.development? || Rails.env.staging?
module ActionView
class Template
protected
def handle_render_error(view, e) #:nodoc:
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
else
assigns = view.respond_to?(:assigns) ? view.assigns : {}
template = self
unless template.source
# If an error occurs while the Jbuilder template is being rendered in
# in a nested context, you have a mismatch between the template format
# and the view context. Therefore, this block of code would raise
# a false exception (ActionView::MissingTemplate) and swallow the original
# error. This monkey patch tricks rails into thinking it was a json request
# so that refreshing the source hits the right partial
if template.formats == [:json]
view.lookup_context.formats = [:json]
end
template = refresh(view)
template.encode!
end
raise Template::Error.new(template, assigns, e)
end
end
end
end
end

Why I am getting missing template from a subdirectory?

I have the following in my views/patients/show.html.slim
== render 'era/header'
Of course, views/patients/era/_header.html.slim exists, though it throws a missing template error:
ActionView::MissingTemplate at /patients/12345
Missing partial era/header with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :slim, :haml]}.
Searched in: * "/home/pablo/code/rails/tarjira/app/views"
If I use == render 'patients/era/header' works, same with == render 'era_header' (assuming I have a views/patients/_era_header.html.slim file). The latter makes me think that rails search the actual directory (views/patients), so I don't understand why in the first case I have to prefix with patients/.
I'm using Rails 4.0.4.
To render a partial as part of a view, you use the render method within the view:
== render 'era_header'
This will render a file named _era_header.html.slim at that point within the view being rendered.
== render 'era/header'
This code will pull in the partial from app/views/era/_header.html.slim. Notice how the Rails is forming the path i.e, by prefixing app/views before the given path in render method call i.e., era/header. This is how render method is implemented in Rails.
Read the Rails Guide explanation for Naming Partials
The desire for partial rendering with relative paths seems to have a long history. There's an issue from 2011 and a pull request from 2015.
For now if you just need 1 extra level as described in your question you can place a callback in your application_controller.rb:
class ApplicationController < ActionController::Base
before_action :_append_view_path
def _append_view_path
append_view_path("app/views/#{controller_path}")
end
end
This way your views will gain the ability to use render('subfolder/partial') instead of render('controller/subfolder/partial').

Template is missing error when rendering html in Rails controller action

I'm attempting to use render html: to render raw html from a controller action:
class SomeController < ApplicationController
def raw_html
render html: '<html><body>Some body text</body></html>'
end
end
However, when I run this controller action, I get a "Template is missing" error
I don't want to use a template, just render raw html.
The error I get is:
Processing by SomeController#raw_html as HTML
Parameters: {}
ActionView::MissingTemplate (Missing template some_controller/raw_html
with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder,
:raw, :ruby]}. Searched in: *
"/Users/doved/source/sample_app/app/views" *
"/Users/doved/.rvm/gems/ruby-2.0.0-p353#syp/gems/chameleon-0.2.4/app/views"
* "/Users/doved/.rvm/gems/ruby-2.0.0-p353#syp/gems/kaminari-0.15.1/app/views"):
app/controllers/some_controller.rb:14:in raw_html'
lib/middleware/cors_middleware.rb:8:incall'
I'm using Rails 4.0.2
What am I doing wrong?
html option was added to render method in Rails 4.1 version.
Checkout the discussion on this topic on Github
If you upgrade the Rails version to Rails 4.1 then you would be able to render html as
def raw_html
render html: '<html><body>Some body text</body></html>'.html_safe ## Add html_safe
end
With the current version of Rails 4.0.2, you would need to use
def raw_html
render text: '<html><body>Some body text</body></html>'
end
You are getting error as: ActionView::MissingTemplate
Because currently html option is not supported by render so the value passed with html option is ignored and Rails starts to look for a template some_controller/raw_html in views directory.
Possible duplicate of
How to return HTML directly from a Rails controller?
This should work for you:
render text: '<html><body>Some body text</body></html>'

Is it possible to convert a string into a partial?

I have a string that stored in my database that is used as a custom layout.
I would like to parse their custom layout inside of my app layout, by using :
render_to_string(partial: custom_template, layout: "pdf_template", locals: locals)
Where custom_template is the string from the DB. However, when I try to do this I get :
NoMethodError: undefined method `to_sym' for nil:NilClass
Is it possible to do what I'm doing? If so, what can I do to complete this?
I've noted that I can try things like this :
render_to_string(text: template, locals: locals, template: "pdf_template")
And
render_to_string(inline: template, locals: locals, template: "pdf_template")
But doing so, it suddenly can't find the template and returns :
ActionView::MissingTemplate: Missing template layouts/pdf_template with {:handlers=>[:erb, :builder, :coffee, :haml], :formats=>[:pdf], :locale=>[:en, :en]}. Searched in:
* "/Users/elephanttrip/Sites/shasta/app/views"
Which is strange because it worked fine in its current location and definition.
If you want to store your views in the database, you should use another rendering engine rather than in Rails by default. Check liquid gem (http://railscasts.com/episodes/118-liquid, http://rubygems.org/gems/liquid).
For example, we use this gem to render emails for our maillists (their templates are stored in the DB).

Resources