I recently upgrade from grails 2.2 to 2.3.1. My controller used to bind data to an command object at controller parameters. After I upgraded to 2.3.1, the binding seems not working and have '[Fatal Error] :-1:-1: Premature end of file.' output to console.
it works fine at 2.2 like this
def home(ACommand cmd) {}
after upgrading, it outputs 'Premature end of file' before it goes to the action and skip the action method and going to home.gsp view directly.
I also tried new an instance inside the action and use bindData(cmd, params). When I step through the action, bindData method produced the same message but can continue and generate view models and pass to home.gsp.
Does anyone happen to know what might cause the problem? Thanks.
Grails 2.3 includes a new data binding mechanism which has additional features. If you need to access the legacy spring data binding mechanism use this configuration in Config.groovy
grails.databinding.useSpringBinder=true
Eventually if you feel the need to use the latest data binder then a transition to use the new features would be needed.
Thanks dmahapatro. I tried it before but does not work.
FYI, I fixed it by rearranging content negotiations at Config.groovy.
Changing from
mime.types = [ xml: ['text/xml', 'application/xml'],
text: 'text/plain',
js: 'text/javascript',
rss: 'application/rss+xml',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
all: '*/*',
json: 'text/json',
html: ['text/html','application/xhtml+xml']
]
to
mime.types = [
all: '*/*',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
form: 'application/x-www-form-urlencoded',
html: ['text/html','application/xhtml+xml'],
js: 'text/javascript',
json: ['application/json', 'text/json'],
multipartForm: 'multipart/form-data',
rss: 'application/rss+xml',
text: 'text/plain',
hal: ['application/hal+json','application/hal+xml'],
xml: ['text/xml', 'application/xml']
]
Solves the problem.
Not sure why the order matters, but I think it is caused by Grails 2.3 databinding intend to parse request body and bind to my command object and lead to an xml parser error.
Related
I'm attempting to upgrade to Turbo from Turbolinks and I've found that the client is not rendering redirects for form submissions.
Versions:
rails 6.1.4
hotwire-rails 0.1.2
#hotwired/turbo-rails 7.0.0-beta.8
I've ignored the incompatibility between Turbo and Devise for now - just trying to get regular forms working without having to disable Turbo on them.
Here's an example action:
def update
authorize #label
#label.update(label_params)
if #label.save
redirect_to document_labels_path(document_id: #document.id)
else
render :new, status: :unprocessable_entity
end
end
Here's a rendered form:
<form class="simple_form new_label" id="label_form" novalidate="novalidate" action="/documents/72/labels" accept-charset="UTF-8" method="post">
...
</form>
When submitting a valid form, the server will say Processing by LabelsController#create as TURBO_STREAM and correctly serve a 302. It will then serve the 200 for the redirect location. The browser however is left just looking at the submitted form. Changing the redirect status to 303 doesn't change anything.
I added a console.log for every Turbo event:
document.addEventListener("turbo:load", function () {
console.log('TURBO:LOAD')
})
document.addEventListener("turbo:click", function () {
console.log('TURBO:CLICK')
})
document.addEventListener("turbo:before-visit", function () {
console.log('TURBO:BEFORE-VISIT')
})
document.addEventListener("turbo:visit", function () {
console.log('TURBO:VISIT')
})
document.addEventListener("turbo:submit-start", function () {
console.log('TURBO:SUBMIT-START')
})
document.addEventListener("turbo:before-fetch-request", function () {
console.log('TURBO:BEFORE-FETCH-REQUEST')
})
document.addEventListener("turbo:before-fetch-response", function () {
console.log('TURBO:BEFORE-FETCH-RESPONSE')
})
document.addEventListener("turbo:submit-end", function (event) {
console.log('TURBO:SUBMIT-END')
// event.detail
})
document.addEventListener("turbo:before-cache", function () {
console.log('TURBO:BEFORE-CACHE')
})
document.addEventListener("turbo:before-stream-render", function () {
console.log('TURBO:BEFORE-STREAM-RENDER')
})
document.addEventListener("turbo:render", function () {
console.log('TURBO:RENDER')
})
This is what the output is for a successful form submission:
TURBO:BEFORE-FETCH-REQUEST
TURBO:SUBMIT-START
TURBO:BEFORE-FETCH-RESPONSE
TURBO:SUBMIT-END
There is no render event. Investigating event.detail.fetchResponse.response for turbo:submit-end it seems to be perfectly aware that the client should redirect, it just didn't.
Response {type: "basic", url: "http://lvh.me:3000/documents/72/labels", redirected: true, status: 200, ok: true, …}
body: (...)
bodyUsed: true
headers: Headers {}
ok: true
redirected: true
status: 200
statusText: "OK"
type: "basic"
url: "http://lvh.me:3000/documents/72/labels"
__proto__: Response
Update: It is actually performing the redirect and the server is generating the response. The issue is that the client is not rendering the redirect response.
What is happening here is that your application is specifying that it prefers turbo-stream responses over text/html responses. If you were to look at your request headers for the redirect page, you'll likely see the following:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
As a result, Rails returns the data with the first type it recognizes, which is text/vnd.turbo-stream.html. Turbo in your browser sees this and, since it's not interpretable as a Turbo Stream, unhelpfully ignores it quietly.
The solution (workaround?) is to make sure you are redirecting to the html version of your page:
redirect_to document_labels_path(document_id: #document.id, format: :html)
This will return the page with a Content-Type of text/html, and Turbo will replace the whole page with the contents.
Jeff's answer is correct but I wanted to share the specific fix for the issue I was having.
If you use HAML or Slim, I've seen it on more than one codebase where developers rename all template files .haml instead of .html.haml (same for Slim). It's never bitten me before using Turbo, but without .html in the filename, part of Rails won't know what format to serve a response in, so it defaults to the request format.
Turbo makes a turbostream request when submitting a form, but if the response is a redirect, it expects it to be text/html in order to render it. If it receives a turbostream response to a redirect request, Turbo just sits there doing nothing with no console errors or warnings (terrible default behavior IMO).
So if your templates do not include .html, just add it back and Turbo will render redirects. You may still need status: :see_other.
More information:
https://github.com/hotwired/turbo-rails/issues/122
https://github.com/hotwired/turbo-rails/issues/287
Adding to the excellent answer of Jeff Seifert.
If you don't need turbo streams, you may also unregister the turbo-stream content type altogether by putting this into an initializer e.g. config/initializers/turbo.rb:
Rails.application.config.after_initialize do
Mime::Type.unregister(:turbo_stream)
end
What's the best way to access the Rails.env in javascript?
Currently I am using ReactJS with existing Rails project, using webpacker gem. (Not using react-rails gem)
What I tried?
Set a javascript variable from the rails application (view template where my root component present) and access it in the root component JS. But this approach looks verbose to me as I needed to pass that variable all over the JS files in the react app.
Is there any better way of doing this?
You can use gon gem. Gon allow you to push rails variables to global variables in js.
In controller:
class SampleController
def index
gon.variable_name = variable_value
....
end
and then you can access variable in js this way
console.log(gon.variable_name)
Another (in my opinion better) solution is query for data you need to another rails action. In your react component you can use componentDidMount for this
componentDidMount(){
fetch('http://url_to_fetch_data_from_server', {
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'X-CSRF-Token': 'token get from metatag',
},
credentials: 'same-origin',
}).then(response => {
response.json().then(json =>{
this.setState(yourData: json.yourData)
})
})
}
Then you can send data to all child component via props
I just noticed that the respond method in controllers is returning HTML responses in the ISO-8859-1 charset (which garbles my unicode characters). It uses UTF-8 if I set the format to JSON. The render method also uses UTF-8.
I'm using Grails 2.4.4 and the Tomcat plugin v. 7.0.55 in development mode without overriding web.xml. Both grails.converters.encoding and grails.views.gsp.encoding are set to UTF-8. I have <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> in my template if that influences anything.
I've also tried forcing the charset by using respond myInstance, [encoding: "UTF-8"] but it didn't change anything.
Is there something I am not seeing or have I hit a bug?
EDIT: my config.groovy contains the following mime type definitions:
grails.mime.types = [ // the first one is the default format
all: '*/*', // 'all' maps to '*' or the first available format in withFormat
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
form: 'application/x-www-form-urlencoded',
html: ['text/html','application/xhtml+xml'],
js: 'text/javascript',
json: ['application/json', 'text/json'],
multipartForm: 'multipart/form-data',
rss: 'application/rss+xml',
text: 'text/plain',
hal: ['application/hal+json','application/hal+xml'],
xml: ['text/xml', 'application/xml']
]
It would seem this is a Grails bug, I've narrowed down the specific case when it happens: you need to have a static responseFormats = ['html', ...] limitation on the controller to trigger it. The fact that Grails' generate-restful-controller includes the responseFormats block automatically makes developers even more likely to encounter this issue.
I've filed a bug report.
EDIT: to keep the responseFormats block but still have UTF-8 responses, it's possible to set the encoding manually, perhaps like this:
def beforeInterceptor = {
response.characterEncoding = 'UTF-8' //workaround for https://jira.grails.org/browse/GRAILS-11830
}
In my project I have to integrate the library and parse the files presented in csv format. To access the library and get the information form that file I use $ajax as follows:
<script>
$(document).ready(function(){
$.ajax({
type: "GET",
url: "http://stats.xxx.tv/osexternal/reports/xxxxx/xxx_2014_YTD/2014-03-12.csv",
contentType: 'application/json',
dataType: 'json',
username: 'xxxx#xxxx.com',
password: 'dT$xxxx%949',
success: function (){
console.log('success');
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
});
<script>
Can anyone let me know what's the wrong with this approach as I am getting cross domain problem.And please let me know any alternatives by using gems.
Thanks for your help in advance!
What you're running into appears to be a CORs issue of some kind. Things to note about CORs issues:
It is a security policy for Javascript, so it only affects calls in/from JS.
Being able to access it from the browser 'directly' doesn't have anything to do with CORs
CORS can be really irritating
Now, on how to solve it, you can try adding:
with_credentials: true to the Ajax arguments, but I have a feeling it's going to be something weirder than that... as well, since you have to include a username and password it's probably best not to expose those on the client for anyone to have...
So, what I'd do is make the call on the server (example is for a rails controller action, but the method could be used in a Sinatra app just the same) then return the CSV to the browser:
require 'net/http'
class MyController < ActionController::Base
# ...
def get_csv
uri = URI('http://stats.adap.tv/osexternal/reports/xxxxx/xxx_2014_YTD/2014-03-12.csv')
csv_request = Net::HTTP::Get.new(uri)
csv_request.basic_auth("username", "password")
csv_data = csv_request.request.body
csv
end
end
I'm assuming you are using Ruby because of your "gems" reference. Here's the doc for Net::HTTP
http://ruby-doc.org/stdlib-2.1.1/libdoc/net/http/rdoc/Net/HTTP.html
and a slightly easier to digest version:
http://www.rubyinside.com/nethttp-cheat-sheet-2940.html
In general, it'll always be easier (and safer) to have your server make a request to an external host (this is a broad generalization and there are absolutely cases where this isn't what you want). If you need to make a cross domain request I'd suggest starting with:
http://www.html5rocks.com/en/tutorials/cors/
It'll probably give you some good tips to figure out why it's not currently working.
Best,
I have a Rails 4.0 app with an Ember.js frontend. I'm using Ember-Auth in conjunction with Devise to handle authentication. For the most part, everything works. However, if I use Jquery File Upload, then all subsequent queries to the server result in an InvalidAuthenticityToken error. The file upload itself works perfectly, but if for instance I visit the Organizations index page afterwards, I'll get the error. If I reload the page, then the errors stop coming and everything works fine again until I perform another upload.
The uploader looks like this:
didInsertElement: ->
$('#image_upload').fileupload
url: "/images"
formData: [{ name: 'auth_token', value: Whistlr.Auth.get('authToken') }]
success: (response) =>
#get('parentView').get('controller').set('image_token', response.token)
Even if I remove everything but the url, I get the InvalidAuthenticityToken afterwards. Any idea what's happening?
Following my suspicion that the session was getting reset, I tried passing the authenticity_token back the client side from the server. Surely enough, it was changing. So I manually placed the new authenticity_token in the headers. The code looks like this:
def create
image = Image.create(image_params)
render json: {image: image, authenticity_token: form_authenticity_token}, status: 201
end
$('#image_upload').fileupload
url: "/images"
dataType: "json"
formData: [{ name: 'auth_token', value: Whistlr.Auth.get('authToken') }]
success: (response) =>
#get('parentView').get('controller').set('image_token', response.image.token)
$('meta[name="csrf-token"]').attr('content', response.authenticity_token)
Note the last line of the javascript, which replaces the csrf-token. Thankfully, this works. As far as I can tell, it's also secure. (If you see a security flaw, please let me know!) But it still seems weird that this is necessary. Nowhere else in my app do I have to manually replace the csrf-token. Why is this happening here? Is it because the session is getting reset, and if so, why is that happening here but not elsewhere?