POST-запросы Ember.js возвращают 400 с сервера (Grape API), но успешно сохраняются в локальном хранилище

Я пытался заставить простое приложение Ember.js публиковать в Grape API в течение нескольких часов, но, похоже, я не могу заставить его работать. Я знаю, что API работает, потому что я могу публиковать в нем новые записи через документацию Swagger, и они сохраняются. Я знаю, что API и Ember работают очень хорошо, потому что я могу получить все записи с сервера и взаимодействовать с ними на странице, и я знаю, что Ember отлично работает в вакууме, потому что мои записи сохраняются в локальном хранилище.

Тем не менее, я просто не могу заставить запрос POST работать. Он всегда возвращает 400. У меня Rack-Cors настроен правильно, и у меня все настроено с ActiveModelAdapter на внешнем интерфейсе и ActiveModelSerializer на заднем конце.

Вот модель

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')
}

и контроллер

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

Соответствующая часть API выглядит следующим образом

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

Форма, которую я использую...

<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>

И ответ я получаю...

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

Любой берущий? Извините, я новичок в этом. Что-то не связывает, но я не знаю почему.


ОБНОВИТЬ

Больше расследований...

ИЗОБРАЖЕНИЕ: POST-запросы к API cURL (через Postman) работают просто отлично.,

Тем не менее, когда я POST из Ember, ответ сервера по-прежнему

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

ИЗОБРАЖЕНИЕ: вывод запроса POST от Chrome Dev Tools выглядит следующим образом

Я также изменил контроллер на..., что дает вывод в журнале инструментов разработчика Chrome выше.

`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`

2 ответа

Решение

Я абсолютно ничего не знаю о Ember.js, но некоторое время я создавал API с использованием Grape, поэтому думаю, что смогу вам помочь. Смотря на изображение, которое вы вложили, кажется, что Ember.js создает неправильный JSON внутри полезной нагрузки, и ваш Grape API не ожидает, что JSON сформирован таким образом. Как вы можете видеть на втором изображении, он создает JSON следующим образом:

{ contact: {...} }

Однако ваш API ожидает формат JSON, подобный следующему:

{ first_name: "" ... }

Теперь посмотрите, как вы отправляете тот же запрос через Chrome Dev Tools... вы используете опцию "form-data" для создания запроса тела, и именно поэтому в данном конкретном случае он работает. Попробуйте изменить его на "raw" и указать неверный JSON выше, и вы получите ту же ошибку, что и при использовании Ember.Js.

У меня нет конкретного решения, потому что у меня нет опыта, чтобы помочь вам с Ember.Js... но вы должны что-то изменить в своем приложении Amber.js таким образом, чтобы оно создавало запрос JSON, например:

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

Вместо:

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

Надеюсь, это поможет вам!

ОБНОВИТЬ

Другим решением вашей проблемы является изменение вашего Grape API. В этом случае вы должны создать групповой блок внутри вашего блока params, например, так (учтите, что поля контактов теперь хранятся внутри 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

Если вы хотите изменить способ форматирования Ember JSON, вам нужно создать собственный сериализатор и переопределить функцию serializeIntoHash. Вы можете использовать этот метод для настройки корневых ключей, сериализованных в JSON. По умолчанию REST Serializer отправляет typeKey модели, которая является верблюжьей версией имени.

Ember-cli поставляется с генератором для запуска сериализаторов. Вы можете запустить его с:

 ember g serializer Contact

Из коробки сериализатор будет выглядеть так:

import DS from 'ember-data';

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

Чтобы заставить его работать с виноградом, вы можете сделать это:

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];
      }
    }
  }
});

Больше информации в документации.

Другие вопросы по тегам