schema-model 0.1.0 → 0.2.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/lib/schema/array_headers.rb +126 -0
- data/lib/schema/arrays.rb +89 -0
- data/lib/schema/associations/has_many.rb +37 -0
- data/lib/schema/associations/has_one.rb +37 -0
- data/lib/schema/csv_parser.rb +45 -0
- data/lib/schema/errors.rb +3 -0
- data/lib/schema/model.rb +36 -40
- data/lib/schema/parsers/common.rb +9 -7
- data/lib/schema/utils.rb +35 -22
- data/lib/schema.rb +12 -3
- metadata +9 -5
- data/lib/schema/relation/has_many.rb +0 -37
- data/lib/schema/relation/has_one.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e515db757ac9610d21f55a046be3dd4b7c9ea089f6ee5db2d027db5857a0804
|
4
|
+
data.tar.gz: 734e46e76a917d70c0f170bea2b4a1bb4b784f43d00a64b7a5eb71d2d142ccfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b068c0de3f58564b9a4ec9480cbf4792b8e865cb3cfbce67ee4f19c9410206bfff2921afa9693f634d335ade358df115f04a87e6668d6927a0b95d137fbaf51
|
7
|
+
data.tar.gz: 996e4ecd5e4b6ff4fc5be81ac349c5fde247781088a40907dfb368146578f57ec51c17947bdca1718289e93c7f35942c9ec4547583bb069497021402985b1dd7
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Schema
|
4
|
+
# Schema::ArrayHeaders maps columns to schema attributes
|
5
|
+
module ArrayHeaders
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
# adds methods to the class
|
11
|
+
module ClassMethods
|
12
|
+
def map_headers_to_attributes(headers, header_prefix = nil)
|
13
|
+
mapped_headers = {}
|
14
|
+
map_headers_to_fields(headers, mapped_headers, header_prefix)
|
15
|
+
map_headers_to_has_one_associations(headers, mapped_headers, header_prefix)
|
16
|
+
map_headers_to_has_many_associations(headers, mapped_headers)
|
17
|
+
mapped_headers
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_unmapped_field_names(mapped_headers, header_prefix = nil)
|
21
|
+
get_field_names(mapped_headers, header_prefix, false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_mapped_field_names(mapped_headers, header_prefix = nil)
|
25
|
+
get_field_names(mapped_headers, header_prefix, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_field_names(mapped_headers, header_prefix = nil, mapped = true)
|
29
|
+
fields = []
|
30
|
+
schema.each do |field_name, field_options|
|
31
|
+
case field_options[:type]
|
32
|
+
when :has_one,
|
33
|
+
:has_many
|
34
|
+
fields += get_model_field_names(field_name, field_options, mapped_headers, header_prefix, mapped)
|
35
|
+
else
|
36
|
+
next if skip_field?(field_name, field_options, mapped_headers, mapped)
|
37
|
+
|
38
|
+
fields << generate_field_name(field_name, field_options, header_prefix)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
fields
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def get_model_field_names(field_name, field_options, mapped_headers, header_prefix, mapped)
|
47
|
+
mapped_model = mapped_headers[field_name] || {}
|
48
|
+
const_get(field_options[:class_name]).get_field_names(
|
49
|
+
mapped_model,
|
50
|
+
header_prefix || field_options[:header_prefix],
|
51
|
+
mapped
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def skip_field?(field_name, field_options, mapped_headers, mapped)
|
56
|
+
# skip alias fields
|
57
|
+
return true if field_options[:alias_of]
|
58
|
+
|
59
|
+
if mapped
|
60
|
+
mapped_headers[field_name].nil?
|
61
|
+
else
|
62
|
+
!mapped_headers[field_name].nil?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate_field_name(field_name, field_options, header_prefix)
|
67
|
+
field_name = field_options[:aliases].first if field_options[:aliases]
|
68
|
+
field_name = header_prefix + 'X' + field_name.to_s if header_prefix
|
69
|
+
field_name.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_mapped_model(field_options, headers, header_prefix)
|
73
|
+
const_get(field_options[:class_name]).map_headers_to_attributes(headers, header_prefix)
|
74
|
+
end
|
75
|
+
|
76
|
+
def map_headers_to_has_one_associations(headers, mapped_headers, header_prefix)
|
77
|
+
schema.each do |field_name, field_options|
|
78
|
+
next unless field_options[:type] == :has_one
|
79
|
+
|
80
|
+
mapped_model = get_mapped_model(field_options, headers, header_prefix)
|
81
|
+
next if mapped_model.empty?
|
82
|
+
|
83
|
+
mapped_headers[field_name] = mapped_model
|
84
|
+
end
|
85
|
+
mapped_headers
|
86
|
+
end
|
87
|
+
|
88
|
+
def map_headers_to_has_many_associations(headers, mapped_headers)
|
89
|
+
schema.each do |field_name, field_options|
|
90
|
+
next unless field_options[:type] == :has_many
|
91
|
+
next unless field_options[:header_prefix]
|
92
|
+
|
93
|
+
mapped_model = get_mapped_model(field_options, headers, field_options[:header_prefix])
|
94
|
+
next if mapped_model.empty?
|
95
|
+
|
96
|
+
mapped_headers[field_name] = mapped_model
|
97
|
+
end
|
98
|
+
mapped_headers
|
99
|
+
end
|
100
|
+
|
101
|
+
def map_headers_to_fields(headers, mapped_headers, header_prefix)
|
102
|
+
schema.each do |field_name, field_options|
|
103
|
+
if header_prefix
|
104
|
+
unless (indexes = find_indexes_for_field(headers, field_options, header_prefix)).empty?
|
105
|
+
mapped_headers[field_options[:alias_of] || field_name] = { indexes: indexes }
|
106
|
+
end
|
107
|
+
elsif (index = headers.index(field_options[:key]))
|
108
|
+
mapped_headers[field_options[:alias_of] || field_name] = { index: index }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
mapped_headers
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_indexes_for_field(headers, field_options, header_prefix)
|
115
|
+
cnt = field_options[:starting_index] || 1
|
116
|
+
indexes = []
|
117
|
+
# finding all headers that look like Company1Name through CompanyXName
|
118
|
+
while (index = headers.index(header_prefix + cnt.to_s + field_options[:key]))
|
119
|
+
cnt += 1
|
120
|
+
indexes << index
|
121
|
+
end
|
122
|
+
indexes
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Schema
|
4
|
+
# Schema::Arrays maps the array to a schema model
|
5
|
+
module Arrays
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
# adds from_array method to the class
|
11
|
+
module ClassMethods
|
12
|
+
def from_array(array, mapped_headers)
|
13
|
+
new.update_attributes_with_array(array, mapped_headers)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_attributes_with_array(array, mapped_headers, offset = nil)
|
18
|
+
self.class.schema.each do |_, field_options|
|
19
|
+
next unless (mapped_field = mapped_headers[field_options[:name]])
|
20
|
+
|
21
|
+
if offset
|
22
|
+
next unless mapped_field[:indexes]
|
23
|
+
next unless (index = mapped_field[:indexes][offset])
|
24
|
+
else
|
25
|
+
next unless (index = mapped_field[:index])
|
26
|
+
end
|
27
|
+
|
28
|
+
public_send(
|
29
|
+
field_options[:setter],
|
30
|
+
array[index]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
update_nested_schemas_from_array(array, mapped_headers, offset)
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_nested_schemas_from_array(array, mapped_headers, current_offset = nil)
|
40
|
+
update_nested_has_one_associations_from_array(array, mapped_headers, current_offset)
|
41
|
+
update_nested_has_many_associations_from_array(array, mapped_headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_nested_has_one_associations_from_array(array, mapped_headers, current_offset = nil)
|
45
|
+
self.class.schema.each do |_, field_options|
|
46
|
+
next unless field_options[:type] == :has_one
|
47
|
+
next unless (mapped_model = mapped_headers[field_options[:name]])
|
48
|
+
|
49
|
+
instance_variable_set(
|
50
|
+
field_options[:instance_variable],
|
51
|
+
create_schema_with_array(field_options, array, mapped_model, current_offset)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_nested_has_many_associations_from_array(array, mapped_headers)
|
57
|
+
self.class.schema.each do |_, field_options|
|
58
|
+
next unless field_options[:type] == :has_many
|
59
|
+
next unless (mapped_model = mapped_headers[field_options[:name]])
|
60
|
+
|
61
|
+
size = largest_number_of_indexes_from_map(mapped_model)
|
62
|
+
|
63
|
+
instance_variable_set(
|
64
|
+
field_options[:instance_variable],
|
65
|
+
size.times.map do |offset|
|
66
|
+
create_schema_with_array(field_options, array, mapped_model, offset)
|
67
|
+
end
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_schema_with_array(field_options, array, mapped_model, offset)
|
73
|
+
self.class.const_get(field_options[:class_name]).new.update_attributes_with_array(array, mapped_model, offset)
|
74
|
+
end
|
75
|
+
|
76
|
+
def largest_number_of_indexes_from_map(mapped_model)
|
77
|
+
size = 0
|
78
|
+
mapped_model.each do |_, info|
|
79
|
+
if info[:indexes]
|
80
|
+
size = info[:indexes].size if info[:indexes] && info[:indexes].size > size
|
81
|
+
else
|
82
|
+
new_size = largest_number_of_indexes_from_map(info)
|
83
|
+
size = new_size if new_size > size
|
84
|
+
end
|
85
|
+
end
|
86
|
+
size
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Schema
|
4
|
+
module Associations
|
5
|
+
# Schema::Associations::HasMany is used to create a list nested schema objects
|
6
|
+
module HasMany
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# no-doc
|
12
|
+
module ClassMethods
|
13
|
+
# rubocop:disable Naming/PredicateName
|
14
|
+
def has_many(name, options = {}, &block)
|
15
|
+
options = ::Schema::Utils.add_association_class(self, name, :has_many, options, &block)
|
16
|
+
|
17
|
+
class_eval(
|
18
|
+
<<~STR, __FILE__, __LINE__ + 1
|
19
|
+
def #{options[:getter]}
|
20
|
+
#{options[:instance_variable]}
|
21
|
+
end
|
22
|
+
|
23
|
+
def #{options[:setter]}(v)
|
24
|
+
if schemas = ::Schema::Utils.create_schemas(self, #{options[:class_name]}, #{name.inspect}, v)
|
25
|
+
#{options[:instance_variable]} = schemas
|
26
|
+
end
|
27
|
+
end
|
28
|
+
STR
|
29
|
+
)
|
30
|
+
|
31
|
+
const_get(options[:class_name])
|
32
|
+
end
|
33
|
+
# rubocop:enable Naming/PredicateName
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Schema
|
4
|
+
module Associations
|
5
|
+
# Schema::Associations::HasOne is used to create a nested schema object
|
6
|
+
module HasOne
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# no-doc
|
12
|
+
module ClassMethods
|
13
|
+
# rubocop:disable Naming/PredicateName
|
14
|
+
def has_one(name, options = {}, &block)
|
15
|
+
options = ::Schema::Utils.add_association_class(self, name, :has_one, options, &block)
|
16
|
+
|
17
|
+
class_eval(
|
18
|
+
<<~STR, __FILE__, __LINE__ + 1
|
19
|
+
def #{options[:getter]}
|
20
|
+
#{options[:instance_variable]}
|
21
|
+
end
|
22
|
+
|
23
|
+
def #{options[:setter]}(v)
|
24
|
+
if schema = ::Schema::Utils.create_schema(self, #{options[:class_name]}, #{name.inspect}, v)
|
25
|
+
#{options[:instance_variable]} = schema
|
26
|
+
end
|
27
|
+
end
|
28
|
+
STR
|
29
|
+
)
|
30
|
+
|
31
|
+
const_get(options[:class_name])
|
32
|
+
end
|
33
|
+
# rubocop:enable Naming/PredicateName
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Schema
|
4
|
+
# Schema::CSVParser is used to create schema models from a csv
|
5
|
+
class CSVParser
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(csv, schema_class, headers = nil)
|
9
|
+
@csv = csv
|
10
|
+
@schema_class = schema_class
|
11
|
+
@headers = headers || csv.shift
|
12
|
+
@mapped_headers = schema_class.map_headers_to_attributes(@headers)
|
13
|
+
end
|
14
|
+
|
15
|
+
def missing_fields(required_fields)
|
16
|
+
required_fields - get_mapped_headers(@mapped_headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
def shift
|
20
|
+
return unless (row = @csv.shift)
|
21
|
+
|
22
|
+
@schema_class.from_array(row, @mapped_headers)
|
23
|
+
end
|
24
|
+
|
25
|
+
def each
|
26
|
+
while (schema = shift)
|
27
|
+
yield schema
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_mapped_headers(mapped_headers)
|
32
|
+
indexed_headers = []
|
33
|
+
mapped_headers.each do |_, info|
|
34
|
+
if (index = info[:index])
|
35
|
+
indexed_headers << @headers[index]
|
36
|
+
elsif (indexes = info[:indexes])
|
37
|
+
indexed_headers += indexes.map { |i| @headers[i] }
|
38
|
+
else
|
39
|
+
indexed_headers += get_mapped_headers(info)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
indexed_headers
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/schema/errors.rb
CHANGED
data/lib/schema/model.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'inheritance-helper'
|
2
4
|
|
3
5
|
module Schema
|
6
|
+
# Schema::Model adds schema building methods to a class
|
4
7
|
module Model
|
5
|
-
def self.included
|
8
|
+
def self.included(base)
|
6
9
|
base.extend InheritanceHelper::Methods
|
7
10
|
base.include Schema::Parsers::Common
|
8
11
|
base.extend ClassMethods
|
@@ -14,11 +17,12 @@ module Schema
|
|
14
17
|
name: name,
|
15
18
|
type: type,
|
16
19
|
getter: name.to_s.freeze,
|
17
|
-
setter: "#{name}="
|
18
|
-
instance_variable: "@#{name}"
|
20
|
+
setter: "#{name}=",
|
21
|
+
instance_variable: "@#{name}"
|
19
22
|
}
|
20
23
|
end
|
21
24
|
|
25
|
+
# no-doc
|
22
26
|
module ClassMethods
|
23
27
|
def schema
|
24
28
|
{}.freeze
|
@@ -30,36 +34,17 @@ module Schema
|
|
30
34
|
}.freeze
|
31
35
|
end
|
32
36
|
|
33
|
-
def attribute(name, type, options={})
|
34
|
-
if options.
|
35
|
-
options[:aliases] = [options[:alias]]
|
36
|
-
end
|
37
|
+
def attribute(name, type, options = {})
|
38
|
+
options[:aliases] = [options[:alias]] if options.key?(:alias)
|
37
39
|
|
38
40
|
options = ::Schema::Model.default_attribute_options(name, type)
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
.merge(
|
42
|
+
parser: "parse_#{type}"
|
43
|
+
).merge(options)
|
42
44
|
|
43
45
|
add_value_to_class_method(:schema, name => options)
|
44
|
-
|
45
|
-
|
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
|
46
|
+
add_attribute_methods(name, options)
|
47
|
+
add_aliases(name, options)
|
63
48
|
end
|
64
49
|
|
65
50
|
def from_hash(data)
|
@@ -73,20 +58,32 @@ STR
|
|
73
58
|
include mod
|
74
59
|
end
|
75
60
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
61
|
+
def add_attribute_methods(name, options)
|
62
|
+
class_eval(
|
63
|
+
<<~STR, __FILE__, __LINE__ + 1
|
64
|
+
def #{options[:getter]}
|
65
|
+
#{options[:instance_variable]}
|
66
|
+
end
|
67
|
+
|
68
|
+
def #{options[:setter]}(v)
|
69
|
+
#{options[:instance_variable]} = #{options[:parser]}(#{name.inspect}, parsing_errors, v)
|
70
|
+
end
|
71
|
+
STR
|
72
|
+
)
|
80
73
|
end
|
81
74
|
|
82
|
-
def
|
83
|
-
|
75
|
+
def add_aliases(name, options)
|
76
|
+
options[:aliases]&.each do |alias_name|
|
77
|
+
add_value_to_class_method(:schema, alias_name.to_sym => options.merge(key: alias_name.to_s, alias_of: name))
|
78
|
+
alias_method(alias_name, options[:getter])
|
79
|
+
alias_method("#{alias_name}=", options[:setter])
|
80
|
+
end
|
84
81
|
end
|
85
82
|
end
|
86
83
|
|
87
84
|
def update_attributes(data)
|
88
85
|
self.class.schema.each do |field_name, field_options|
|
89
|
-
next if !
|
86
|
+
next if !data.key?(field_options[:key]) && !data.key?(field_name)
|
90
87
|
|
91
88
|
public_send(
|
92
89
|
field_options[:setter],
|
@@ -97,13 +94,12 @@ STR
|
|
97
94
|
self
|
98
95
|
end
|
99
96
|
|
100
|
-
def as_json(opts={})
|
101
|
-
self.class.schema.
|
97
|
+
def as_json(opts = {})
|
98
|
+
self.class.schema.each_with_object({}) do |(field_name, field_options), memo|
|
102
99
|
unless field_options[:alias_of]
|
103
100
|
value = public_send(field_options[:getter])
|
104
|
-
memo[field_name] = value if !
|
101
|
+
memo[field_name] = value if !value.nil? || opts[:include_nils]
|
105
102
|
end
|
106
|
-
memo
|
107
103
|
end
|
108
104
|
end
|
109
105
|
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'time'
|
2
4
|
|
3
5
|
module Schema
|
4
6
|
module Parsers
|
7
|
+
# Schema::Parsers::Common are parser methods for basic types
|
5
8
|
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
|
+
INTEGER_REGEX = /^(?:[1-9]\d*|0)$/.freeze
|
10
|
+
FLOAT_REGEX = /^(?:[1-9]\d*|0)(?:\.\d+)?$/.freeze
|
11
|
+
BOOLEAN_REGEX = /^(?:1|t|true|on|y|yes)$/i.freeze
|
9
12
|
|
13
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
10
14
|
def parse_integer(field_name, parsing_errors, value)
|
11
15
|
case value
|
12
16
|
when Integer
|
@@ -19,9 +23,7 @@ module Schema
|
|
19
23
|
nil
|
20
24
|
end
|
21
25
|
when Float
|
22
|
-
if (value % 1) > 0.0
|
23
|
-
parsing_errors.add(field_name, :incompatable)
|
24
|
-
end
|
26
|
+
parsing_errors.add(field_name, :incompatable) if (value % 1) > 0.0
|
25
27
|
value.to_i
|
26
28
|
when nil
|
27
29
|
nil
|
@@ -30,6 +32,7 @@ module Schema
|
|
30
32
|
nil
|
31
33
|
end
|
32
34
|
end
|
35
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
33
36
|
|
34
37
|
def parse_string(field_name, parsing_errors, value)
|
35
38
|
case value
|
@@ -126,4 +129,3 @@ module Schema
|
|
126
129
|
end
|
127
130
|
end
|
128
131
|
end
|
129
|
-
|
data/lib/schema/utils.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Schema
|
4
|
+
# Schema::Utils is a collection of common utility methods used in this gem
|
2
5
|
module Utils
|
3
|
-
|
6
|
+
module_function
|
4
7
|
|
5
|
-
def classify_name(
|
6
|
-
|
8
|
+
def classify_name(name)
|
9
|
+
name.gsub(/[^\da-z_-]/, '').gsub(/(^.|[_|-].)/) { |m| m[-1].upcase }
|
7
10
|
end
|
8
11
|
|
9
|
-
def create_schema_class(base_schema_class,
|
10
|
-
|
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)
|
12
|
+
def create_schema_class(base_schema_class, class_name, base_class, &block)
|
13
|
+
kls = Class.new(base_class)
|
16
14
|
base_schema_class.const_set(class_name, kls)
|
17
15
|
kls = base_schema_class.const_get(class_name)
|
18
16
|
|
19
|
-
|
20
|
-
|
17
|
+
include_schema_modules(kls, base_schema_class.schema_config) if base_class == Object
|
18
|
+
|
19
|
+
kls.class_eval(&block) if block
|
21
20
|
|
21
|
+
kls
|
22
|
+
end
|
23
|
+
|
24
|
+
def include_schema_modules(kls, schema_config)
|
25
|
+
kls.include ::Schema::Model
|
22
26
|
schema_config[:schema_includes].each do |mod|
|
23
27
|
kls.schema_include(mod)
|
24
28
|
end
|
25
|
-
|
26
|
-
kls.class_eval(&block)
|
27
|
-
|
28
|
-
return kls, class_name
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_schema(base_schema, schema_class, schema_name, data)
|
@@ -33,23 +33,36 @@ module Schema
|
|
33
33
|
schema = schema_class.from_hash(data)
|
34
34
|
base_schema.parsing_errors.add(schema_name, :invalid) unless schema.parsing_errors.empty?
|
35
35
|
schema
|
36
|
-
elsif !
|
36
|
+
elsif !data.nil?
|
37
37
|
base_schema.parsing_errors.add(schema_name, :incompatable)
|
38
38
|
nil
|
39
|
-
else
|
40
|
-
nil
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
42
|
def create_schemas(base_schema, schema_class, schema_name, list)
|
45
43
|
if list.is_a?(Array)
|
46
44
|
list.each_with_index.map { |data, idx| create_schema(base_schema, schema_class, "#{idx}:#{schema_name}", data) }
|
47
|
-
elsif !
|
45
|
+
elsif !list.nil?
|
48
46
|
base_schema.parsing_errors.add(schema_name, :incompatable)
|
49
47
|
nil
|
50
|
-
else
|
51
|
-
nil
|
52
48
|
end
|
53
49
|
end
|
50
|
+
|
51
|
+
def association_options(name, type, options)
|
52
|
+
options[:class_name] ||= 'Schema' + classify_name(type.to_s) + classify_name(name.to_s)
|
53
|
+
::Schema::Model.default_attribute_options(name, type).merge(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_association_class(base_schema_class, name, type, options, &block)
|
57
|
+
options = ::Schema::Utils.association_options(name, type, options)
|
58
|
+
::Schema::Utils.create_schema_class(
|
59
|
+
base_schema_class,
|
60
|
+
options[:class_name],
|
61
|
+
options[:base_class] || Object,
|
62
|
+
&block
|
63
|
+
)
|
64
|
+
base_schema_class.add_value_to_class_method(:schema, name => options)
|
65
|
+
options
|
66
|
+
end
|
54
67
|
end
|
55
68
|
end
|
data/lib/schema.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Schema is a series of tools for transforming data into models
|
1
4
|
module Schema
|
5
|
+
autoload :ArrayHeaders, 'schema/array_headers'
|
6
|
+
autoload :Arrays, 'schema/arrays'
|
7
|
+
autoload :CSVParser, 'schema/csv_parser'
|
2
8
|
autoload :Errors, 'schema/errors'
|
9
|
+
autoload :CSVParser, 'schema/csv_parser'
|
3
10
|
autoload :Model, 'schema/model'
|
4
11
|
autoload :Utils, 'schema/utils'
|
5
12
|
|
13
|
+
# Schema::Parsers are used to convert values into the correct data type
|
6
14
|
module Parsers
|
7
15
|
autoload :Common, 'schema/parsers/common'
|
8
16
|
end
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
autoload :
|
18
|
+
# Schema::Associations mange the associations between schema models
|
19
|
+
module Associations
|
20
|
+
autoload :HasMany, 'schema/associations/has_many'
|
21
|
+
autoload :HasOne, 'schema/associations/has_one'
|
13
22
|
end
|
14
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Doug Youch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inheritance-helper
|
@@ -31,14 +31,18 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
33
|
- lib/schema.rb
|
34
|
+
- lib/schema/array_headers.rb
|
35
|
+
- lib/schema/arrays.rb
|
36
|
+
- lib/schema/associations/has_many.rb
|
37
|
+
- lib/schema/associations/has_one.rb
|
38
|
+
- lib/schema/csv_parser.rb
|
34
39
|
- lib/schema/errors.rb
|
35
40
|
- lib/schema/model.rb
|
36
41
|
- lib/schema/parsers/common.rb
|
37
|
-
- lib/schema/relation/has_many.rb
|
38
|
-
- lib/schema/relation/has_one.rb
|
39
42
|
- lib/schema/utils.rb
|
40
43
|
homepage: https://github.com/dougyouch/schema
|
41
|
-
licenses:
|
44
|
+
licenses:
|
45
|
+
- MIT
|
42
46
|
metadata: {}
|
43
47
|
post_install_message:
|
44
48
|
rdoc_options: []
|
@@ -1,37 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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
|