contentful-management.rb icon indicating copy to clipboard operation
contentful-management.rb copied to clipboard

Getting save failures for lack of an X-Contentful-Content-Type header

Open joelip opened this issue 7 years ago • 7 comments

Hi there,

I've been trying to duplicate Contentful entries in an application I'm writing and trying to save an object instantiated with the new operator is yielding this error:

#<Contentful::Management::BadRequest: HTTP status code: 400 Bad Request
Message: You should provide a content type in X-Contentful-Content-Type request header.
Request ID: 2cb81ec8f3daee7d4587a8bb61688a4b>

Here is how I'm initializing the client and instantiating the new entry object:

client = Contentful::Management::Client.new(
   '<access-token-here>',
   http_proxy, # This comes from a private method. Can elaborate further if necessary.
)
source_entry = management_client.entries.find(entry_id)
source_content_type = management_client.content_types.find(entry_content_type_id)
dup_entry = source_content_type.entries.new
# Here is where I have some application-specific code for calling the setters with the values
# from `source_entry.fields_for_query`
dup_entry.save # returns the bad request object

I was modeling my code after the examples provided in the README. Any insight into what might be happening here?

joelip avatar Sep 28 '18 21:09 joelip

Hey @joelip,

You should probably try doing:

dup_entry = source_content_type.entries.create(
  # ... your stuff here ...
)

I'd often advise against using new as it may lead to inconsistent states some times.

Cheers

dlitvakb avatar Oct 01 '18 08:10 dlitvakb

@dlitvakb Can you elaborate on the "inconsistent states" more? I'm trying to reduce the number of API calls I need to make via the management API because it's so heavily rate-limited, so adding an extra create call is not something that's ideal for our use-case. Is there any way to use the new method properly that isn't in your documentation already?

joelip avatar Oct 01 '18 17:10 joelip

Hey @joelip,

It highly depends on what you do in the part of the snippet you omitted.

The main difference between using ::create and ::new, is that with ::create you're directly making the API call with all the values for the fields set from the beginning and returns the fully hydrated entry. When using ::new instead, you get an unhydrated entry, in which you start appending values to, this can lead to parts of the entry being incomplete at the time of finally making the request when you call #save.

The amount of API calls (if you're not creating entries with multiple locales), should be in either case 1 for each entry, no matter which method you choose to use. If you're using multiple locales, using the ::new method may reduce it to 1 API call, while when using ::create you need a minimum of 2 calls, 1 for the default locale, 1 for all the other locales.

Hope this explains it a little bit better,

Cheers

dlitvakb avatar Oct 02 '18 08:10 dlitvakb

It seems like based on the examples in your documentation, that the minimum number of API calls to duplicate an entry would be 4:

  • 1 to retrieve the source entry
  • 1 to retrieve the content type of the entry that you'll be creating
  • 1 to create the entry
  • 1 to update the entry with the fields from the original (since I can't add them until the entry has been created, or do it before the API call is made with ::new)

Am I misunderstanding what's required to create a new entry based on the existing attributes of a source entry?

joelip avatar Oct 03 '18 21:10 joelip

Hey @joelip,

I don't understand the 4th point there, from what I understand from your use-case you can use 3 (or 2, if you wrap the Content Type ID in an object that responds to #id).

  • Retrieve the source entry
  • Retrieve the destination content type (or use a wrapper for the content type id if you already have it)
class ContentTypeIdWrapper
  attr_reader :id

  def initialize(content_type_id)
    @id = content_type_id
  end
end
  • Create the destination entry
new_entry = client.entries(space_id, environment_id).create(
  content_type: ContentTypeIdWrapper.new(content_type_id),
  # ... all the fields from the old entry you want to duplicate here ...
)

If you're using multiple locales, after the initial creation, you should populate the entry with the additional locales and re-save.

Hope this explains it better,

Cheers

dlitvakb avatar Oct 18 '18 12:10 dlitvakb

Interesting, so as long as the object passed to content_type responds to id it works just fine?

joelip avatar Oct 18 '18 18:10 joelip

After trying this myself, it looks like this:

new_entry = client.entries(space_id, environment_id).create(
  content_type: ContentTypeIdWrapper.new(content_type_id),
  # ... all the fields from the old entry you want to duplicate here ...
)

Doesn't work unfortunately. It fails in this method, which is called at create time. I am assuming that the client was created using Contentful::Management::Client.new('<management-api-token>') so if that assumption is wrong, then let me know and I can try something else.

joelip avatar Oct 19 '18 00:10 joelip