MediaTypes
Installation
Add this line to your application's Gemfile:
gem 'media_types'
And then execute:
$ bundle
Or install it yourself as:
$ gem install media_types
Usage
By default there are no media types registered or defined, except for an abstract base type.
Definition
You can define media types by inheriting from this base type, or create your own base type with a class method
.base_format that is used to create the final media type string by injecting formatted parameters:
%<type>s: the typemedia_typereceived%<version>s: the version, defaults to:current_version%<view>s: the view, defaults to%<suffix>s: the suffix
require 'media_types'
class Venue < MediaTypes::Base
def self.base_format
'application/vnd.mydomain.%<type>s.v%<version>.s%<view>s+%<suffix>s'
end
media_type 'venue', defaults: { suffix: :json, version: 2 }
validations do
attribute :name, String
collection :location do
attribute :latitude, Numeric
attribute :longitude, Numeric
attribute :altitude, AllowNil(Numeric)
end
link :self
link :route, allow_nil: true
version 1 do
attribute :name, String
attribute :coords, String
attribute :updated_at, String
link :self
end
view 'create' do
collection :location do
attribute :latitude, Numeric
attribute :longitude, Numeric
attribute :altitude, AllowNil(Numeric)
end
version 1 do
collection :location do
attribute :latitude, Numeric
attribute :longitude, Numeric
attribute :altitude, AllowNil(Numeric)
end
end
end
end
registrations :venue_json do
view 'create', :create_venue
view 'index', :venue_urls
view 'collection', :venue_collection
versions [1,2]
suffix :json
suffix :xml
end
end
Schema Definitions
If you define a scheme using current_scheme { }, you may use any of the following dsl:
attribute
Adds an attribute to the schema, if a +block+ is given, uses that to test against instead of +type+
| param | type | description |
|---|---|---|
| key | Symbol |
the attribute name |
| opts | Hash |
options to pass to Scheme or Attribute |
| type | Class, ===, Scheme |
The type of the value, can be anything that responds to ===, or scheme to use if no &block is given. Defaults to String without a &block and to Hash with a &block. |
| optional: | TrueClass, FalseClass |
if true, key may be absent, defaults to false |
| &block | Block |
defines the scheme of the value of this attribute |
Add an attribute named foo, expecting a string
require 'media_types'
class MyMedia
include MediaTypes::Dsl
validations do
attribute :foo, String
end
end
MyMedia.valid?({ foo: 'my-string' })
# => true
Add an attribute named foo, expecting nested scheme
class MyMedia
include MediaTypes::Dsl
validations do
attribute :foo do
attribute :bar, String
end
end
end
MyMedia.valid?({ foo: { bar: 'my-string' }})
# => true
any
Allow for any key. The &block defines the Schema for each value.
| param | type | description |
|---|---|---|
| scheme | Scheme, NilClass |
scheme to use if no &block is given |
| allow_empty: | TrueClass, FalsClass |
if true, empty (no key/value present) is allowed |
| expected_type: | Class, |
forces the validated value to have this type, defaults to Hash. Use Object if either Hash or Array is fine |
| &block | Block |
defines the scheme of the value of this attribute |
Add a collection named foo, expecting any key with a defined value
class MyMedia
include MediaTypes::Dsl
validations do
collection :foo do
any do
attribute :bar, String
end
end
end
end
MyMedia.valid?({ foo: [{ anything: { bar: 'my-string' }, other_thing: { bar: 'other-string' } }] })
# => true
not_strict
Allow for extra keys in the schema/collection even when passing strict: true to #validate!
Allow for extra keys in collection
class MyMedia
include MediaTypes::Dsl
validations do
collection :foo do
attribute :required, String
not_strict
end
end
end
MyMedia.valid?({ foo: [{ required: 'test', bar: 42 }] })
# => true
collection
Expect a collection such as an array or hash. The &block defines the Schema for each item in that collection.
| param | type | description |
|---|---|---|
| key | Symbol |
key of the collection (same as #attribute) |
| scheme | Scheme, NilClass, Class |
scheme to use if no &block is given or Class of each item in the |
| allow_empty: | TrueClass, FalseClass |
if true, empty (no key/value present) is allowed |
| expected_type: | Class, |
forces the validated value to have this type, defaults to Array. Use Object if either Array or Hash is fine. |
| optional: | TrueClass, FalseClass |
if true, key may be absent, defaults to false |
| &block | Block |
defines the scheme of the value of this attribute |
Collection with an array of string
class MyMedia
include MediaTypes::Dsl
validations do
collection :foo, String
end
end
MyMedia.valid?({ collection: ['foo', 'bar'] })
# => true
Collection with defined scheme
class MyMedia
include MediaTypes::Dsl
validations do
collection :foo do
attribute :required, String
attribute :number, Numeric
end
end
end
MyMedia.valid?({ foo: [{ required: 'test', number: 42 }, { required: 'other', number: 0 }] })
# => true
link
Expect a link with a required href: String attribute
| param | type | description |
|---|---|---|
| key | Symbol |
key of the link (same as #attribute) |
| allow_nil: | TrueClass, FalseClass |
if true, value may be nil |
| optional: | TrueClass, FalseClass |
if true, key may be absent, defaults to false |
| &block | Block |
defines the scheme of the value of this attribute, in addition to the href attribute |
Links as defined in HAL, JSON-Links and other specs
class MyMedia
include MediaTypes::Dsl
validations do
link :_self
link :image
end
end
MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
# => true
Link with extra attributes
class MyMedia
include MediaTypes::Dsl
validations do
link :image do
attribute :templated, TrueClass
end
end
end
MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
# => true
Link with extra attributes
class MyMedia
include MediaTypes::Dsl
validations do
link :image do
attribute :templated, TrueClass
end
end
end
MyMedia.valid?({ _links: { image: { href: 'https://image.org/{md5}', templated: true }} })
# => true
Validation
If your type has a validations, you can now use this media type for validation:
Venue.valid?({
#...
})
# => true if valid, false otherwise
Venue.validate!({
# /*...*/
})
# => raises if it's not valid
If an array is passed, check the scheme for each value, unless the scheme is defined as expecting a hash:
expected_hash = Scheme.new(expected_type: Hash) { attribute(:foo) }
expected_object = Scheme.new { attribute(:foo) }
expected_hash.valid?({ foo: 'string' })
# => true
expected_hash.valid?([{ foo: 'string' }])
# => false
expected_object.valid?({ foo: 'string' })
# => true
expected_object.valid?([{ foo: 'string' }])
# => true
Formatting for headers
Any media type object can be coerced in valid string to be used with Content-Type or Accept:
Venue.mime_type.to_s
# => "application/vnd.mydomain.venue.v2+json"
Venue.mime_type.version(1).to_s
# => "application/vnd.mydomain.venue.v1+json"
Venue.mime_type.version(1).suffix(:xml).to_s
# => "application/vnd.mydomain.venue.v1+xml"
Venue.mime_type.to_s(0.2)
# => "application/vnd.mydomain.venue.v2+json; q=0.2"
Venue.mime_type.collection.to_s
# => "application/vnd.mydomain.venue.v2.collection+json"
Venue.mime_type.view('active').to_s
# => "application/vnd.mydomain.venue.v2.active+json"
Register in Rails or Rack
Define a registrations block on your media type, indicating the symbol for the base type (registrations :symbol do)
and inside use the registrations dsl to define which media types to register. versions array_of_numbers determines which versions,
suffix name adds a suffix, type_alias name adds an alias and view name, symbol adds a view.
As long as action_dispatch is available, you can register the mime type with action_dispatch/http/mime_type:
Venue.register
# => Mime type is now available using the symbol, or lookup the actual mimetype
You can do this in the mime_types initializer, or anywhere before your controllers are instantiated. Yes, the symbol
(by default <type>_v<version>_<suffix>) can now be used in your format blocks, or as extension in the url.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can
also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the
version number in version.rb, call bundle exec rake release to create a new git tag, push git commits and tags, and
push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at SleeplessByte/media-types-ruby