sumaki 0.2.0 → 0.4.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 +69 -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/enum.rb +11 -6
- data/lib/sumaki/model/fields/reflection.rb +24 -0
- data/lib/sumaki/model/fields/type/boolean.rb +31 -0
- data/lib/sumaki/model/fields/type/date.rb +35 -0
- data/lib/sumaki/model/fields/type/date_time.rb +35 -0
- data/lib/sumaki/model/fields/type/float.rb +23 -0
- data/lib/sumaki/model/fields/type/integer.rb +23 -0
- data/lib/sumaki/model/fields/type/string.rb +23 -0
- data/lib/sumaki/model/fields/type/value.rb +32 -0
- data/lib/sumaki/model/fields/type.rb +50 -0
- data/lib/sumaki/model/fields.rb +137 -0
- data/lib/sumaki/model.rb +70 -4
- data/lib/sumaki/version.rb +1 -1
- metadata +16 -4
- data/lib/sumaki/model/association.rb +0 -116
- data/lib/sumaki/model/attribute.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23a3806fcf2a27686f82307a043ef96e36efc90fe73fa2f0da6bba0b1c4506dc
|
4
|
+
data.tar.gz: 3602002eaab7328a7b331add996774487687aa310aa06bdea99e84746544fca1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c5104b11ef1d3029c8f3e0e5795279a9ae82061c61d2e6b15f4f9fb62629c4a99fb362ec4818959827da4a10034b4024442092c0f0aa0fb056cdae1c1333197
|
7
|
+
data.tar.gz: f3e582ba5ccafc8a44f1ef66a48df31c2ea1b5e95ef434b958b42647218f5a593dfb13c9aa0cb899e63356313e7a42118ced4445a2a426d70438db3f5da0b70a
|
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.4.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,14 +93,48 @@ 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
|
|
114
|
+
#### Type casting
|
115
|
+
|
116
|
+
When a type is specified, it will be typecast.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Character
|
120
|
+
include Sumaki::Model
|
121
|
+
|
122
|
+
field :age, :int
|
123
|
+
end
|
124
|
+
|
125
|
+
character = Character.new({ age: '208' })
|
126
|
+
character.age #=> 208
|
127
|
+
```
|
128
|
+
|
129
|
+
Types are:
|
130
|
+
|
131
|
+
* `:int`
|
132
|
+
* `:float`
|
133
|
+
* `:string`
|
134
|
+
* `:bool`
|
135
|
+
* `:date`
|
136
|
+
* `:datetime`
|
137
|
+
|
104
138
|
### Access to the sub object
|
105
139
|
|
106
140
|
By declaring `singular`, you can access the sub object.
|
@@ -114,23 +148,31 @@ class Book
|
|
114
148
|
class Company
|
115
149
|
include Sumaki::Model
|
116
150
|
field :name
|
117
|
-
field :prefecture
|
118
151
|
end
|
119
152
|
end
|
153
|
+
```
|
120
154
|
|
155
|
+
```ruby
|
121
156
|
data = {
|
122
157
|
title: 'The Ronaldo Chronicles',
|
123
158
|
company: {
|
124
159
|
name: 'Autumn Books',
|
125
|
-
prefecture: 'Tokyo'
|
126
160
|
}
|
127
161
|
}
|
128
162
|
|
129
|
-
|
130
|
-
|
163
|
+
# Read from the sub object
|
164
|
+
book = Book.new(data)
|
165
|
+
book.company.name #=> 'Autumn Books'
|
131
166
|
```
|
132
167
|
|
133
|
-
|
168
|
+
```ruby
|
169
|
+
# Build a sub object
|
170
|
+
book = Book.new({})
|
171
|
+
book.build_company(name: 'Autumn Books')
|
172
|
+
book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
|
173
|
+
```
|
174
|
+
|
175
|
+
Sub object is wrapped with the class inferred from the field name under the original class.
|
134
176
|
|
135
177
|
This can be changed by specifying the class to wrap.
|
136
178
|
|
@@ -172,7 +214,9 @@ class Company
|
|
172
214
|
field :name
|
173
215
|
end
|
174
216
|
end
|
217
|
+
```
|
175
218
|
|
219
|
+
```ruby
|
176
220
|
data = {
|
177
221
|
name: 'The Ronaldo Vampire Hunter Agency',
|
178
222
|
member: [
|
@@ -182,11 +226,19 @@ data = {
|
|
182
226
|
]
|
183
227
|
}
|
184
228
|
|
229
|
+
# Read from the sub object
|
185
230
|
company = Company.new(data)
|
186
231
|
company.member.size #=> 3
|
187
232
|
company.member[2].name #=> 'John'
|
188
233
|
```
|
189
234
|
|
235
|
+
```ruby
|
236
|
+
# Build a sub object
|
237
|
+
company = Company.new({})
|
238
|
+
company.member.build(name: 'John')
|
239
|
+
company.member[0].name #=> 'John'
|
240
|
+
```
|
241
|
+
|
190
242
|
The `class_name` option can also be used to specify the class to wrap.
|
191
243
|
|
192
244
|
### Access to the parent object
|
@@ -227,14 +279,25 @@ class Character
|
|
227
279
|
field :name
|
228
280
|
enum :type, { vampire: 1, vampire_hunter: 2, familier: 3, editor: 4 }
|
229
281
|
end
|
282
|
+
```
|
230
283
|
|
284
|
+
```ruby
|
231
285
|
data = {
|
232
286
|
name: 'John',
|
233
287
|
type: 3
|
234
288
|
}
|
235
289
|
|
290
|
+
# Read the enum
|
236
291
|
character = Character.new(data)
|
237
|
-
character.type #=> :familier
|
292
|
+
character.type.name #=> :familier
|
293
|
+
character.type.familier? #=> true
|
294
|
+
```
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
# Write the enum value
|
298
|
+
character = Character.new({})
|
299
|
+
character.type = 1
|
300
|
+
character.type.name #=> :vampire
|
238
301
|
```
|
239
302
|
|
240
303
|
|
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
|
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'type'
|
4
|
+
|
5
|
+
module Sumaki
|
6
|
+
module Model
|
7
|
+
module Fields
|
8
|
+
class Reflection # :nodoc:
|
9
|
+
def initialize(name, type = nil)
|
10
|
+
@name = name
|
11
|
+
@type = type
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_class
|
19
|
+
@type_class ||= @type.nil? ? Type::Value : Type.lookup(@type)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'value'
|
4
|
+
|
5
|
+
module Sumaki
|
6
|
+
module Model
|
7
|
+
module Fields
|
8
|
+
module Type
|
9
|
+
class Boolean < Value # :nodoc:
|
10
|
+
def self.serialize(value)
|
11
|
+
return if value.nil?
|
12
|
+
|
13
|
+
case value
|
14
|
+
when true then true
|
15
|
+
when false then false
|
16
|
+
else
|
17
|
+
raise ArgumentError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.deserialize(value)
|
22
|
+
case value
|
23
|
+
when true then true
|
24
|
+
when false then false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|