sumaki 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 743c9cc65c7fb7b06a254d6f96ccacd405101fbd87b12b9ba9a0283341f0203b
4
+ data.tar.gz: 922c9652b6a0bee5fcca21e261b653a8fe9362ad0ee174050f6a0e8fae9986d1
5
+ SHA512:
6
+ metadata.gz: 3d368a98fe977ba95aad3b0d01233a46eb00391e0c2825faa6b4fe9991b6394808a90dbfe37af1648ddf3bed13b904fb3dee72f5ee83237c4c0d876a628df660
7
+ data.tar.gz: 122196f48283f4e23f3a9ba52d84aff764487066016da59bb42e5fe5e405780bde902f41bf45bc25ead3b6fcc5347935b786d4723e17328f137052b0ff0836ed
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ NewCops: enable
5
+
6
+ Lint/ConstantDefinitionInBlock:
7
+ Exclude:
8
+ - 'spec/sumaki/model/association_spec.rb'
9
+ - 'spec/sumaki/model/attribute_spec.rb'
10
+ - 'spec/sumaki/model/enum_spec.rb'
11
+
12
+ RSpec/LeakyConstantDeclaration:
13
+ Exclude:
14
+ - 'spec/sumaki/model/association_spec.rb'
15
+ - 'spec/sumaki/model/attribute_spec.rb'
16
+ - 'spec/sumaki/model/enum_spec.rb'
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-01-29
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in sumaki.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+
10
+ gem 'debug'
11
+ gem 'rspec', '~> 3.0'
12
+ gem 'rubocop', require: false
13
+ gem 'rubocop-rspec', require: false
14
+ gem 'simplecov'
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sumaki (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ debug (1.7.1)
11
+ irb (>= 1.5.0)
12
+ reline (>= 0.3.1)
13
+ diff-lcs (1.5.0)
14
+ docile (1.4.0)
15
+ io-console (0.6.0)
16
+ irb (1.6.2)
17
+ reline (>= 0.3.0)
18
+ json (2.6.3)
19
+ parallel (1.22.1)
20
+ parser (3.2.0.0)
21
+ ast (~> 2.4.1)
22
+ rainbow (3.1.1)
23
+ rake (13.0.6)
24
+ regexp_parser (2.6.2)
25
+ reline (0.3.2)
26
+ io-console (~> 0.5)
27
+ rexml (3.2.5)
28
+ rspec (3.12.0)
29
+ rspec-core (~> 3.12.0)
30
+ rspec-expectations (~> 3.12.0)
31
+ rspec-mocks (~> 3.12.0)
32
+ rspec-core (3.12.0)
33
+ rspec-support (~> 3.12.0)
34
+ rspec-expectations (3.12.2)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.12.0)
37
+ rspec-mocks (3.12.3)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.12.0)
40
+ rspec-support (3.12.0)
41
+ rubocop (1.44.1)
42
+ json (~> 2.3)
43
+ parallel (~> 1.10)
44
+ parser (>= 3.2.0.0)
45
+ rainbow (>= 2.2.2, < 4.0)
46
+ regexp_parser (>= 1.8, < 3.0)
47
+ rexml (>= 3.2.5, < 4.0)
48
+ rubocop-ast (>= 1.24.1, < 2.0)
49
+ ruby-progressbar (~> 1.7)
50
+ unicode-display_width (>= 2.4.0, < 3.0)
51
+ rubocop-ast (1.24.1)
52
+ parser (>= 3.1.1.0)
53
+ rubocop-capybara (2.17.0)
54
+ rubocop (~> 1.41)
55
+ rubocop-rspec (2.18.1)
56
+ rubocop (~> 1.33)
57
+ rubocop-capybara (~> 2.17)
58
+ ruby-progressbar (1.11.0)
59
+ simplecov (0.22.0)
60
+ docile (~> 1.1)
61
+ simplecov-html (~> 0.11)
62
+ simplecov_json_formatter (~> 0.1)
63
+ simplecov-html (0.12.3)
64
+ simplecov_json_formatter (0.1.4)
65
+ unicode-display_width (2.4.2)
66
+
67
+ PLATFORMS
68
+ x86_64-linux
69
+
70
+ DEPENDENCIES
71
+ debug
72
+ rake (~> 13.0)
73
+ rspec (~> 3.0)
74
+ rubocop
75
+ rubocop-rspec
76
+ simplecov
77
+ sumaki!
78
+
79
+ BUNDLED WITH
80
+ 2.4.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Loose Coupling
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # Sumaki
2
+
3
+ ---
4
+
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/dd92d4092d6858cbcfb2/maintainability)](https://codeclimate.com/github/nowlinuxing/sumaki/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/dd92d4092d6858cbcfb2/test_coverage)](https://codeclimate.com/github/nowlinuxing/sumaki/test_coverage)
7
+
8
+ **Sumaki** is a wrapper for structured data like JSON.
9
+
10
+ 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.
11
+
12
+ This makes it easy to add or modify definitions as needed while checking the target data.
13
+
14
+ 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.
15
+
16
+ ```ruby
17
+ class AnimeList
18
+ include Sumaki::Model
19
+ repeated :anime
20
+ field :name
21
+
22
+ class Anime
23
+ include Sumaki::Model
24
+ singular :studio
25
+ field :title
26
+
27
+ class Studio
28
+ include Sumaki::Model
29
+ field :name
30
+ end
31
+ end
32
+ end
33
+
34
+ data = {
35
+ name: 'Winter 2023',
36
+ anime: [
37
+ {
38
+ title: 'The Vampire Dies in No Time',
39
+ studio: {
40
+ name: 'MADHOUSE Inc.'
41
+ }
42
+ },
43
+ {
44
+ title: '“Ippon” again!',
45
+ studio: {
46
+ name: 'BAKKEN RECORD'
47
+ }
48
+ }
49
+ ]
50
+ }
51
+
52
+ anime_list = AnimeList.new(data)
53
+ anime_list.name #=> 'Winter 2023'
54
+ anime_list.anime[0].title #=> 'The Vampire Dies in No Time'
55
+ anime_list.anime[0].studio.name #=> 'MADHOUSE Inc.'
56
+ anime_list.anime[0].object #=> { title: 'The Vampire Dies in No Time', studio: { name: 'MADHOUSE Inc.' } }
57
+ ```
58
+
59
+
60
+ ## Installation
61
+
62
+ Install the gem and add to the application's Gemfile by executing:
63
+
64
+ $ bundle add sumaki
65
+
66
+ If bundler is not being used to manage dependencies, install the gem by executing:
67
+
68
+ $ gem install sumaki
69
+
70
+ ## Usage
71
+
72
+ Include `Sumaki::Model` module in the class that wraps the data, and give the data when creating the instance.
73
+
74
+ ```ruby
75
+ class Anime
76
+ include Sumaki::Model
77
+ end
78
+
79
+ Anime.new({})
80
+ ```
81
+
82
+ Only this does not give access to fields or structured data, so the following declarations need to be added.
83
+
84
+ ### Access to the fields
85
+
86
+ By declaring `field`, you can access the field.
87
+
88
+ ```ruby
89
+ class Anime
90
+ include Sumaki::Model
91
+ field :title
92
+ field :url
93
+ end
94
+
95
+ anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
96
+ anime.title #=> 'The Vampire Dies in No Time'
97
+ anime.url #=> 'https://sugushinu-anime.jp/'
98
+ ```
99
+
100
+ If the data contains attributes not declared in the field, it raises no error and is simply ignored.
101
+
102
+ ### Access to the sub object
103
+
104
+ By declaring `singular`, you can access the sub object.
105
+
106
+ ```ruby
107
+ class Book
108
+ include Sumaki::Model
109
+ singular :company
110
+ field :title
111
+
112
+ class Company
113
+ include Sumaki::Model
114
+ field :name
115
+ field :prefecture
116
+ end
117
+ end
118
+
119
+ data = {
120
+ title: 'The Ronaldo Chronicles',
121
+ company: {
122
+ name: 'Autumn Books',
123
+ prefecture: 'Tokyo'
124
+ }
125
+ }
126
+
127
+ comic = Book.new(data)
128
+ comic.company.name #=> 'Autumn Books'
129
+ ```
130
+
131
+ sub object is wrapped with the class inferred from the field name under the original class.
132
+
133
+ This can be changed by specifying the class to wrap.
134
+
135
+ ```ruby
136
+ class Book
137
+ include Sumaki::Model
138
+ singular :author, class_name: 'Character'
139
+ field :title
140
+
141
+ class Character
142
+ include Sumaki::Model
143
+ field :name
144
+ end
145
+ end
146
+
147
+ data = {
148
+ title: 'The Ronaldo Chronicles',
149
+ author: {
150
+ name: 'Ronaldo'
151
+ }
152
+ }
153
+
154
+ book = Book.new(data)
155
+ book.author.class #=> Book::Character
156
+ ```
157
+
158
+ ### Access to the repeated sub objects
159
+
160
+ By declaring `repeated`, you can access the repeated sub objects as an Array.
161
+
162
+ ```ruby
163
+ class Company
164
+ include Sumaki::Model
165
+ repeated :member
166
+ field :name
167
+
168
+ class Member
169
+ include Sumaki::Model
170
+ field :name
171
+ end
172
+ end
173
+
174
+ data = {
175
+ name: 'The Ronaldo Vampire Hunter Agency',
176
+ member: [
177
+ { name: 'Ronaldo' },
178
+ { name: 'Draluc' },
179
+ { name: 'John' }
180
+ ]
181
+ }
182
+
183
+ company = Company.new(data)
184
+ company.member.size #=> 3
185
+ company.member[2].name #=> 'John'
186
+ ```
187
+
188
+ The `class_name` option can also be used to specify the class to wrap.
189
+
190
+ ### Access to the parent object
191
+
192
+ Parent object can be referenced from sub object by `#parent` method.
193
+
194
+ ```ruby
195
+ class Character
196
+ include Sumaki::Model
197
+ singular :child
198
+ field :name
199
+
200
+ class Child
201
+ include Sumaki::Model
202
+ field :name
203
+ end
204
+ end
205
+
206
+ data = {
207
+ name: 'Draus',
208
+ child: {
209
+ name: 'Draluc'
210
+ }
211
+ }
212
+
213
+ character = Character.new(data)
214
+ character.child.name #=> 'Draluc'
215
+ character.child.parent.name #=> 'Draus'
216
+ ```
217
+
218
+ ### Enumerations
219
+
220
+ By declaring `enum`, You can map a field to the specified value.
221
+
222
+ ```ruby
223
+ class Character
224
+ include Sumaki::Model
225
+ field :name
226
+ enum :type, vampire: 1, vampire_hunter: 2, familier: 3, editor: 4
227
+ end
228
+
229
+ data = {
230
+ name: 'John',
231
+ type: 3
232
+ }
233
+
234
+ character = Character.new(data)
235
+ character.type #=> :familier
236
+ ```
237
+
238
+
239
+ ## Development
240
+
241
+ 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.
242
+
243
+ 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](https://rubygems.org).
244
+
245
+ ## Contributing
246
+
247
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sumaki.
248
+
249
+ ## License
250
+
251
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumaki
4
+ module Adapter
5
+ # = Sumaki::Adapter::Hash
6
+ class Hash
7
+ def get(data, key)
8
+ data[key]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'adapter/hash'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'adapter'
4
+
5
+ module Sumaki
6
+ module Config # :nodoc:
7
+ singleton_class.attr_accessor :default_adapter
8
+
9
+ self.default_adapter = Sumaki::Adapter::Hash.new
10
+ end
11
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumaki
4
+ module Model
5
+ # = Sumaki::Model::Association
6
+ module Association
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.include InstanceMethods
10
+
11
+ base.instance_variable_set(:@classes, {})
12
+ end
13
+
14
+ module ClassMethods # :nodoc:
15
+ # Access to the sub object.
16
+ #
17
+ # class Book
18
+ # include Sumaki::Model
19
+ # singular :company
20
+ #
21
+ # class Company
22
+ # include Sumaki::Model
23
+ # end
24
+ # end
25
+ #
26
+ # data = {
27
+ # title: 'The Ronaldo Chronicles',
28
+ # company: {
29
+ # name: 'Autumn Books',
30
+ # }
31
+ # }
32
+ # book = Book.new(data)
33
+ # book.company.class #=> Book::Company
34
+ #
35
+ # == Options
36
+ #
37
+ # [:class_name]
38
+ # Specify the name of the class to wrap. Use this if the name of the class
39
+ # to wrap is not inferred from the nested field names.
40
+ def singular(name, class_name: nil)
41
+ association_methods_module.define_method(name) do
42
+ klass = self.class.class_for(name, class_name)
43
+ klass.new(get(name), parent: self)
44
+ end
45
+ end
46
+
47
+ # Access to the repeated sub objects
48
+ #
49
+ # class Company
50
+ # include Sumaki::Model
51
+ # repeated :member
52
+ #
53
+ # class Member
54
+ # include Sumaki::Model
55
+ # end
56
+ # end
57
+ #
58
+ # data = {
59
+ # name: 'The Ronaldo Vampire Hunter Agency',
60
+ # member: [
61
+ # { name: 'Ronaldo' },
62
+ # { name: 'Draluc' },
63
+ # { name: 'John' }
64
+ # ]
65
+ # }
66
+ # company = Company.new(data)
67
+ # company.member[2].class #=> Company::Member
68
+ #
69
+ # == Options
70
+ #
71
+ # [:class_name]
72
+ # Specify the name of the class to wrap. Use this if the name of the class
73
+ # to wrap is not inferred from the nested field names.
74
+ def repeated(name, class_name: nil)
75
+ association_methods_module.define_method(name) do
76
+ klass = self.class.class_for(name, class_name)
77
+ get(name).map { |object| klass.new(object, parent: self) }
78
+ end
79
+ end
80
+
81
+ def class_for(name, class_name = nil)
82
+ return @classes[name] if @classes.key?(name)
83
+
84
+ basename = class_name || classify(name.to_s)
85
+ klass = if const_defined?(basename)
86
+ const_get(basename)
87
+ else
88
+ const_set(basename, Class.new { include Model })
89
+ end
90
+ klass.parent ||= self
91
+ @classes[name] = klass
92
+ end
93
+
94
+ private
95
+
96
+ def association_methods_module
97
+ @association_methods_module ||= begin
98
+ mod = Module.new
99
+ include mod
100
+ mod
101
+ end
102
+ end
103
+
104
+ def classify(key)
105
+ key.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
106
+ end
107
+ end
108
+
109
+ module InstanceMethods # :nodoc:
110
+ private
111
+
112
+ def get(name)
113
+ self.class.adapter.get(object, name)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumaki
4
+ module Model
5
+ # = Sumaki::Model::Attribute
6
+ module Attribute
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods # :nodoc:
12
+ # Access to the field.
13
+ #
14
+ # class Anime
15
+ # include Sumaki::Model
16
+ # field :title
17
+ # field :url
18
+ # end
19
+ #
20
+ # anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
21
+ # anime.title #=> 'The Vampire Dies in No Time'
22
+ # anime.url #=> 'https://sugushinu-anime.jp/'
23
+ def field(name)
24
+ attribute_methods_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
25
+ def #{name} # def title
26
+ get(:'#{name}') # get(:'title')
27
+ end # end
28
+ RUBY
29
+ end
30
+
31
+ private
32
+
33
+ def attribute_methods_module
34
+ @attribute_methods_module ||= begin
35
+ mod = Module.new
36
+ include mod
37
+ mod
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumaki
4
+ module Model
5
+ # = Sumaki::Model::Enum
6
+ module Enum
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ class EnumValue < Hash # :nodoc:
12
+ code = <<-RUBY
13
+ case value
14
+ when Symbol then super(value) || super(value.to_s)
15
+ when String then super(value) || super(value.to_sym)
16
+ else super(value)
17
+ end
18
+ RUBY
19
+
20
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
21
+ def key?(value); #{code}; end
22
+ RUBY
23
+
24
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
25
+ def key(value); #{code}; end
26
+ RUBY
27
+
28
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
29
+ def value?(value); #{code}; end
30
+ RUBY
31
+ end
32
+
33
+ module ClassMethods # :nodoc:
34
+ # Map a field to the specified value
35
+ #
36
+ # class Character
37
+ # include Sumaki::Model
38
+ # field :name
39
+ # enum :type, vampire: 1, vampire_hunter: 2, familier: 3, editor: 4
40
+ # end
41
+ #
42
+ # data = {
43
+ # name: 'John',
44
+ # type: 3
45
+ # }
46
+ #
47
+ # character = Character.new(data)
48
+ # character.type #=> :familier
49
+ def enum(name, **values)
50
+ values = EnumValue[values]
51
+
52
+ enum_methods_module.define_method(name) do
53
+ value = get(name)
54
+
55
+ if values.key?(value)
56
+ value
57
+ elsif values.value?(value)
58
+ values.key(value)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def enum_methods_module
66
+ @enum_methods_module ||= begin
67
+ mod = Module.new
68
+ include mod
69
+ mod
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model/attribute'
4
+ require_relative 'model/association'
5
+ require_relative 'model/enum'
6
+
7
+ module Sumaki
8
+ # = Sumaki
9
+ #
10
+ # Sumaki is a wrapper for structured data like JSON.
11
+ #
12
+ # class AnimeList
13
+ # include Sumaki::Model
14
+ # repeated :anime
15
+ # field :name
16
+ #
17
+ # class Anime
18
+ # include Sumaki::Model
19
+ # singular :studio
20
+ # field :title
21
+ #
22
+ # class Studio
23
+ # include Sumaki::Model
24
+ # field :name
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # data = {
30
+ # name: 'Winter 2023',
31
+ # anime: [
32
+ # {
33
+ # title: 'The Vampire Dies in No Time',
34
+ # studio: {
35
+ # name: 'MADHOUSE Inc.'
36
+ # }
37
+ # },
38
+ # {
39
+ # title: '“Ippon” again!',
40
+ # studio: {
41
+ # name: 'BAKKEN RECORD'
42
+ # }
43
+ # }
44
+ # ]
45
+ # }
46
+ #
47
+ # anime_list = AnimeList.new(data)
48
+ # anime_list.name #=> 'Winter 2023'
49
+ # anime_list.anime[0].title #=> 'The Vampire Dies in No Time'
50
+ # anime_list.anime[0].studio.name #=> 'MADHOUSE Inc.'
51
+ # anime_list.anime[0].object #=> { title: 'The Vampire Dies in No Time', studio: { name: 'MADHOUSE Inc.' } }
52
+ #
53
+ # == Access to fields
54
+ #
55
+ # By declaring `field`, you can access the field.
56
+ #
57
+ # class Anime
58
+ # include Sumaki::Model
59
+ # field :title
60
+ # field :url
61
+ # end
62
+ #
63
+ # anime = Anime.new({ title: "The Vampire Dies in No Time", url: "https://sugushinu-anime.jp/" })
64
+ # anime.title #=> "The Vampire Dies in No Time"
65
+ #
66
+ # == Access to sub objects
67
+ #
68
+ # By declaring `singular`, you can access the sub object.
69
+ #
70
+ # class Book
71
+ # include Sumaki::Model
72
+ # singular :company
73
+ # field :title
74
+ #
75
+ # class Company
76
+ # include Sumaki::Model
77
+ # field :name
78
+ # end
79
+ # end
80
+ #
81
+ # data = {
82
+ # title: "The Ronaldo Chronicles",
83
+ # company: {
84
+ # name: 'Autumn Books',
85
+ # }
86
+ # }
87
+ #
88
+ # comic = Book.new(data)
89
+ # comic.company.name #=> 'Autumn Books'
90
+ #
91
+ # By declaring `repeated`, you can access the repeated sub objects as an Array.
92
+ #
93
+ # class Company
94
+ # include Sumaki::Model
95
+ # repeated :member
96
+ # field :name
97
+ #
98
+ # class Member
99
+ # include Sumaki::Model
100
+ # field :name
101
+ # end
102
+ # end
103
+ #
104
+ # data = {
105
+ # name: 'The Ronaldo Vampire Hunter Agency',
106
+ # member: [
107
+ # { name: 'Ronaldo' },
108
+ # { name: 'Draluc' },
109
+ # { name: 'John' }
110
+ # ]
111
+ # }
112
+ #
113
+ # company = Company.new(data)
114
+ # company.member.size #=> 3
115
+ # company.member[2].name #=> 'John'
116
+ #
117
+ # == Access to the parent object
118
+ #
119
+ # Parent object can be referenced from sub object by `#parent` method.
120
+ #
121
+ # class Character
122
+ # include Sumaki::Model
123
+ # singular :child
124
+ # field :name
125
+ #
126
+ # class Child
127
+ # include Sumaki::Model
128
+ # field :name
129
+ # end
130
+ # end
131
+ #
132
+ # data = {
133
+ # name: 'Draus',
134
+ # child: {
135
+ # name: 'Draluc'
136
+ # }
137
+ # }
138
+ #
139
+ # character = Character.new(data)
140
+ # character.child.name #=> 'Draluc'
141
+ # character.child.parent.name #=> 'Draus'
142
+ #
143
+ # == Enumerations
144
+ #
145
+ # By declaring `enum`, You can map a field to a specified value.
146
+ #
147
+ # class Character
148
+ # include Sumaki::Model
149
+ # field :name
150
+ # enum :type, vampire: 1, vampire_hunter: 2, familier: 3, editor: 4
151
+ # end
152
+ #
153
+ # data = {
154
+ # name: 'John',
155
+ # type: 3
156
+ # }
157
+ #
158
+ # character = Character.new(data)
159
+ # character.type #=> :familier
160
+ #
161
+ module Model
162
+ def self.included(base)
163
+ base.extend ClassMethods
164
+ base.include InstanceMethods
165
+
166
+ base.include Attribute
167
+ base.include Association
168
+ base.include Enum
169
+ end
170
+
171
+ module ClassMethods # :nodoc:
172
+ attr_writer :adapter
173
+ attr_accessor :parent
174
+
175
+ def adapter
176
+ @adapter || parent&.adapter || Config.default_adapter
177
+ end
178
+ end
179
+
180
+ module InstanceMethods # :nodoc:
181
+ attr_reader :object, :parent
182
+
183
+ def initialize(object, parent: nil)
184
+ @object = object
185
+ @parent = parent
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumaki
4
+ VERSION = '0.1.0'
5
+ end
data/lib/sumaki.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'sumaki/version'
4
+ require_relative 'sumaki/config'
5
+ require_relative 'sumaki/adapter'
6
+ require_relative 'sumaki/model'
data/sig/sumaki.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Sumaki
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/sumaki.gemspec ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/sumaki/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'sumaki'
7
+ spec.version = Sumaki::VERSION
8
+ spec.authors = ['Loose Coupling']
9
+ spec.email = ['loosecpl@gmail.com']
10
+
11
+ spec.summary = 'Sumaki is a wrapper for structured data like JSON.'
12
+ spec.description = <<~DESC
13
+ Sumaki is a wrapper for structured data like JSON.
14
+ 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.
15
+ This makes it easy to add or modify definitions as needed while checking the target data.
16
+ 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.
17
+ DESC
18
+ spec.homepage = 'https://github.com/nowlinuxing/sumaki/'
19
+ spec.license = 'MIT'
20
+ spec.required_ruby_version = '>= 2.6.0'
21
+
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
23
+
24
+ spec.metadata['homepage_uri'] = spec.homepage
25
+ spec.metadata['source_code_uri'] = spec.homepage
26
+ spec.metadata['changelog_uri'] = spec.homepage
27
+ spec.metadata['rubygems_mfa_required'] = 'true'
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(__dir__) do
32
+ `git ls-files -z`.split("\x0").reject do |f|
33
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
34
+ end
35
+ end
36
+ spec.bindir = 'exe'
37
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ['lib']
39
+
40
+ # Uncomment to register a new dependency of your gem
41
+ # spec.add_dependency "example-gem", "~> 1.0"
42
+
43
+ # For more information and examples about making a new gem, check out our
44
+ # guide at: https://bundler.io/guides/creating_gem.html
45
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sumaki
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Loose Coupling
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Sumaki is a wrapper for structured data like JSON.
15
+ 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.
16
+ This makes it easy to add or modify definitions as needed while checking the target data.
17
+ 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.
18
+ email:
19
+ - loosecpl@gmail.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - ".rspec"
25
+ - ".rubocop.yml"
26
+ - CHANGELOG.md
27
+ - Gemfile
28
+ - Gemfile.lock
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - lib/sumaki.rb
33
+ - lib/sumaki/adapter.rb
34
+ - lib/sumaki/adapter/hash.rb
35
+ - lib/sumaki/config.rb
36
+ - lib/sumaki/model.rb
37
+ - lib/sumaki/model/association.rb
38
+ - lib/sumaki/model/attribute.rb
39
+ - lib/sumaki/model/enum.rb
40
+ - lib/sumaki/version.rb
41
+ - sig/sumaki.rbs
42
+ - sumaki.gemspec
43
+ homepage: https://github.com/nowlinuxing/sumaki/
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ allowed_push_host: https://rubygems.org/
48
+ homepage_uri: https://github.com/nowlinuxing/sumaki/
49
+ source_code_uri: https://github.com/nowlinuxing/sumaki/
50
+ changelog_uri: https://github.com/nowlinuxing/sumaki/
51
+ rubygems_mfa_required: 'true'
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.6.0
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.4.1
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Sumaki is a wrapper for structured data like JSON.
71
+ test_files: []