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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.overcommit.yml +37 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +56 -0
  6. data/.rubocop_todo.yml +26 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +5 -0
  10. data/CHANGELOG.md +17 -0
  11. data/CODE_OF_CONDUCT.md +74 -0
  12. data/Gemfile +8 -0
  13. data/Gemfile.lock +92 -0
  14. data/Guardfile +30 -0
  15. data/LICENSE.txt +21 -0
  16. data/README.md +114 -0
  17. data/Rakefile +8 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/entity_schema.gemspec +49 -0
  21. data/lib/entity_schema/class_methods.rb +28 -0
  22. data/lib/entity_schema/dsl.rb +40 -0
  23. data/lib/entity_schema/dsl_helper.rb +26 -0
  24. data/lib/entity_schema/fields/abstract.rb +89 -0
  25. data/lib/entity_schema/fields/builders/.rubocop.yml +7 -0
  26. data/lib/entity_schema/fields/builders/abstract.rb +98 -0
  27. data/lib/entity_schema/fields/builders/belongs_to.rb +91 -0
  28. data/lib/entity_schema/fields/builders/collection.rb +20 -0
  29. data/lib/entity_schema/fields/builders/fk_belongs_to.rb +21 -0
  30. data/lib/entity_schema/fields/builders/object.rb +40 -0
  31. data/lib/entity_schema/fields/builders/object_belongs_to.rb +17 -0
  32. data/lib/entity_schema/fields/builders/property.rb +33 -0
  33. data/lib/entity_schema/fields/collection.rb +35 -0
  34. data/lib/entity_schema/fields/fk_belongs_to.rb +16 -0
  35. data/lib/entity_schema/fields/object.rb +36 -0
  36. data/lib/entity_schema/fields/object_belongs_to.rb +16 -0
  37. data/lib/entity_schema/fields/observer_belongs_to.rb +33 -0
  38. data/lib/entity_schema/fields/property.rb +24 -0
  39. data/lib/entity_schema/instance_methods.rb +42 -0
  40. data/lib/entity_schema/schema.rb +77 -0
  41. data/lib/entity_schema/version.rb +5 -0
  42. data/lib/entity_schema.rb +18 -0
  43. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EntitySchema
4
+ VERSION = '0.1.0'
5
+ 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