schema-model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []