Sumaki
Sumaki is a wrapper for structured data like JSON.
Since Sumaki wraps the target data as it is, rather than parsing it using a schema, the original data can be referenced at any time.
This makes it easy to add or modify definitions as needed while checking the target data.
This feature may be useful when there is no document defining the structure of the data, or when the specification is complex and difficult to grasp, and the definition is written little by little starting from the obvious places.
class AnimeList
include Sumaki::Model
repeated :anime
field :name
class Anime
include Sumaki::Model
singular :studio
field :title
class Studio
include Sumaki::Model
field :name
end
end
end
data = {
name: 'Winter 2023',
anime: [
{
title: 'The Vampire Dies in No Time',
studio: {
name: 'MADHOUSE Inc.'
}
},
{
title: '“Ippon” again!',
studio: {
name: 'BAKKEN RECORD'
}
}
]
}
anime_list = AnimeList.new(data)
anime_list.name #=> 'Winter 2023'
anime_list.anime[0].title #=> 'The Vampire Dies in No Time'
anime_list.anime[0].studio.name #=> 'MADHOUSE Inc.'
anime_list.anime[0].object #=> { title: 'The Vampire Dies in No Time', studio: { name: 'MADHOUSE Inc.' } }
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add sumaki
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install sumaki
Usage
Include Sumaki::Model
module in the class that wraps the data, and give the data when creating the instance.
class Anime
include Sumaki::Model
end
Anime.new({})
Only this does not give access to fields or structured data, so the following declarations need to be added.
Access to the fields
By declaring field
, you can access the field.
class Anime
include Sumaki::Model
field :title
field :url
end
# Read the field values
anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
anime.title #=> 'The Vampire Dies in No Time'
anime.url #=> 'https://sugushinu-anime.jp/'
# Write the field value
anime = Anime.new({})
anime.title = 'The Vampire Dies in No Time'
anime.title #=> 'The Vampire Dies in No Time'
If the data contains attributes not declared in the field, it raises no error and is simply ignored.
Type casting
When a type is specified, it will be typecast.
class Character
include Sumaki::Model
field :age, :int
end
character = Character.new({ age: '208' })
character.age #=> 208
Types are:
:int
:float
:string
:bool
:date
:datetime
Access to the sub object
By declaring singular
, you can access the sub object.
class Book
include Sumaki::Model
singular :company
field :title
class Company
include Sumaki::Model
field :name
end
end
data = {
title: 'The Ronaldo Chronicles',
company: {
name: 'Autumn Books',
}
}
# Read from the sub object
book = Book.new(data)
book.company.name #=> 'Autumn Books'
# Build a sub object
book = Book.new({})
book.build_company(name: 'Autumn Books')
book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
Sub object is wrapped with the class inferred from the field name under the original class.
This can be changed by specifying the class to wrap.
class Book
include Sumaki::Model
singular :author, class_name: 'Character'
field :title
class Character
include Sumaki::Model
field :name
end
end
data = {
title: 'The Ronaldo Chronicles',
author: {
name: 'Ronaldo'
}
}
book = Book.new(data)
book.author.class #=> Book::Character
Access to the repeated sub objects
By declaring repeated
, you can access the repeated sub objects as an Array.
class Company
include Sumaki::Model
repeated :member
field :name
class Member
include Sumaki::Model
field :name
end
end
data = {
name: 'The Ronaldo Vampire Hunter Agency',
member: [
{ name: 'Ronaldo' },
{ name: 'Draluc' },
{ name: 'John' }
]
}
# Read from the sub object
company = Company.new(data)
company.member.size #=> 3
company.member[2].name #=> 'John'
# Build a sub object
company = Company.new({})
company.member.build(name: 'John')
company.member[0].name #=> 'John'
The class_name
option can also be used to specify the class to wrap.
Access to the parent object
Parent object can be referenced from sub object by #parent
method.
class Character
include Sumaki::Model
singular :child
field :name
class Child
include Sumaki::Model
field :name
end
end
data = {
name: 'Draus',
child: {
name: 'Draluc'
}
}
character = Character.new(data)
character.child.name #=> 'Draluc'
character.child.parent.name #=> 'Draus'
Enumerations
By declaring enum
, You can map a field to the specified value.
class Character
include Sumaki::Model
field :name
enum :type, { vampire: 1, vampire_hunter: 2, familier: 3, editor: 4 }
end
data = {
name: 'John',
type: 3
}
# Read the enum
character = Character.new(data)
character.type.name #=> :familier
character.type.familier? #=> true
# Write the enum value
character = Character.new({})
character.type = 1
character.type.name #=> :vampire
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
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
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/nowlinuxing/sumaki.
License
The gem is available as open source under the terms of the MIT License.