3

I've been trying to get a simple Ember.js application to post to a Grape API backend for hours now, but I cannot seem to get it to work. I know the API works because I can post new records to it through the Swagger documentation, and they are persisted. I know the API and Ember are talking just fine because I can get all records from the server and interact with them on the page, and I know that Ember is working fine in a vacuum because my records are persisted to local storage.

However, I just cannot seem to get a POST request to work. It always comes back a 400. I have Rack-Cors configured properly, and I have everything set up with an ActiveModelAdapter on the Front-End and an ActiveModelSerializer on the back end.

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 response 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 isn't getting bound, but I don't know why.


UPDATE

More investigations...

IMAGE: POST requests to the API cURL (via Postman) work just fine..

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 output from the POST request from the Chrome Dev Tools looks like this

I have 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`
Bottled Smoke
  • 356
  • 1
  • 6
  • Could you show the request that Ember sends (for example via the chrome console?). Are you able to use curl to make a valid POST request? – Thomas Brus Nov 11 '14 at 19:03
  • 1
    I don't know nothing about Grape but it seems that your fields in Ember model are camelCase but in your backend these are underscored. (firstName vs first_name) – Griffosx Nov 11 '14 at 21:10
  • @ThomasBrus I've added an image of a successful cURL sent from Postman as well as an image of the request failing when sent from ember as viewed from the Chrome Dev Tools output. It has to be an error with the serialization of the ember post, doesn't it? Because from the looks of it, the API is handling it fine on its own. – Bottled Smoke Nov 12 '14 at 09:58
  • @Griffosx I have considered that to be a problem, but in the update section of the post, my output JSON seems to be serialized from camel to snake correctly by the ActiveModelAdapter that I've set up in the ember app. In Rails, I am running an ActiveModelSerializer that is also converting all the JSON from snake to camel case when sent to Ember; however, I do think you are right in that there is something wrong with how my Ember app is sending the data back to the server, as cURL is working (via PostMan). – Bottled Smoke Nov 12 '14 at 10:04
  • I would agree with @Griffosx that the "easy" path would be to keep all attributes as snake_case in both directions. Modify your model to first_name, etc. and then get rid of the active model serializer that changes the data to CamelCase. – GSP Nov 12 '14 at 19:40

2 Answers2

5

I know absolutely nothing about Ember.js but I've been building APIs using Grape for a while so I think I can help you. Looking the image that you've attached, it seems that Ember.js is creating an incorrect JSON inside the Payload and your Grape API isn't expecting a JSON formmated that way. As you can see in the second image, it's creating a JSON like this:

{ contact: {...} }

However, your API is expecting a JSON format like this one:

{ first_name: "" ... }

Now, look how you're sending the same request through the Chrome Dev Tools... you're using the "form-data" option to create the body request and that's why in this specific case it's working. Try changing it to "raw" and putting the incorrect JSON above and you'll get the same error that you're getting using Ember.Js.

I don't have the specific solution because I don't have expertise to assist you with Ember.Js... but you have to change something in your Amber.js application in a way that it creates a JSON request like this:

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

Instead of:

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

Hope it helps you!

UPDATE

Another solution to your problem is to change your Grape API. In this case, you have to create a group block inside your params block, like this (take into account that the contact fields are now stored inside the params[:contact] Hash):

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
Marlon
  • 888
  • 6
  • 12
  • That is absolutely the issue here. Thank you. /// [Failing example](http://imgur.com/sMrjpjy) /// [Passing example](http://imgur.com/JREmVFQ) /// I do have a question, however. I am going to try to both change the JSON output of my Ember app as well as change what JSON the Grape API is accepting. My fear is that if I change the JSON output of my Ember App, it will conflict with the default behavior of the app, whereas I built the API myself; it's more likely that I incorrectly namespaced the JSON it accepts. Could help me out with a short example for how to namespace the API correctly? – Bottled Smoke Nov 12 '14 at 23:55
  • 1
    I changed my answer to show you how to modify the way your API handles the JSON. – Marlon Nov 13 '14 at 02:28
  • 1
    Okay I got it to work only after changing the code to (/n indicates newline) \\ params do /n group :contact, type: Hash do /n ... and so on. Without type: Hash, Grape was thinking I was sending an array, so it kept throwing 400 - 'contact' is invalid, but with type: Hash, it proceeds flawlessly. Thank you for all your help. Let me know when you update your answer to reflect this change, and I'll accept it as the solution. – Bottled Smoke Nov 13 '14 at 03:55
  • 1
    Yes! You're right. I forgot to add the type parameter to the "group" block. I've just edited the answer to reflect this change. Thanks! – Marlon Nov 13 '14 at 12:10
0

If you want to change the way Ember format the JSON you will need to create a custom serializer and override the serializeIntoHash function. You can use this method to customize the root keys serialized into the JSON. By default the REST Serializer sends the typeKey of a model, which is a camelized version of the name.

Ember-cli comes with a generator for starting serializers. You can run 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 grape 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 info in the documentation.

null
  • 3,959
  • 1
  • 21
  • 28