Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formatting Active Record Timestamps for JSON API

I have a failing test for a GET request — the issue lies with the way ActiveRecord's timestamps are formatted in the response. The RSpec test expects the data format to be Mon, 29 Dec 2014 13:37:09 UTC +00:00,, but it instead receives 2014-12-29T13:37:09Z. Is there any way to change the formatting of the ActiveRecord timestamp, or what the RSpec test expects?

Edit:

Here's the failing test:

describe 'GET /v1/tasks/:id' do 
  it 'returns an task by :id' do
    task = create(:task)
    get "/v1/tasks/#{task.id}"

    expect(response_json).to eq( 
      {
        'id' => task.id,
        'title' => task.title,
        'note' => task.note,
        'due_date' => task.due_date,
        'created_at' => task.created_at,
        'updated_at' => task.updated_at
      } 
    )
  end
end

The test fails on created_at and updated_at. response_json is a helper method that parses the JSON response.

Here's where the test fails:

   expected: {"id"=>1, "title"=>"MyString", "note"=>"MyText", "due_date"=>Mon, 29 Dec 2014, "created_at"=>Mon, 29 Dec 2014 13:37:09 UTC +00:00, "updated_at"=>Mon, 29 Dec 2014 13:37:09 UTC +00:00}
        got: {"id"=>1, "title"=>"MyString", "note"=>"MyText", "due_date"=>"2014-12-29", "created_at"=>"2014-12-29T13:37:09Z", "updated_at"=>"2014-12-29T13:37:09Z"}
like image 655
David Barsky Avatar asked Dec 05 '25 13:12

David Barsky


2 Answers

The solution was embarrassing simple — I had specify that the dates should be formatted using ISO 8601 using the to_formatted_s() method. Below is the correct, passing version:

require 'spec_helper'

describe 'GET /v1/tasks/:id' do 
  it 'returns an task by :id' do
    task = create(:task)
    get "/v1/tasks/#{task.id}"

    expect(response_json).to eq( 
      {
        'id' => task.id,
        'title' => task.title,
        'note' => task.note,
        'due_date' => task.due_date.to_formatted_s(:iso8601),
        'created_at' => task.created_at.to_formatted_s(:iso8601),
        'updated_at' => task.updated_at.to_formatted_s(:iso8601)
      } 
    )
  end
end
like image 107
David Barsky Avatar answered Dec 07 '25 04:12

David Barsky


The problem is that the date columns are being 'jsonified' in a format that you may or may not have specified. This value is of a different format and a string; therefore matching it with a date value fails.

To resolve this you could either adjust how the created_at value is 'jsonified' (format it) or you could just do this in your test:

columns = %w{id title note due_date created_at updated_at}

# get task as json string:
task_json_string = task.to_json(only: columns)

# turn json into hash
task_json = ActiveSupport::JSON.decode(task_json_string) 

json_attributes = task_json.values.first

expected_hash = columns.each_with_object({}) do |attr, hash|
  # ensuring that the order of keys is as specified by 'columns'
  hash[attr] = json_attributes[attr]
end

expect(response_json).to eq expected_hash

That should work (minor debugging might be required though).

To go the former route, add an instance method like the following to your Task model:

def as_json( *args )
  super(*args).tap do |hash|
    attributes_hash = hash.values.first

    %w{created_at updated_at}.each do |attr|
      if attributes_hash.has_key?(attr)
        # Use strftime time instead of to_s to specify a format:
        attributes_hash[attr] = self.send(attr).to_s
      end
    end
  end
end

Then in your test your task json could simply be:

task_json = task.as_json.values.first

Or after adding the add_json method and keeping the to_s there, in your test all you will need to do is add a .to_s to your date method e.g. task.created_at.to_s; the values will match.

Thereby removing the object-to-json-to-hash step and doing object-to-hash directly.

like image 26
SHS Avatar answered Dec 07 '25 04:12

SHS



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!