POST requests from Ember.js return 400 from the server (API Grape), but successfully persist to local storage

I've been trying to get a simple Ember.js app to publish to a Grape API server for hours, but I can't get it to work. I know the API works because I can post new entries to it through the Swagger documentation and they persist. I know the API and Ember speaks just fine because I can get all the records from the server and interact with them on the page, and I know that Ember works great in a vacuum because my records are stored in local storage.

However, I just can't seem to get the POST request to work. It always returns 400. I have my Rack-Cors set up correctly and I have everything set up with the ActiveModelAdapter on the Front-End and the ActiveModelSerializer on the back.

Here is the model

Contact = DS.Model.extend {
  firstName: DS.attr('string'),
  lastName:  DS.attr('string'),
  email:     DS.attr('string'),
  title:     DS.attr('string'),
  createdAt: DS.attr('date'),
  updatedAt: DS.attr('date')
}

      

and controller

ContactsNewController = Ember.ObjectController.extend(
  actions:
    save: ->
      @get('model').save()
    cancel: ->
      true
)

      

The relevant part of the API looks like this

desc 'Post a contact'
  params do
    requires :first_name, type: String, desc: 'First name of contact'
    requires :last_name , type: String, desc: 'Last name of the contact'
    requires :email     , type: String, desc: 'Email of the contact'
    requires :title     , type: String, desc: 'Title of the contact'
  end
  post do
    Contact.create!(
      first_name: params[:first_name],
      last_name:  params[:last_name],
      email:      params[:email],
      title:      params[:title]
    )
  end

      

The form I'm using is ...

<form {{action 'save' on='submit'}}>
  <h2>{{errorMessage}}</h2>

  <label>First Name</label>
  {{input value=firstName placeholder='First Name' type='text' autofocus=true}}

  <label>Last Name</label>
  {{input value=lastName placeholder='Last Name' type='text'}}

  <label>Email</label>
  {{input value=email placeholder='Email' type='text'}}

  <label>Job Title</label>
  {{input value=title placeholder='Job Title' type='text'}}

  <hr>

  <input type='submit' value='Save'>
</form>

      

And the answer I get is ...

{"error":"first_name is missing, last_name is missing, email is missing, title is missing"}

      

Any takers? Sorry, I'm new to this. Something unrelated, but I don't know why.


UPDATE

More research ...

IMAGE: POST requests for cURL API (via Postman) work very well. ...

However, when I post from Ember the server response is still

{"error":"first_name is missing, last_name is missing, email is missing, title is missing"}

      

IMAGE: The result of a POST request from Chrome Dev Tools looks like this:

I also changed the controller to ... which gives me the output in the chrome dev tools log above.

`import Ember from 'ember'`

ContactsNewController = Ember.ObjectController.extend(
  actions:
    createContact: ->
      firstName = @get('firstName')
      lastName  = @get('lastName')
      email     = @get('email')
      title     = @get('title')

    newRecord = @store.createRecord('contact', {
                   firstName: firstName,
                   lastName:  lastName,
                   email:     email,
                   title:     title
                 })

    self = this

    transitionToContact = (contact) ->
      self.transitionToRoute('contacts.show', contact)

    newRecord.save().then(transitionToContact)
   )

`export default ContactsNewController`

      

+3


source to share


2 answers


I don't know anything about Ember.js, but I'm building up the API with Grape, so I think I can help you. Looking at the image you have connected it seems that Ember.js is generating incorrect JSON inside the payload and your Grape API is not expecting JSON to be generated that way. As you can see in the second image, it creates JSON like this:

{ contact: {...} }

      

However, your API expects a JSON format like this:

{ first_name: "" ... }

      

Now watch how you are submitting the same request through the Chrome Dev tools ... you are using the "form-data" option to create the body request and why in this particular case it works. Try changing it to "raw" and putting the wrong JSON above and you will get the same error as you are using Ember.Js.

I don't have a specific solution because I have no experience to help you with Ember.Js ... but you have to change something in your Amber.js app so that it creates a JSON request, like this:

{ first_name: "", last_name: "", email: "" }

      



Instead:

{ contact: { first_name: "" ... } }

      

Hope this helps you!

UPDATE

Another solution to your problem is to change the Grape API. In this case, you need to create a group block inside the params block , for example (note that contact fields are now stored inside the [: contact] Hash parameters):

desc 'Post a contact'
params do
    group :contact, type: Hash do
        requires :first_name, type: String, desc: 'First name of contact'
        requires :last_name , type: String, desc: 'Last name of the contact'
        requires :email     , type: String, desc: 'Email of the contact'
        requires :title     , type: String, desc: 'Title of the contact'
    end
end
post do
    Contact.create!(
        first_name: params[:contact][:first_name],
        last_name:  params[:contact][:last_name],
        email:      params[:contact][:email],
        title:      params[:contact][:title]
    )
end

      

+5


source


If you want to change the way Ember formats JSON, you will need to create your own serializer and override the serializeIntoHash function . You can use this method to customize root keys serialized to JSON. By default, the REST serializer sends a ModelKey type, which is the camel version of the name.

Ember-cli comes with a generator to run serializers. You can start it with:

 ember g serializer Contact

      

Out of the box, the serializer will look like this:



import DS from 'ember-data';

export default DS.RESTSerializer.extend({
});

      

To make it work with grapes, you can do this:

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  serializeIntoHash: function(data, type, record, options) {
    var properties = this.serialize(record, options);
    for(var prop in properties){
      if(properties.hasOwnProperty(prop)){
        data[prop] = properties[prop];
      }
    }
  }
});

      

More information in the documentation .

0


source







All Articles