skemata

A lightweight schema.org object DSL written in Ruby. This library is currently under active development and is missing many features, including validation. See the contributing section for more information.

Code Climate CircleCI

Getting Started

Developed against MRI 2.4.0

Add the following line to your Gemfile:

gem 'skemata'

...or this to your *.gemspec:

gem.add_dependency 'skemata'

...and then bundle

Basic Usage

Invoke Skemata.draw with a schema.org type and a PORO that you wish to serialize. If an attribute isn't present, it will default to null. Provide a block to an attribute key to draw a child object. That's it!

require 'skemata'

schema_json = Skemata.draw :Thing, Dog.last do
  name
  description
  # You can also provide any kind of Ruby PORO for attribute values as long as they are serializable!
  some_custom_attribute_not_in_the_object 'woop woop'
end

schema_json looks like this:

{
  "@type": "Product",
  "@context": "https://schema.org",
  "name": "Fido",
  "description": "Cute and adorable!",
  "some_custom_attribute_not_in_the_object": "woop woop"
}

Advanced Usage

Defining attributes explicitly

  • different_key :attribute_name_on_object can be used to specify a different field name in the output JSON (for presentation), while :attribute_name_on_object will be the attribute that is retrieved from the object being serialized.
car = OpenStruct.new(
  brand: 'Mercedes-Benz',
  model: 'E550',
  next_model_up: OpenStruct.new(brand: 'Mercedes-Benz', model: 'E63 AMG')
)

car_json = Skemata.draw :Vehicle, car do
  brand_name :brand
  model
  next_model_up :Vehicle, :next_model_up do 
    model
  end
end

car_json looks like this:

{
  "@type": "Vehicle",
  "@context": "https://schema.org",
  "brand_name": "Mercedes-Benz",
  "model": "E550",
  "next_model_up": {
    "@type": "Vehicle",
    "model": "E63 AMG"
  }
}

Resolving attributes implicitly

After defining a few objects, it may become apparent that a lot of the schema entries for keys may match attributes on your objects. Skemata can infer field names by using the schema object type or the field name.

Fields

  • attribute_name is short for attribute_name :attribute_name. If these two match, the :attribute_name symbol does not need to be specified.

Objects

object_key :Type, :attribute_key do 
  # attributes
end

Shown above is the explicit way to specify a new child object with object_key under the parent (:attribute_key is the key containing another object to serialize in the block provided), with type :Type. It has happened frequently that either the object_key or :Type are actually fields in objects that we wish to serialize. The DSL will attempt to resolve both object_key and :Type by seeing if the root_object has either attribute before falling back on the explicit definition of :attribute_key if present.

Even less explicit!

If you know that the root_object has an attribute with the same name as :type, you do not need to provide any other arguments (other than the block).

object_key :type do 
  # attributes
end

Hashes

Specify attributes as Hash keys.

Skemata.draw :Foo, { bar: 'baz' } do
    bar
    bar_with_another_name :bar
end
{"@type":"Foo","@context":"https://schema.org","bar":"baz","bar_with_another_name":"baz"}

Distant attributes

Sometimes it is necessary to retrieve attributes from relational data (e.g. an ActiveRecord model) without serializing the whole object as a new child. Assuming a Student model with a Parent that has a name field.

Skemata.draw :Person, Student.last do
  parent_name nested(:parent, :name)
end
{"@type":"Person","@context":"https://schema.org","parent_name":"Some Name"}

Applying transformations

If this was not already apparent, since we effectively fold into one object for presentation, you can use any method that each successive object will respond to. Using the previous example, it would be valid to do this:

nested(:parent, :name, :upcase)

Contributing

This library is being built incrementally with features that are of immediate need. That being said, there is a plan to build:

  • schema.org type validations
  • Mapping support
    • Define a map for a certain object type and automatically marshal those objects without explicitly drawing it each time

How to contribute

  • Fork
  • Make a new branch
  • Write tests / ensure there are no linting errors
  • Pull request

Credits

Copyright (c) David Stancu, contributors, MBTMedia LLC 2017.

MIT License