格式化JSON API的活动记录时间戳

时间:2014-12-29 14:46:48

标签: ruby-on-rails ruby json rspec datetime-format

我对GET请求的测试失败 - 问题在于ActiveRecord的时间戳在响应中的格式。 RSpec测试期望数据格式为Mon, 29 Dec 2014 13:37:09 UTC +00:00,,但它接收2014-12-29T13:37:09Z。有没有办法改变ActiveRecord时间戳的格式,或RSpec测试期望的内容?

修改

这是失败的测试:

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

created_atupdated_at上的测试失败。 response_json是一个解析JSON响应的辅助方法。

这是测试失败的地方:

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

4 个答案:

答案 0 :(得分:3)

解决方案很简单 - 我已指定使用to_formatted_s()方法使用ISO 8601格式化日期。以下是正确的传递版本:

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

答案 1 :(得分:2)

问题是日期列是以您可能指定或未指定的格式进行“jsonified”的。该值具有不同的格式和字符串;因此将其与日期值匹配失败。

要解决此问题,您可以调整created_at值'jsonified'的格式(格式化),或者您可以在测试中执行此操作:

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

这应该可行(尽管可能需要进行小调试)。

要转到以前的路线,请将以下类似的实例方法添加到Task模型中:

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

然后在你的测试中你的task json可能只是:

task_json = task.as_json.values.first

或者在添加add_json方法并保留to_s之后,在测试中,您需要做的只是在日期方法中添加.to_s,例如task.created_at.to_s;值将匹配。

从而删除object-to-json-to-hash步骤并直接执行object-to-hash。

答案 2 :(得分:2)

以下日期和日期时间格式应该可以解决问题。

{
  'id' => task.id,
  'title' => task.title,
  'note' => task.note,
  'due_date' => task.due_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
  'created_at' =>task.created_at.strftime("%Y-%m-%d %H:%M:%S %Z"),
  'updated_at' =>task.updated_at.strftime("%Y-%m-%d %H:%M:%S %Z")
} 

修改 更正日期格式。

答案 3 :(得分:0)

正如@Humza所提到的那样,问题源于价值如何被“jsonified”。

tl; dr - 只需将您的规范更改为'created_at' => task.created_at.as_json

长版

在ActiveSupport(5.1.4)中,所有值都通过ActiveSupport::JSON::Encoding#jsonify运行:

def jsonify(value)
  case value
  when String
    EscapedString.new(value)
  when Numeric, NilClass, TrueClass, FalseClass
    value.as_json
  when Hash
    Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
  when Array
    value.map { |v| jsonify(v) }
  else
    jsonify value.as_json
  end
end

正如您所看到的,ActiveSupport::TimeWithZone没有特殊情况,它只是调用ActiveSupport::TimeWithZone#as_json。那么让我们看看它是什么样的:

[1] pry(#<DataModelRowSerializer>)> show-source ActiveSupport::TimeWithZone#as_json

From: /Users/stevehull/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activesupport-5.1.4/lib/active_support/time_with_zone.rb @ line 165:
Owner: ActiveSupport::TimeWithZone
Visibility: public
Number of lines: 7

def as_json(options = nil)
  if ActiveSupport::JSON::Encoding.use_standard_json_time_format
    xmlschema(ActiveSupport::JSON::Encoding.time_precision)
  else
    %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
  end
end

因此,如果您愿意,可以期待'created_at' => task.created_at.xmlschema(ActiveSupport::JSON::Encoding.time_precision),但使用as_json会更加紧凑;)