Last active
August 23, 2016 20:13
-
-
Save Rio517/431e3b02db702e26aa59a3ec80e2eae8 to your computer and use it in GitHub Desktop.
JSON-API mocking library/script - in Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"data": { | |
"type": "user", | |
"id": 1, | |
"attributes": { | |
"email": "[email protected]", | |
"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