sumaki 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/Gemfile.lock +14 -12
- data/README.md +45 -6
- data/lib/sumaki/adapter/hash.rb +16 -0
- data/lib/sumaki/model/associations/association.rb +53 -0
- data/lib/sumaki/model/associations/collection.rb +53 -0
- data/lib/sumaki/model/associations/reflection.rb +64 -0
- data/lib/sumaki/model/associations.rb +159 -0
- data/lib/sumaki/model/attribute.rb +43 -5
- data/lib/sumaki/model/enum.rb +11 -6
- data/lib/sumaki/model.rb +66 -2
- data/lib/sumaki/version.rb +1 -1
- metadata +6 -3
- data/lib/sumaki/model/association.rb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cc5d96c3a979b46ace35c6bcae70136875b415af2dbe971f45731ff69aa61ff
|
4
|
+
data.tar.gz: dbae61d4d18b0c6e3034620ea3dbe77467b4133da5df1eb45b9d89c927d98504
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a7ecf9d761bf8b6f8ff2b5236090f83105795dcb78e45d27c109a7377ead5ed323a38e51a6b3be5996bedd95917e7676e13894dd16517bac180de6ea589be6f
|
7
|
+
data.tar.gz: 1c3624fdf113f3d260e58e00f68c96f51abcdac1fe1f55e7f30ec93a3b62159b3a051618a3bfe4b9c791f66499d6160429083c73eb90d2661d97e027c74c0f16
|
data/.rubocop.yml
CHANGED
@@ -5,12 +5,12 @@ AllCops:
|
|
5
5
|
|
6
6
|
Lint/ConstantDefinitionInBlock:
|
7
7
|
Exclude:
|
8
|
-
- 'spec/sumaki/model/
|
8
|
+
- 'spec/sumaki/model/associations_spec.rb'
|
9
9
|
- 'spec/sumaki/model/attribute_spec.rb'
|
10
10
|
- 'spec/sumaki/model/enum_spec.rb'
|
11
11
|
|
12
12
|
RSpec/LeakyConstantDeclaration:
|
13
13
|
Exclude:
|
14
|
-
- 'spec/sumaki/model/
|
14
|
+
- 'spec/sumaki/model/associations_spec.rb'
|
15
15
|
- 'spec/sumaki/model/attribute_spec.rb'
|
16
16
|
- 'spec/sumaki/model/enum_spec.rb'
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sumaki (0.
|
4
|
+
sumaki (0.3.0)
|
5
5
|
minenum
|
6
6
|
|
7
7
|
GEM
|
@@ -14,14 +14,14 @@ GEM
|
|
14
14
|
diff-lcs (1.5.1)
|
15
15
|
docile (1.4.0)
|
16
16
|
io-console (0.7.2)
|
17
|
-
irb (1.
|
18
|
-
rdoc
|
17
|
+
irb (1.13.1)
|
18
|
+
rdoc (>= 4.0.0)
|
19
19
|
reline (>= 0.4.2)
|
20
20
|
json (2.7.2)
|
21
21
|
language_server-protocol (3.17.0.3)
|
22
22
|
minenum (0.1.0)
|
23
23
|
parallel (1.24.0)
|
24
|
-
parser (3.3.0
|
24
|
+
parser (3.3.1.0)
|
25
25
|
ast (~> 2.4.1)
|
26
26
|
racc
|
27
27
|
psych (5.1.2)
|
@@ -31,10 +31,11 @@ GEM
|
|
31
31
|
rake (13.2.1)
|
32
32
|
rdoc (6.6.3.1)
|
33
33
|
psych (>= 4.0.0)
|
34
|
-
regexp_parser (2.9.
|
35
|
-
reline (0.5.
|
34
|
+
regexp_parser (2.9.2)
|
35
|
+
reline (0.5.7)
|
36
36
|
io-console (~> 0.5)
|
37
|
-
rexml (3.2.
|
37
|
+
rexml (3.2.8)
|
38
|
+
strscan (>= 3.0.9)
|
38
39
|
rspec (3.13.0)
|
39
40
|
rspec-core (~> 3.13.0)
|
40
41
|
rspec-expectations (~> 3.13.0)
|
@@ -44,11 +45,11 @@ GEM
|
|
44
45
|
rspec-expectations (3.13.0)
|
45
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
47
|
rspec-support (~> 3.13.0)
|
47
|
-
rspec-mocks (3.13.
|
48
|
+
rspec-mocks (3.13.1)
|
48
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
49
50
|
rspec-support (~> 3.13.0)
|
50
51
|
rspec-support (3.13.1)
|
51
|
-
rubocop (1.63.
|
52
|
+
rubocop (1.63.5)
|
52
53
|
json (~> 2.3)
|
53
54
|
language_server-protocol (>= 3.17.0)
|
54
55
|
parallel (~> 1.10)
|
@@ -59,13 +60,13 @@ GEM
|
|
59
60
|
rubocop-ast (>= 1.31.1, < 2.0)
|
60
61
|
ruby-progressbar (~> 1.7)
|
61
62
|
unicode-display_width (>= 2.4.0, < 3.0)
|
62
|
-
rubocop-ast (1.31.
|
63
|
-
parser (>= 3.3.0
|
63
|
+
rubocop-ast (1.31.3)
|
64
|
+
parser (>= 3.3.1.0)
|
64
65
|
rubocop-capybara (2.20.0)
|
65
66
|
rubocop (~> 1.41)
|
66
67
|
rubocop-factory_bot (2.25.1)
|
67
68
|
rubocop (~> 1.41)
|
68
|
-
rubocop-rspec (2.29.
|
69
|
+
rubocop-rspec (2.29.2)
|
69
70
|
rubocop (~> 1.40)
|
70
71
|
rubocop-capybara (~> 2.17)
|
71
72
|
rubocop-factory_bot (~> 2.22)
|
@@ -80,6 +81,7 @@ GEM
|
|
80
81
|
simplecov-html (0.12.3)
|
81
82
|
simplecov_json_formatter (0.1.4)
|
82
83
|
stringio (3.1.0)
|
84
|
+
strscan (3.1.0)
|
83
85
|
unicode-display_width (2.5.0)
|
84
86
|
|
85
87
|
PLATFORMS
|
data/README.md
CHANGED
@@ -93,12 +93,22 @@ class Anime
|
|
93
93
|
field :title
|
94
94
|
field :url
|
95
95
|
end
|
96
|
+
```
|
96
97
|
|
98
|
+
```ruby
|
99
|
+
# Read the field values
|
97
100
|
anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
|
98
101
|
anime.title #=> 'The Vampire Dies in No Time'
|
99
102
|
anime.url #=> 'https://sugushinu-anime.jp/'
|
100
103
|
```
|
101
104
|
|
105
|
+
```ruby
|
106
|
+
# Write the field value
|
107
|
+
anime = Anime.new({})
|
108
|
+
anime.title = 'The Vampire Dies in No Time'
|
109
|
+
anime.title #=> 'The Vampire Dies in No Time'
|
110
|
+
```
|
111
|
+
|
102
112
|
If the data contains attributes not declared in the field, it raises no error and is simply ignored.
|
103
113
|
|
104
114
|
### Access to the sub object
|
@@ -114,23 +124,31 @@ class Book
|
|
114
124
|
class Company
|
115
125
|
include Sumaki::Model
|
116
126
|
field :name
|
117
|
-
field :prefecture
|
118
127
|
end
|
119
128
|
end
|
129
|
+
```
|
120
130
|
|
131
|
+
```ruby
|
121
132
|
data = {
|
122
133
|
title: 'The Ronaldo Chronicles',
|
123
134
|
company: {
|
124
135
|
name: 'Autumn Books',
|
125
|
-
prefecture: 'Tokyo'
|
126
136
|
}
|
127
137
|
}
|
128
138
|
|
129
|
-
|
130
|
-
|
139
|
+
# Read from the sub object
|
140
|
+
book = Book.new(data)
|
141
|
+
book.company.name #=> 'Autumn Books'
|
142
|
+
```
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Build a sub object
|
146
|
+
book = Book.new({})
|
147
|
+
book.build_company(name: 'Autumn Books')
|
148
|
+
book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
|
131
149
|
```
|
132
150
|
|
133
|
-
|
151
|
+
Sub object is wrapped with the class inferred from the field name under the original class.
|
134
152
|
|
135
153
|
This can be changed by specifying the class to wrap.
|
136
154
|
|
@@ -172,7 +190,9 @@ class Company
|
|
172
190
|
field :name
|
173
191
|
end
|
174
192
|
end
|
193
|
+
```
|
175
194
|
|
195
|
+
```ruby
|
176
196
|
data = {
|
177
197
|
name: 'The Ronaldo Vampire Hunter Agency',
|
178
198
|
member: [
|
@@ -182,11 +202,19 @@ data = {
|
|
182
202
|
]
|
183
203
|
}
|
184
204
|
|
205
|
+
# Read from the sub object
|
185
206
|
company = Company.new(data)
|
186
207
|
company.member.size #=> 3
|
187
208
|
company.member[2].name #=> 'John'
|
188
209
|
```
|
189
210
|
|
211
|
+
```ruby
|
212
|
+
# Build a sub object
|
213
|
+
company = Company.new({})
|
214
|
+
company.member.build(name: 'John')
|
215
|
+
company.member[0].name #=> 'John'
|
216
|
+
```
|
217
|
+
|
190
218
|
The `class_name` option can also be used to specify the class to wrap.
|
191
219
|
|
192
220
|
### Access to the parent object
|
@@ -227,14 +255,25 @@ class Character
|
|
227
255
|
field :name
|
228
256
|
enum :type, { vampire: 1, vampire_hunter: 2, familier: 3, editor: 4 }
|
229
257
|
end
|
258
|
+
```
|
230
259
|
|
260
|
+
```ruby
|
231
261
|
data = {
|
232
262
|
name: 'John',
|
233
263
|
type: 3
|
234
264
|
}
|
235
265
|
|
266
|
+
# Read the enum
|
236
267
|
character = Character.new(data)
|
237
|
-
character.type #=> :familier
|
268
|
+
character.type.name #=> :familier
|
269
|
+
character.type.familier? #=> true
|
270
|
+
```
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
# Write the enum value
|
274
|
+
character = Character.new({})
|
275
|
+
character.type = 1
|
276
|
+
character.type.name #=> :vampire
|
238
277
|
```
|
239
278
|
|
240
279
|
|
data/lib/sumaki/adapter/hash.rb
CHANGED
@@ -7,6 +7,22 @@ module Sumaki
|
|
7
7
|
def get(data, key)
|
8
8
|
data[key]
|
9
9
|
end
|
10
|
+
|
11
|
+
def set(data, key, value)
|
12
|
+
data[key] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_singular(data, name)
|
16
|
+
data[name] = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_repeated_element(_data, _name)
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply_repeated(data, name, objects)
|
24
|
+
data[name] = objects
|
25
|
+
end
|
10
26
|
end
|
11
27
|
end
|
12
28
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sumaki
|
4
|
+
module Model
|
5
|
+
module Associations
|
6
|
+
module Association
|
7
|
+
class Singular # :nodoc:
|
8
|
+
def initialize(owner, reflection)
|
9
|
+
@owner = owner
|
10
|
+
@reflection = reflection
|
11
|
+
end
|
12
|
+
|
13
|
+
def model
|
14
|
+
@model ||= begin
|
15
|
+
object = @owner.get(@reflection.name)
|
16
|
+
object.nil? ? nil : @reflection.model_class.new(object, parent: @owner)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_model(attrs = {})
|
21
|
+
assoc = @owner.object_accessor.build_singular(@reflection.name)
|
22
|
+
|
23
|
+
model = @reflection.model_class.new(assoc, parent: @owner)
|
24
|
+
model.assign(attrs)
|
25
|
+
|
26
|
+
@model = model
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Repeated # :nodoc:
|
31
|
+
def initialize(owner, reflection)
|
32
|
+
@owner = owner
|
33
|
+
@reflection = reflection
|
34
|
+
end
|
35
|
+
|
36
|
+
def collection
|
37
|
+
@collection ||= begin
|
38
|
+
objects_or_value = @owner.get(@reflection.name)
|
39
|
+
models = if objects_or_value.is_a?(Array)
|
40
|
+
model_class = @reflection.model_class
|
41
|
+
objects_or_value.map { |object| model_class.new(object, parent: @owner) }
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
|
46
|
+
@reflection.collection_class.new(models, owner: @owner)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Sumaki
|
6
|
+
module Model
|
7
|
+
module Associations
|
8
|
+
class Collection # :nodoc:
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
singleton_class.attr_accessor :reflection
|
13
|
+
|
14
|
+
def_delegators :@models, :each, :[]
|
15
|
+
def_delegators :@models, :inspect, :pretty_print
|
16
|
+
def_delegators 'self.class', :reflection
|
17
|
+
|
18
|
+
def self.build_subclass(reflection)
|
19
|
+
subclass = Class.new(self)
|
20
|
+
subclass.reflection = reflection
|
21
|
+
subclass
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(models = [], owner:)
|
25
|
+
@models = models
|
26
|
+
@owner = owner
|
27
|
+
end
|
28
|
+
|
29
|
+
def build(attrs = {})
|
30
|
+
object = @owner.object_accessor.build_repeated_element(reflection.name)
|
31
|
+
model = reflection.model_class.new(object, parent: @owner)
|
32
|
+
model.assign(attrs)
|
33
|
+
|
34
|
+
self << model
|
35
|
+
|
36
|
+
model
|
37
|
+
end
|
38
|
+
|
39
|
+
def <<(...)
|
40
|
+
r = @models.<<(...)
|
41
|
+
apply
|
42
|
+
r
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def apply
|
48
|
+
@owner.object_accessor.apply_repeated(reflection.name, @models)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'collection'
|
4
|
+
|
5
|
+
module Sumaki
|
6
|
+
module Model
|
7
|
+
module Associations
|
8
|
+
module Reflection
|
9
|
+
class Base # :nodoc:
|
10
|
+
def initialize(owner_class, name, class_name: nil)
|
11
|
+
@owner_class = owner_class
|
12
|
+
@name = name
|
13
|
+
@class_name = class_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def name = @name.to_sym
|
17
|
+
|
18
|
+
def model_class
|
19
|
+
@model_class ||= begin
|
20
|
+
basename = @class_name&.to_s || classify(@name.to_s)
|
21
|
+
klass = if @owner_class.const_defined?(basename)
|
22
|
+
@owner_class.const_get(basename)
|
23
|
+
else
|
24
|
+
@owner_class.const_set(basename, Class.new { include Model })
|
25
|
+
end
|
26
|
+
klass.parent = @owner_class
|
27
|
+
klass
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def association_for(model)
|
32
|
+
self.class.association_class.new(model, self)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def classify(str)
|
38
|
+
str.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Singular < Base # :nodoc:
|
43
|
+
def self.association_class = Association::Singular
|
44
|
+
end
|
45
|
+
|
46
|
+
class Repeated < Base # :nodoc:
|
47
|
+
def self.association_class = Association::Repeated
|
48
|
+
|
49
|
+
def collection_class
|
50
|
+
@collection_class ||= begin
|
51
|
+
class_name = "#{model_class.name[/(\w+)$/]}Collection"
|
52
|
+
|
53
|
+
if @owner_class.const_defined?(class_name)
|
54
|
+
@owner_class.const_get(class_name)
|
55
|
+
else
|
56
|
+
@owner_class.const_set(class_name, Collection.build_subclass(self))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'associations/reflection'
|
4
|
+
require_relative 'associations/association'
|
5
|
+
|
6
|
+
module Sumaki
|
7
|
+
module Model
|
8
|
+
# = Sumaki::Model::Associations
|
9
|
+
module Associations
|
10
|
+
def self.included(base)
|
11
|
+
base.extend ClassMethods
|
12
|
+
base.include InstanceMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module AccessorAdder
|
16
|
+
module Singular # :nodoc:
|
17
|
+
def add(model_class, methods_module, reflection)
|
18
|
+
add_getter(methods_module, reflection.name)
|
19
|
+
add_builder(methods_module, reflection.name)
|
20
|
+
|
21
|
+
model_class.reflections[reflection.name] = reflection
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def add_getter(methods_module, name)
|
27
|
+
methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
28
|
+
def #{name} # def author
|
29
|
+
association(:#{name}).model # association(:author).model
|
30
|
+
end # end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_builder(methods_module, name)
|
35
|
+
methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
36
|
+
def build_#{name}(attrs = {}) # def build_author(attrs = {})
|
37
|
+
association(:#{name}).build_model(attrs) # association(:author).build_model(attrs)
|
38
|
+
end # end
|
39
|
+
RUBY
|
40
|
+
end
|
41
|
+
|
42
|
+
module_function :add, :add_getter, :add_builder
|
43
|
+
end
|
44
|
+
|
45
|
+
module Repeated # :nodoc:
|
46
|
+
def add(model_class, methods_module, reflection)
|
47
|
+
methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
48
|
+
def #{reflection.name} # def book
|
49
|
+
association(:#{reflection.name}).collection # association(:book).collection
|
50
|
+
end # end
|
51
|
+
RUBY
|
52
|
+
|
53
|
+
model_class.reflections[reflection.name] = reflection
|
54
|
+
end
|
55
|
+
module_function :add
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods # :nodoc:
|
60
|
+
# Access to the sub object.
|
61
|
+
#
|
62
|
+
# class Book
|
63
|
+
# include Sumaki::Model
|
64
|
+
# singular :company
|
65
|
+
#
|
66
|
+
# class Company
|
67
|
+
# include Sumaki::Model
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# data = {
|
72
|
+
# title: 'The Ronaldo Chronicles',
|
73
|
+
# company: {
|
74
|
+
# name: 'Autumn Books',
|
75
|
+
# }
|
76
|
+
# }
|
77
|
+
# book = Book.new(data)
|
78
|
+
# book.company.class #=> Book::Company
|
79
|
+
#
|
80
|
+
# Sub object can also be created.
|
81
|
+
#
|
82
|
+
# book = Book.new({})
|
83
|
+
# book.build_company(name: 'Autumn Books')
|
84
|
+
# book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
|
85
|
+
#
|
86
|
+
# == Options
|
87
|
+
#
|
88
|
+
# [:class_name]
|
89
|
+
# Specify the name of the class to wrap. Use this if the name of the class
|
90
|
+
# to wrap is not inferred from the nested field names.
|
91
|
+
def singular(name, class_name: nil)
|
92
|
+
reflection = Reflection::Singular.new(self, name, class_name: class_name)
|
93
|
+
AccessorAdder::Singular.add(self, association_methods_module, reflection)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Access to the repeated sub objects
|
97
|
+
#
|
98
|
+
# class Company
|
99
|
+
# include Sumaki::Model
|
100
|
+
# repeated :member
|
101
|
+
#
|
102
|
+
# class Member
|
103
|
+
# include Sumaki::Model
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# data = {
|
108
|
+
# name: 'The Ronaldo Vampire Hunter Agency',
|
109
|
+
# member: [
|
110
|
+
# { name: 'Ronaldo' },
|
111
|
+
# { name: 'Draluc' },
|
112
|
+
# { name: 'John' }
|
113
|
+
# ]
|
114
|
+
# }
|
115
|
+
# company = Company.new(data)
|
116
|
+
# company.member[2].class #=> Company::Member
|
117
|
+
#
|
118
|
+
# Sub object can also be created.
|
119
|
+
#
|
120
|
+
# company = Company.new({})
|
121
|
+
# company.member.build(name: 'John')
|
122
|
+
# company.member[0].name #=> 'John'
|
123
|
+
#
|
124
|
+
# == Options
|
125
|
+
#
|
126
|
+
# [:class_name]
|
127
|
+
# Specify the name of the class to wrap. Use this if the name of the class
|
128
|
+
# to wrap is not inferred from the nested field names.
|
129
|
+
def repeated(name, class_name: nil)
|
130
|
+
reflection = Reflection::Repeated.new(self, name, class_name: class_name)
|
131
|
+
AccessorAdder::Repeated.add(self, association_methods_module, reflection)
|
132
|
+
end
|
133
|
+
|
134
|
+
def reflections
|
135
|
+
@reflections ||= {}
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def association_methods_module
|
141
|
+
@association_methods_module ||= begin
|
142
|
+
mod = Module.new
|
143
|
+
include mod
|
144
|
+
mod
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module InstanceMethods # :nodoc:
|
150
|
+
private
|
151
|
+
|
152
|
+
def association(name)
|
153
|
+
@associations ||= {}
|
154
|
+
@associations[name.to_sym] ||= self.class.reflections[name.to_sym].association_for(self)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -6,6 +6,31 @@ module Sumaki
|
|
6
6
|
module Attribute
|
7
7
|
def self.included(base)
|
8
8
|
base.extend ClassMethods
|
9
|
+
base.include InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module AccessorAdder # :nodoc:
|
13
|
+
def add(methods_module, field_name)
|
14
|
+
add_getter(methods_module, field_name)
|
15
|
+
add_setter(methods_module, field_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_getter(methods_module, field_name)
|
19
|
+
methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
20
|
+
def #{field_name} # def title
|
21
|
+
get(:'#{field_name}') # get(:'title')
|
22
|
+
end # end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_setter(methods_module, field_name)
|
27
|
+
methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
28
|
+
def #{field_name}=(value) # def title=(value)
|
29
|
+
set(:'#{field_name}', value) # set(:'title', value)
|
30
|
+
end # end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
module_function :add, :add_getter, :add_setter
|
9
34
|
end
|
10
35
|
|
11
36
|
module ClassMethods # :nodoc:
|
@@ -20,12 +45,19 @@ module Sumaki
|
|
20
45
|
# anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
|
21
46
|
# anime.title #=> 'The Vampire Dies in No Time'
|
22
47
|
# anime.url #=> 'https://sugushinu-anime.jp/'
|
48
|
+
#
|
49
|
+
# The Field value cam be set.
|
50
|
+
#
|
51
|
+
# anime = Anime.new({})
|
52
|
+
# anime.title = 'The Vampire Dies in No Time'
|
53
|
+
# anime.title #=> 'The Vampire Dies in No Time'
|
23
54
|
def field(name)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
55
|
+
field_names << name.to_sym
|
56
|
+
AccessorAdder.add(attribute_methods_module, name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def field_names
|
60
|
+
@field_names ||= []
|
29
61
|
end
|
30
62
|
|
31
63
|
private
|
@@ -38,6 +70,12 @@ module Sumaki
|
|
38
70
|
end
|
39
71
|
end
|
40
72
|
end
|
73
|
+
|
74
|
+
module InstanceMethods # :nodoc:
|
75
|
+
def fields
|
76
|
+
self.class.field_names.map.with_object({}) { |e, r| r[e] = public_send(e) }
|
77
|
+
end
|
78
|
+
end
|
41
79
|
end
|
42
80
|
end
|
43
81
|
end
|
data/lib/sumaki/model/enum.rb
CHANGED
@@ -15,7 +15,11 @@ module Sumaki
|
|
15
15
|
def get(model, name)
|
16
16
|
model.get(name)
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
|
+
def set(model, name, value)
|
20
|
+
model.set(name, value)
|
21
|
+
end
|
22
|
+
module_function :get, :set
|
19
23
|
end
|
20
24
|
|
21
25
|
module ClassMethods # :nodoc:
|
@@ -36,6 +40,12 @@ module Sumaki
|
|
36
40
|
# character.type.name #=> :familier
|
37
41
|
# character.type.familier? #=> true
|
38
42
|
# character.type.vampire? #=> false
|
43
|
+
#
|
44
|
+
# Enum can also be set.
|
45
|
+
#
|
46
|
+
# character = Character.new({})
|
47
|
+
# character.type = 1
|
48
|
+
# character.type.name #=> :vampire
|
39
49
|
def enum(name, values)
|
40
50
|
super(name, values, adapter: EnumAttrAccessor)
|
41
51
|
end
|
@@ -49,11 +59,6 @@ module Sumaki
|
|
49
59
|
mod
|
50
60
|
end
|
51
61
|
end
|
52
|
-
|
53
|
-
# TODO: remove this
|
54
|
-
def classify(key)
|
55
|
-
key.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
|
56
|
-
end
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|
data/lib/sumaki/model.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'model/attribute'
|
4
|
-
require_relative 'model/
|
4
|
+
require_relative 'model/associations'
|
5
5
|
require_relative 'model/enum'
|
6
6
|
|
7
7
|
module Sumaki
|
@@ -164,7 +164,7 @@ module Sumaki
|
|
164
164
|
base.include InstanceMethods
|
165
165
|
|
166
166
|
base.include Attribute
|
167
|
-
base.include
|
167
|
+
base.include Associations
|
168
168
|
base.include Enum
|
169
169
|
end
|
170
170
|
|
@@ -177,6 +177,33 @@ module Sumaki
|
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
180
|
+
class ObjectAccessor # :nodoc:
|
181
|
+
def initialize(object, adapter)
|
182
|
+
@object = object
|
183
|
+
@adapter = adapter
|
184
|
+
end
|
185
|
+
|
186
|
+
def get(name)
|
187
|
+
@adapter.get(@object, name)
|
188
|
+
end
|
189
|
+
|
190
|
+
def set(name, value)
|
191
|
+
@adapter.set(@object, name, value)
|
192
|
+
end
|
193
|
+
|
194
|
+
def build_singular(name)
|
195
|
+
@adapter.build_singular(@object, name)
|
196
|
+
end
|
197
|
+
|
198
|
+
def build_repeated_element(name)
|
199
|
+
@adapter.build_repeated_element(@object, name)
|
200
|
+
end
|
201
|
+
|
202
|
+
def apply_repeated(name, models)
|
203
|
+
@adapter.apply_repeated(@object, name, models.map(&:object))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
180
207
|
module InstanceMethods # :nodoc:
|
181
208
|
attr_reader :object, :parent
|
182
209
|
|
@@ -184,6 +211,43 @@ module Sumaki
|
|
184
211
|
@object = object
|
185
212
|
@parent = parent
|
186
213
|
end
|
214
|
+
|
215
|
+
def object_accessor
|
216
|
+
@object_accessor ||= ObjectAccessor.new(object, self.class.adapter)
|
217
|
+
end
|
218
|
+
|
219
|
+
def get(name)
|
220
|
+
object_accessor.get(name)
|
221
|
+
end
|
222
|
+
|
223
|
+
def set(name, value)
|
224
|
+
object_accessor.set(name, value)
|
225
|
+
end
|
226
|
+
|
227
|
+
def assign(attrs)
|
228
|
+
attrs.each do |attr, value|
|
229
|
+
public_send(:"#{attr}=", value)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def inspect
|
234
|
+
inspection = fields
|
235
|
+
.map { |name, value| "#{name}: #{value.inspect}" }
|
236
|
+
.join(', ')
|
237
|
+
"#<#{self.class.name} #{inspection}>"
|
238
|
+
end
|
239
|
+
|
240
|
+
def pretty_print(pp)
|
241
|
+
pp.object_address_group(self) do
|
242
|
+
pp.seplist(fields) do |field, value|
|
243
|
+
pp.breakable
|
244
|
+
pp.group(1) do
|
245
|
+
pp.text "#{field}: "
|
246
|
+
pp.pp value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
187
251
|
end
|
188
252
|
end
|
189
253
|
end
|
data/lib/sumaki/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sumaki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Loose Coupling
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minenum
|
@@ -48,7 +48,10 @@ files:
|
|
48
48
|
- lib/sumaki/adapter/hash.rb
|
49
49
|
- lib/sumaki/config.rb
|
50
50
|
- lib/sumaki/model.rb
|
51
|
-
- lib/sumaki/model/
|
51
|
+
- lib/sumaki/model/associations.rb
|
52
|
+
- lib/sumaki/model/associations/association.rb
|
53
|
+
- lib/sumaki/model/associations/collection.rb
|
54
|
+
- lib/sumaki/model/associations/reflection.rb
|
52
55
|
- lib/sumaki/model/attribute.rb
|
53
56
|
- lib/sumaki/model/enum.rb
|
54
57
|
- lib/sumaki/version.rb
|
@@ -1,116 +0,0 @@
|
|
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
|
-
def get(name)
|
111
|
-
self.class.adapter.get(object, name)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|