schema-model 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3dbf6656e98c4b1fb37ef7efcc4495c3fc56f39dcfd87c3e38f5caf4b3607670
4
+ data.tar.gz: 32583217eae45307d91662e063e447a8dcd7dbd709c60fa6757bb12a428ebfc7
5
+ SHA512:
6
+ metadata.gz: b23a4e93ca896b22d5bba542b5e530046cd74e0b4cab5e8b11c217efadb1f20bc87a6111bc048c7f8b62905838eee8060a27fc8847f2b4c2533b3321eb68fa7b
7
+ data.tar.gz: 0dc15eefe3675480d601ea8f4d04282598b6bcdd52d6b0d558d95978b1a70bfb7445c0b0c125ccdbc424806fde06c7d8dea6e3297a4ecfc55d35786425333695
@@ -0,0 +1,25 @@
1
+ module Schema
2
+ class Errors
3
+ attr_reader :errors
4
+
5
+ EMPTY_ARRAY = [].freeze
6
+
7
+ def initialize
8
+ @errors = {}
9
+ end
10
+
11
+ def [](name)
12
+ @errors[name] || EMPTY_ARRAY
13
+ end
14
+
15
+ def add(name, error)
16
+ @errors[name] ||= []
17
+ @errors[name] << error
18
+ end
19
+ alias []= add
20
+
21
+ def empty?
22
+ @errors.empty?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,119 @@
1
+ require 'inheritance-helper'
2
+
3
+ module Schema
4
+ module Model
5
+ def self.included base
6
+ base.extend InheritanceHelper::Methods
7
+ base.include Schema::Parsers::Common
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ def self.default_attribute_options(name, type)
12
+ {
13
+ key: name.to_s.freeze,
14
+ name: name,
15
+ type: type,
16
+ getter: name.to_s.freeze,
17
+ setter: "#{name}=".freeze,
18
+ instance_variable: "@#{name}".freeze,
19
+ }
20
+ end
21
+
22
+ module ClassMethods
23
+ def schema
24
+ {}.freeze
25
+ end
26
+
27
+ def schema_config
28
+ {
29
+ schema_includes: []
30
+ }.freeze
31
+ end
32
+
33
+ def attribute(name, type, options={})
34
+ if options.has_key?(:alias)
35
+ options[:aliases] = [options[:alias]]
36
+ end
37
+
38
+ options = ::Schema::Model.default_attribute_options(name, type)
39
+ .merge(
40
+ parser: "parse_#{type}".freeze
41
+ ).merge(options)
42
+
43
+ add_value_to_class_method(:schema, name => options)
44
+
45
+ class_eval(<<-STR
46
+ def #{options[:getter]}
47
+ #{options[:instance_variable]}
48
+ end
49
+
50
+ def #{options[:setter]}(v)
51
+ #{options[:instance_variable]} = #{options[:parser]}(#{name.inspect}, parsing_errors, v)
52
+ end
53
+ STR
54
+ )
55
+
56
+ if options[:aliases]
57
+ options[:aliases].each do |alias_name|
58
+ add_value_to_class_method(:schema, alias_name.to_sym => options.merge(key: alias_name.to_s, alias_of: name))
59
+ alias_method(alias_name, options[:getter])
60
+ alias_method("#{alias_name}=", options[:setter])
61
+ end
62
+ end
63
+ end
64
+
65
+ def from_hash(data)
66
+ new.update_attributes(data)
67
+ end
68
+
69
+ def schema_include(mod)
70
+ config = schema_config.dup
71
+ config[:schema_includes] = config[:schema_includes] + [mod]
72
+ redefine_class_method(:schema_config, config.freeze)
73
+ include mod
74
+ end
75
+
76
+ def schema_base_class=(kls)
77
+ config = schema_config.dup
78
+ config[:schema_base_class] = kls
79
+ redefine_class_method(:schema_config, config.freeze)
80
+ end
81
+
82
+ def set_schema_base_class_to_superclass
83
+ self.schema_base_class = superclass
84
+ end
85
+ end
86
+
87
+ def update_attributes(data)
88
+ self.class.schema.each do |field_name, field_options|
89
+ next if ! data.has_key?(field_options[:key]) && ! data.has_key?(field_name)
90
+
91
+ public_send(
92
+ field_options[:setter],
93
+ data[field_options[:key]] || data[field_name.to_sym]
94
+ )
95
+ end
96
+
97
+ self
98
+ end
99
+
100
+ def as_json(opts={})
101
+ self.class.schema.inject({}) do |memo, (field_name, field_options)|
102
+ unless field_options[:alias_of]
103
+ value = public_send(field_options[:getter])
104
+ memo[field_name] = value if ! value.nil? || opts[:include_nils]
105
+ end
106
+ memo
107
+ end
108
+ end
109
+
110
+ def to_hash
111
+ as_json(include_nils: true)
112
+ end
113
+ alias to_h to_hash
114
+
115
+ def parsing_errors
116
+ @parsing_errors ||= ::Schema::Errors.new
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,129 @@
1
+ require 'time'
2
+
3
+ module Schema
4
+ module Parsers
5
+ module Common
6
+ INTEGER_REGEX = /^(?:[1-9]\d*|0)$/
7
+ FLOAT_REGEX = /^(?:[1-9]\d*|0)(?:\.\d+)?$/
8
+ BOOLEAN_REGEX = /^(?:1|t|true|on|y|yes)$/i
9
+
10
+ def parse_integer(field_name, parsing_errors, value)
11
+ case value
12
+ when Integer
13
+ value
14
+ when String
15
+ if INTEGER_REGEX.match?(value)
16
+ Integer(value)
17
+ else
18
+ parsing_errors.add(field_name, :invalid)
19
+ nil
20
+ end
21
+ when Float
22
+ if (value % 1) > 0.0
23
+ parsing_errors.add(field_name, :incompatable)
24
+ end
25
+ value.to_i
26
+ when nil
27
+ nil
28
+ else
29
+ parsing_errors.add(field_name, :unhandled_type)
30
+ nil
31
+ end
32
+ end
33
+
34
+ def parse_string(field_name, parsing_errors, value)
35
+ case value
36
+ when String
37
+ value
38
+ when Hash, Array
39
+ parsing_errors.add(field_name, :incompatable)
40
+ nil
41
+ when nil
42
+ nil
43
+ else
44
+ String(value)
45
+ end
46
+ end
47
+
48
+ def parse_float(field_name, parsing_errors, value)
49
+ case value
50
+ when Float
51
+ value
52
+ when Integer
53
+ value.to_f
54
+ when String
55
+ if FLOAT_REGEX.match?(value)
56
+ Float(value)
57
+ else
58
+ parsing_errors.add(field_name, :invalid)
59
+ nil
60
+ end
61
+ when nil
62
+ nil
63
+ else
64
+ parsing_errors.add(field_name, :unhandled_type)
65
+ nil
66
+ end
67
+ end
68
+
69
+ def parse_time(field_name, parsing_errors, value)
70
+ case value
71
+ when Time
72
+ value
73
+ when Date
74
+ value.to_time
75
+ when String
76
+ begin
77
+ Time.xmlschema(value)
78
+ rescue ArgumentError
79
+ parsing_errors.add(field_name, :invalid)
80
+ nil
81
+ end
82
+ when nil
83
+ nil
84
+ else
85
+ parsing_errors.add(field_name, :unhandled_type)
86
+ nil
87
+ end
88
+ end
89
+
90
+ def parse_date(field_name, parsing_errors, value)
91
+ case value
92
+ when Date
93
+ value
94
+ when Time
95
+ value.to_date
96
+ when String
97
+ begin
98
+ Date.parse(value)
99
+ rescue ArgumentError
100
+ parsing_errors.add(field_name, :invalid)
101
+ nil
102
+ end
103
+ when nil
104
+ nil
105
+ else
106
+ parsing_errors.add(field_name, :unhandled_type)
107
+ nil
108
+ end
109
+ end
110
+
111
+ def parse_boolean(field_name, parsing_errors, value)
112
+ case value
113
+ when TrueClass, FalseClass
114
+ value
115
+ when Integer, Float
116
+ value != 0
117
+ when String
118
+ BOOLEAN_REGEX.match?(value)
119
+ when nil
120
+ nil
121
+ else
122
+ parsing_errors.add(field_name, :unhandled_type)
123
+ nil
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
@@ -0,0 +1,37 @@
1
+ module Schema
2
+ module Relation
3
+ module HasMany
4
+ def self.included base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def has_many(name, options={}, &block)
10
+ _, class_name = ::Schema::Utils.create_schema_class(self, 'SchemaHasMany', name, &block)
11
+
12
+ options = ::Schema::Model.default_attribute_options(name, :has_many)
13
+ .merge(
14
+ class_name: class_name
15
+ ).merge(options)
16
+
17
+ add_value_to_class_method(:schema, name => options)
18
+
19
+ class_eval(<<-STR
20
+ def #{options[:getter]}
21
+ #{options[:instance_variable]}
22
+ end
23
+
24
+ def #{options[:setter]}(v)
25
+ if schemas = ::Schema::Utils.create_schemas(self, #{options[:class_name]}, #{name.inspect}, v)
26
+ #{options[:instance_variable]} = schemas
27
+ end
28
+ end
29
+ STR
30
+ )
31
+
32
+ const_get(class_name)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Schema
2
+ module Relation
3
+ module HasOne
4
+ def self.included base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def has_one(name, options={}, &block)
10
+ _, class_name = ::Schema::Utils.create_schema_class(self, 'SchemaHasOne', name, &block)
11
+
12
+ options = ::Schema::Model.default_attribute_options(name, :has_one)
13
+ .merge(
14
+ class_name: class_name
15
+ ).merge(options)
16
+
17
+ add_value_to_class_method(:schema, name => options)
18
+
19
+ class_eval(<<-STR
20
+ def #{options[:getter]}
21
+ #{options[:instance_variable]}
22
+ end
23
+
24
+ def #{options[:setter]}(v)
25
+ if schema = ::Schema::Utils.create_schema(self, #{options[:class_name]}, #{name.inspect}, v)
26
+ #{options[:instance_variable]} = schema
27
+ end
28
+ end
29
+ STR
30
+ )
31
+
32
+ const_get(class_name)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+ module Schema
2
+ module Utils
3
+ extend self
4
+
5
+ def classify_name(prefix, name)
6
+ prefix + name.gsub(/[^\da-z_-]/, '').gsub(/(^.|[_|-].)/) { |m| m[-1].upcase }
7
+ end
8
+
9
+ def create_schema_class(base_schema_class, class_name_prefix, name, &block)
10
+ schema_config = base_schema_class.schema_config
11
+ kls = Class.new(schema_config[:schema_base_class] || Object) do
12
+ include ::Schema::Model
13
+ end
14
+
15
+ class_name = classify_name(class_name_prefix, name.to_s)
16
+ base_schema_class.const_set(class_name, kls)
17
+ kls = base_schema_class.const_get(class_name)
18
+
19
+ # make sure additional schema classes use this base class
20
+ kls.schema_base_class = schema_config[:schema_base_class] if schema_config[:schema_base_class]
21
+
22
+ schema_config[:schema_includes].each do |mod|
23
+ kls.schema_include(mod)
24
+ end
25
+
26
+ kls.class_eval(&block)
27
+
28
+ return kls, class_name
29
+ end
30
+
31
+ def create_schema(base_schema, schema_class, schema_name, data)
32
+ if data.is_a?(Hash)
33
+ schema = schema_class.from_hash(data)
34
+ base_schema.parsing_errors.add(schema_name, :invalid) unless schema.parsing_errors.empty?
35
+ schema
36
+ elsif ! data.nil?
37
+ base_schema.parsing_errors.add(schema_name, :incompatable)
38
+ nil
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ def create_schemas(base_schema, schema_class, schema_name, list)
45
+ if list.is_a?(Array)
46
+ list.each_with_index.map { |data, idx| create_schema(base_schema, schema_class, "#{idx}:#{schema_name}", data) }
47
+ elsif ! list.nil?
48
+ base_schema.parsing_errors.add(schema_name, :incompatable)
49
+ nil
50
+ else
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/schema.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Schema
2
+ autoload :Errors, 'schema/errors'
3
+ autoload :Model, 'schema/model'
4
+ autoload :Utils, 'schema/utils'
5
+
6
+ module Parsers
7
+ autoload :Common, 'schema/parsers/common'
8
+ end
9
+
10
+ module Relation
11
+ autoload :HasMany, 'schema/relation/has_many'
12
+ autoload :HasOne, 'schema/relation/has_one'
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schema-model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Doug Youch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: inheritance-helper
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Easy way to create models from payloads
28
+ email: dougyouch@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/schema.rb
34
+ - lib/schema/errors.rb
35
+ - lib/schema/model.rb
36
+ - lib/schema/parsers/common.rb
37
+ - lib/schema/relation/has_many.rb
38
+ - lib/schema/relation/has_one.rb
39
+ - lib/schema/utils.rb
40
+ homepage: https://github.com/dougyouch/schema
41
+ licenses: []
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.0.3
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Schema Model
62
+ test_files: []