Skip to content

Instantly share code, notes, and snippets.

@Rio517
Last active August 23, 2016 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rio517/431e3b02db702e26aa59a3ec80e2eae8 to your computer and use it in GitHub Desktop.
Save Rio517/431e3b02db702e26aa59a3ec80e2eae8 to your computer and use it in GitHub Desktop.
JSON-API mocking library/script - in Ruby
# JSON-API mocking library - a very, very simple script to help mock JSON-API
# JSON. This is more designed to return scaffolding that can then be manually
# edited. It supports only single resource objects, with included or linked
# children, but only one child per relationship. This might be useful for
# specing out what your API could look like for frontend and backend developers.
# Example usage is at the bottom. If I were to take this further, I'd make it
# more recursive.
class JsonApiMock
BASE_URL = 'https://hostmaker.co/api/v1/'
attr_accessor :output, :serializable_objects
def initialize(*serializable_objects)
self.serializable_objects = serializable_objects
self.output = {}
populate_object_data
populate_included_relationship_data
end
def print_output
puts JSON.pretty_generate(output)
end
private
def single_resource?
serializable_objects == 1
end
def populate_object_data
object_data = serializable_objects.map(&:object_data)
output[:data] = single_resource? ? object_data.first : object_data
end
def populate_included_relationship_data
included_data = serializable_objects.map(&:included_relationship_data)
.compact.flatten.sort_by{|o| o[:type]}
if included_data.any?
output[:included] = included_data
end
end
end
# Relationship represents the child relations of the root_object. Parameters:
# * included - boolean - specify if data in included in request - default: false
# * ar_object - ActiveRecord object - if child is AR object, pass in
# * type - relationship type - only required if ar_object is nil
# * relationship_name - name of relationship - defaults to type
# * attributes - the child objects attributes - defaults to null, pulls from
# ar_object if present
# * related_objects - Any child related objects
class SerializeableObject
include ActiveModel::Model #from Rails, just to be lazy about setting attrs
attr_accessor :relationship_name, :ar_object, :included, :type, :attributes, :related_objects,
:id, :base_object_data, :passed_attributes
def initialize(*args)
super(*args)
if built_from_ar_object?
self.attributes ||= {}
self.id ||= rand_id
else
self.attributes ||= ar_object.try(:attributes) | {}
self.type ||= ar_object.class.name.underscore.pluralize
self.id ||= attributes.id
end
self.attributes = attributes.slice(*passed_attributes) if passed_attributes && passed_attributes.any?
self.relationship_name ||= type
self.related_objects = Array(related_objects)
end
def object_data
@object_data ||= build_object_data
end
def included_relationship_data
related_objects.select(&:included).map(&:object_data) + related_objects.map(&:included_relationship_data)
end
protected
def add_relationship_data(output:{})
if output[relationship_name]
output[relationship_name][:links][:related] = self_collection_url
else
output[relationship_name] = {links: {related: self_single_url}}
end
if included
output[relationship_name][:data] ||= []
output[relationship_name][:data] << {type: type, id: id}
end
end
private
def build_object_data
self.base_object_data = {
type: type.to_s,
id: id,
attributes: attributes,
links: {self: self_single_url}
}
collect_child_relationships
base_object_data
end
def collect_child_relationships
if related_objects && related_objects.any?
base_object_data[:relationships] ||= {}
related_objects.each do |rel|
rel.add_relationship_data(output: base_object_data[:relationships])
end
end
end
def built_from_ar_object?
@built_from_ar_object ||= ar_object.nil?
end
def rand_id
@rand_id ||= Random.rand(9999)
end
def self_single_url
@self_url ||= self_collection_url + '/' + id.to_s
end
def self_collection_url
JsonApiMock::BASE_URL + type.to_s
end
end
# USAGE ##########
# below we load a user from our rails backend, but any
# object that responds to at least attributes and class is fine.
included_profile_photo = SerializeableObject.new(
included: true,
type: :profile_photos,
relationship_name: :active_profile_photo,
attributes: {
"60x60": "/path/to/60x60/image.jpg",
"120x120": "/path/to/120x120/image.jpg",
"240x240": "/path/to/240x240/image.jpg"
})
parent_object = SerializeableObject.new(ar_object: User.first, related_objects: [included_profile_photo])
JsonApiMock.new(parent_object).print_output
# After generating the above, then you can copy and paste to create has_many
# relations or add/remove attributes for unimplemented objects.
# Here is another example with multiple objects
mock_objects = Article.last(10).map do |article|
author = SerializeableObject.new(ar_object: article.author, included: true)
SerializeableObject.new(ar_object: article, related_objects: author)
end
JsonApiMock.new(*mock_objects).print_output
{
"data": {
"type": "user",
"id": 1,
"attributes": {
"email": "user@domain.com",
"sign_in_count": 234,
"current_sign_in_at": "2016-08-11 22:31:50 UTC",
"last_sign_in_at": "2016-08-09 05:29:01 UTC",
"current_sign_in_ip": "54.240.197.234",
"last_sign_in_ip": "67.170.10.201",
"name": "John Doe",
"role": "supervisor",
"phone_number": null,
"created_at": "2015-10-11 01:00:16 UTC",
"updated_at": "2016-08-11 22:31:50 UTC"
},
"links": {
"self": "https://domain.com/api/v1/user/1"
},
"relationships": {
"active_profile_photo": {
"links": {
"related": "https://domain.com/api/v1/profile_photos/1884"
},
"data": [
{
"type": "profile_photos",
"id": 1884
}
]
}
}
},
"included": [
{
"type": "profile_photos",
"id": 1884,
"attributes": {
"60x60": "/path/to/60x60/image.jpg",
"120x120": "/path/to/120x120/image.jpg",
"240x240": "/path/to/240x240/image.jpg"
},
"links": {
"self": "https://domain.com/api/v1/profile_photos/1884"
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment