1

I've done my googling and searching for a solution to my problem and haven't found a solid solution in part because of how odd my project is.

I'm trying to make a simple Chat-App with Rails 4 backend using only "vanilla" javascript (no jQuery or Rails helpers allowed). I've got a GET Ajax request working which gathers all of the seeded message data and shows it in the chat. However, when I try to write a POST request to my Rails backend I'm running into two main problems. They may or may not be related to each other.

The first problem, which I've temporarily overcome, is a 422 status error (Unprocessable Entity). I believe this is because my xmlhttp.setRequestHeadervalues are wrong?
However, I don't know what else they should be. I've temporarily by-passed this problem by putting a skip_before_filter :verify_authenticity_token in my Rails MessagesController.
Is there a more elegant way of solving this?

This leads me to my next problem which is a 400 status error (Bad Request). When I look at my error on the Terminal while running my server the problems looks like...

Started POST "/messages" for 127.0.0.1 at 2014-11-26 17:32:48 -0800
Processing by MessagesController#create as */ *
Parameters: {"user"=>"namechatsample", "content"=>"messsagechatsample"}
Completed 400 Bad Request in 4ms

ActionController::ParameterMissing (param is missing or the value is empty: message): app/controllers/messages_controller.rb:17:in message_params' app/controllers/messages_controller.rb:9:increate'

Any guidance and help would be appreciated!

Here is my Code.

Ajax Calls

var getRequest = function(callback) {
  var xmlhttp;
  if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
  } else {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
      if (xmlhttp.status == 200) {
        data = JSON.parse(xmlhttp.responseText);
        callback(data.messages)
      } else if (xmlhttp.status == 400) {
        alert('There was an error 400 for GET')
      } else {
        alert('something else other than 200 was returned for GET! This status is ' + xmlhttp.status)
      }
    }
  }
  xmlhttp.open("GET", "/messages", true);
  xmlhttp.send();
}


var postRequest = function(user, message) {
  var xmlhttp;
  if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
  } else {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
      if (xmlhttp.status == 200) {
        data = JSON.parse(xmlhttp.responseText);
        alert('Post ajax call was a success')
      } else if (xmlhttp.status == 400) {
        alert('There was an error 400 for POST')
      } else {
        alert('something else other than 200 was returned for POST! The status is ' + xmlhttp.status)
      }
    }
  }
  xmlhttp.open("POST", "/messages", true);
  xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xmlhttp.send("user=" + user + "&content=" + message + "");
}

postRequest("namechatsample", "messsagechatsample")

Rails 4 Message Controller

class MessagesController < ApplicationController
 skip_before_filter  :verify_authenticity_token

 def index #returns last 10 messages
   render :json => Message.last(10)
 end

 def create #makes a new message
   msg = Message.new(message_params)
   msg.save
   render :json => msg
 end


 private
  def message_params
    params.require(:message).permit(:user, :content)
  end
end

If you think the problems in some other file, or code location, please let me know!

Lalit Kumar B
  • 47,486
  • 13
  • 97
  • 124
J-fish
  • 218
  • 3
  • 13

3 Answers3

3

422 Status Error

The 422 error is because by default Rails adds Cross Site Request Forgery (CSRF) prevention measures through an authenticity token. If the token is invalid (or not present) at 422 status error is returned on POST/PUT/DESTROY requests. To get around this you can disable the authenticity check (like you have done) or you can send the required token in the ajax request as described in this answer.

400 Status Error

This is because the strong parameters you have defined in your controller require require a message parameter to be part of the params sent (params.require(:message)...) and it allows a 'content' value as a nested param. E.g the required structure needs to be:

:message => {:content => 'message data'}

In the javascript you have sent the parameter name for the message as just 'content', the parameter name needs to be "message[content]". E.g:

xmlhttp.send("message[user]=" + user + "&message[content]=" + message + "");
Community
  • 1
  • 1
Pete
  • 2,196
  • 1
  • 17
  • 25
  • Thanks for the reply Pete, I've figured out how to grab the token by using `document.querySelectorAll('meta[name=csrf-token]')[0].content` and your explanation of the nested params was big "oh ya!" moment for me. Thanks. – J-fish Nov 27 '14 at 02:38
1

You have:

 def message_params
     params.require(:message).permit(:user, :content)
 end

This is requiring strong parameters to be passed to the controller in the request. According to this statement, the json will have to look like

 {"message" : {"user" : "someuserthing", "content" : "someusercontent"}}

It doesn't look like you are far off, but making sure that your app is passing that JSON in that format is the first step.

BTW, it is very helpful to be looking in your console to see what the actual passed json is. In your case, you say:

 Parameters: {"user"=>"namechatsample", "content"=>"messsagechatsample"}

As you can see, that doesn't have the root element of "message".

Good Luck.

jjk
  • 536
  • 7
  • 15
0

This leads me to my next problem which is a 400 status error (Bad Request)

You require(:message), but send only user and content.

Is there a more elegant way of solving this?

Yes, if you don't want to deal with CSRF at all, you can put this in application.rb:

self.allow_forgery_protection = false

Or, you could implement proper CSRF handling. :)

However, let me add an unsolicited bit: Rails is not a very good fit for a chat app. Ideally you'd want something comet-ish, like socket.io.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • hey Amadan, I'm sorry that I'm such a noob... but can you explain more when you say that I require(:message), but send only user and content ? Should my xmlhttp.send() include a message value? – J-fish Nov 27 '14 at 02:12
  • Look at your `message_params` in your controller. You wrote that, not me :) You probably want `params.require(:user, :content)` and not mention `:message` at all, since your data structure does not fit what you have there at the moment. – Amadan Nov 27 '14 at 02:13