entity_schema 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.overcommit.yml +37 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/.rubocop_todo.yml +26 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +92 -0
- data/Guardfile +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/entity_schema.gemspec +49 -0
- data/lib/entity_schema/class_methods.rb +28 -0
- data/lib/entity_schema/dsl.rb +40 -0
- data/lib/entity_schema/dsl_helper.rb +26 -0
- data/lib/entity_schema/fields/abstract.rb +89 -0
- data/lib/entity_schema/fields/builders/.rubocop.yml +7 -0
- data/lib/entity_schema/fields/builders/abstract.rb +98 -0
- data/lib/entity_schema/fields/builders/belongs_to.rb +91 -0
- data/lib/entity_schema/fields/builders/collection.rb +20 -0
- data/lib/entity_schema/fields/builders/fk_belongs_to.rb +21 -0
- data/lib/entity_schema/fields/builders/object.rb +40 -0
- data/lib/entity_schema/fields/builders/object_belongs_to.rb +17 -0
- data/lib/entity_schema/fields/builders/property.rb +33 -0
- data/lib/entity_schema/fields/collection.rb +35 -0
- data/lib/entity_schema/fields/fk_belongs_to.rb +16 -0
- data/lib/entity_schema/fields/object.rb +36 -0
- data/lib/entity_schema/fields/object_belongs_to.rb +16 -0
- data/lib/entity_schema/fields/observer_belongs_to.rb +33 -0
- data/lib/entity_schema/fields/property.rb +24 -0
- data/lib/entity_schema/instance_methods.rb +42 -0
- data/lib/entity_schema/schema.rb +77 -0
- data/lib/entity_schema/version.rb +5 -0
- data/lib/entity_schema.rb +18 -0
- metadata +227 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative 'object'
|
5
|
+
require_relative '../collection'
|
6
|
+
|
7
|
+
module EntitySchema
|
8
|
+
module Fields
|
9
|
+
module Builders
|
10
|
+
# TODO: doc
|
11
|
+
class Collection < Builders::Object
|
12
|
+
private
|
13
|
+
|
14
|
+
def field_klass
|
15
|
+
Fields::Collection
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative '../fk_belongs_to'
|
5
|
+
|
6
|
+
module EntitySchema
|
7
|
+
module Fields
|
8
|
+
module Builders
|
9
|
+
# TODO: doc
|
10
|
+
class FkBelongsTo < Abstract
|
11
|
+
def create_field_params(o, name)
|
12
|
+
super.merge!(predicate: false)
|
13
|
+
end
|
14
|
+
|
15
|
+
def field_klass
|
16
|
+
Fields::FkBelongsTo
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative '../object'
|
5
|
+
|
6
|
+
module EntitySchema
|
7
|
+
module Fields
|
8
|
+
module Builders
|
9
|
+
# TODO: doc
|
10
|
+
|
11
|
+
class Object < Abstract
|
12
|
+
private
|
13
|
+
|
14
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
15
|
+
def extract_options(h)
|
16
|
+
super.merge!(
|
17
|
+
mapper: check_ducktype!(:mapper, h, [:call]),
|
18
|
+
map_to: check!(:map_to, h, [Class]),
|
19
|
+
map_method: check!(:map_method, h, [Symbol, nil]),
|
20
|
+
serialize_method: check!(:serialize, h, [Symbol, nil]),
|
21
|
+
serializer: check_ducktype!(:serializer, h, [:call])
|
22
|
+
)
|
23
|
+
end
|
24
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
25
|
+
|
26
|
+
def create_field_params(o, name)
|
27
|
+
super.merge!(
|
28
|
+
map_to: o[:map_to],
|
29
|
+
map_method: o[:map_method] || :new,
|
30
|
+
serialize_method: o[:serialize_method] || :to_h
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def field_klass
|
35
|
+
Fields::Object
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'object'
|
4
|
+
require_relative '../object_belongs_to'
|
5
|
+
|
6
|
+
module EntitySchema
|
7
|
+
module Fields
|
8
|
+
module Builders
|
9
|
+
# TODO: doc
|
10
|
+
class ObjectBelongsTo < Builders::Object
|
11
|
+
def field_klass
|
12
|
+
Fields::ObjectBelongsTo
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative '../property'
|
5
|
+
|
6
|
+
module EntitySchema
|
7
|
+
module Fields
|
8
|
+
module Builders
|
9
|
+
# TODO: doc
|
10
|
+
class Property < Abstract
|
11
|
+
private
|
12
|
+
|
13
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
14
|
+
def extract_options(h)
|
15
|
+
super.merge!(
|
16
|
+
predicate: check!(:predicate, h, [true, false, nil])
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_field_params(o, name)
|
21
|
+
super.merge!(
|
22
|
+
predicate: to_bool(o[:predicate])
|
23
|
+
)
|
24
|
+
end
|
25
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
26
|
+
|
27
|
+
def field_klass
|
28
|
+
Fields::Property
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
module Fields
|
5
|
+
# TODO: doc
|
6
|
+
class Collection < Object
|
7
|
+
def set(obj, collection)
|
8
|
+
return super if collection.is_a?(Array) || collection.nil?
|
9
|
+
raise ArgumentError, 'collection field must be Array'
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(obj)
|
13
|
+
values = read(obj)
|
14
|
+
values&.first.is_a?(Hash) ? write(obj, map(values)) : values
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialize(obj, output)
|
18
|
+
values = read(obj)
|
19
|
+
output[src_key] = (values.first.is_a?(Hash) ? values : unwrap(values))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :serialize_method
|
25
|
+
|
26
|
+
def map(tuples)
|
27
|
+
tuples.map { |tuple| map_to.public_send(map_method, tuple) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def unwrap(objects)
|
31
|
+
objects.map { |obj| obj.public_send(serialize_method) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
module Fields
|
5
|
+
# Fk
|
6
|
+
class FkBelongsTo < Property
|
7
|
+
attr_accessor :observer_belongs_to
|
8
|
+
|
9
|
+
def set(obj, value, notify_observer: true)
|
10
|
+
super(obj, value).tap do |fk|
|
11
|
+
observer_belongs_to.fk_changed(fk, obj) if notify_observer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
module Fields
|
5
|
+
# TODO: doc
|
6
|
+
class Object < Abstract
|
7
|
+
def initialize(name, schema, options)
|
8
|
+
@map_to = options.delete(:map_to)
|
9
|
+
@map_method = options.delete(:map_method)
|
10
|
+
@serialize_method = options.delete(:serialize_method)
|
11
|
+
super(name, schema, options)
|
12
|
+
guard_unknown_options!(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(obj)
|
16
|
+
value = read(obj)
|
17
|
+
value.is_a?(Hash) ? write(obj, map(value)) : value
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize(obj, output)
|
21
|
+
return unless given?(obj)
|
22
|
+
value = read(obj)
|
23
|
+
return output[src_key] = value if value.is_a?(Hash)
|
24
|
+
output[src_key] = value.public_send(serialize_method)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :map_to, :map_method, :serialize_method
|
30
|
+
|
31
|
+
def map(tuple)
|
32
|
+
map_to.public_send(map_method, tuple)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
module Fields
|
5
|
+
# Withoutfk
|
6
|
+
class ObjectBelongsTo < Object
|
7
|
+
attr_accessor :observer_belongs_to
|
8
|
+
|
9
|
+
def set(obj, value, notify_observer: true)
|
10
|
+
super(obj, value).tap do |new_object|
|
11
|
+
observer_belongs_to.object_changed(new_object, obj) if notify_observer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
module Fields
|
5
|
+
# Doc
|
6
|
+
class ObserverBelongsTo
|
7
|
+
attr_reader :fk_field, :object_field, :object_pk
|
8
|
+
|
9
|
+
def initialize(fk_belongs_to, object_belongs_to, object_pk:)
|
10
|
+
@fk_field = fk_belongs_to
|
11
|
+
@object_field = object_belongs_to
|
12
|
+
@object_pk = object_pk
|
13
|
+
end
|
14
|
+
|
15
|
+
def fk_changed(new_fk, obj)
|
16
|
+
object = object_field.get(obj)
|
17
|
+
return if object.nil?
|
18
|
+
old_fk = object.public_send(object_pk)
|
19
|
+
object_field.set(obj, nil) if new_fk != old_fk
|
20
|
+
end
|
21
|
+
|
22
|
+
def object_changed(new_object, obj)
|
23
|
+
new_pk =
|
24
|
+
case new_object
|
25
|
+
when Hash then new_object[object_pk]
|
26
|
+
when nil then nil
|
27
|
+
else new_object.public_send(object_pk)
|
28
|
+
end
|
29
|
+
fk_field.set(obj, new_pk, notify_observer: false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
|
5
|
+
module EntitySchema
|
6
|
+
module Fields
|
7
|
+
# TODO: doc
|
8
|
+
class Property < Abstract
|
9
|
+
def initialize(name, schema, options)
|
10
|
+
@predicate = options.delete(:predicate)
|
11
|
+
super(name, schema, options)
|
12
|
+
guard_unknown_options!(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def predicate?
|
16
|
+
@predicate
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(obj)
|
20
|
+
read(obj)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
# TODO: doc
|
5
|
+
module InstanceMethods
|
6
|
+
def initialize(params = EMPTY_HASH)
|
7
|
+
update_attributes(params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def update_attributes(params)
|
11
|
+
self.class.entity_schema.set_from_params(self, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(name, value)
|
15
|
+
self.class.entity_schema.public_set(self, name, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias []= set
|
19
|
+
|
20
|
+
def get(name)
|
21
|
+
self.class.entity_schema.public_get(self, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias [] get
|
25
|
+
|
26
|
+
def field?(name)
|
27
|
+
self.class.entity_schema.field?(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def given?(name)
|
31
|
+
self.class.entity_schema.given?(self, name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def key?(name)
|
35
|
+
self.class.entity_schema.weak_given?(self, name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
self.class.entity_schema.serialize(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EntitySchema
|
4
|
+
# aggregate of field resolvers
|
5
|
+
class Schema
|
6
|
+
attr_reader :owner_name
|
7
|
+
|
8
|
+
def initialize(owner_name:)
|
9
|
+
@owner_name = owner_name
|
10
|
+
@fields = {}
|
11
|
+
@fields_by_key = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def extends(other)
|
15
|
+
raise 'Cant extend not empty Schema' if fields.any?
|
16
|
+
@fields.merge! other.fields
|
17
|
+
@fields_by_key.merge! other.fields_by_key
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_field(field)
|
22
|
+
fields[field.name] = field
|
23
|
+
fields_by_key[field.src_key] = field
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_from_params(obj, params)
|
27
|
+
params.each { |key, value| fields_by_key[key]&.set(obj, value) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def public_set(obj, name, value)
|
31
|
+
guard_unknown_field!(name)
|
32
|
+
fields[name].public_set(obj, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public_get(obj, name)
|
36
|
+
guard_unknown_field!(name)
|
37
|
+
fields[name].public_get(obj)
|
38
|
+
end
|
39
|
+
|
40
|
+
def serialize(obj)
|
41
|
+
fields.values.each_with_object({}) { |field, output| field.serialize(obj, output) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def field?(name)
|
45
|
+
fields.key?(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def given?(obj, name)
|
49
|
+
guard_unknown_field!(name)
|
50
|
+
fields[name].given?(obj)
|
51
|
+
end
|
52
|
+
|
53
|
+
def weak_given?(obj, name)
|
54
|
+
fields[name]&.given?(obj)
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: use it
|
58
|
+
def deep_freeze
|
59
|
+
fields.each do |name, field|
|
60
|
+
name.freeze
|
61
|
+
field.freeze
|
62
|
+
end.freeze
|
63
|
+
freeze
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
attr_reader :fields, :fields_by_key
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def guard_unknown_field!(name)
|
73
|
+
return if field?(name)
|
74
|
+
raise NameError, "Unknown field '#{name}' for `#{owner_name}`"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'entity_schema/version'
|
4
|
+
|
5
|
+
require 'entity_schema/class_methods'
|
6
|
+
require 'entity_schema/dsl'
|
7
|
+
require 'entity_schema/instance_methods'
|
8
|
+
|
9
|
+
module EntitySchema
|
10
|
+
Undefined = :undefined
|
11
|
+
EMPTY_HASH = {}.freeze # TODO: EMPTY_HASH from Dry Core
|
12
|
+
|
13
|
+
def self.extended(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
base.extend Dsl
|
16
|
+
base.include InstanceMethods
|
17
|
+
end
|
18
|
+
end
|