yaml_exporter 0.1.0 → 0.2.1

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.
@@ -1,196 +0,0 @@
1
- require 'yaml'
2
- require 'json-schema'
3
-
4
- module YamlSerializable
5
- class YamlExporter
6
- def self.export(object, structure)
7
- new(object, structure).export
8
- end
9
-
10
- def self.import(object, yaml_string, structure)
11
- new(object, structure).import(yaml_string)
12
- end
13
-
14
- def self.generate_schema(klass, structure)
15
- new(klass.new, structure).generate_schema
16
- end
17
-
18
- def initialize(object, structure)
19
- @object = object
20
- @structure = structure
21
- end
22
-
23
- def export
24
- yaml = YAML.dump(build_hash(@structure, @object))
25
- validate(yaml)
26
- yaml
27
- end
28
-
29
- def import(yaml_string)
30
- data = YAML.safe_load(yaml_string)
31
- validate(yaml_string)
32
- update_object(data)
33
- @object
34
- end
35
-
36
- def generate_schema
37
- {
38
- type: 'object',
39
- properties: generate_properties(@structure, @object.class),
40
- required: required_attributes(@structure, @object.class)
41
- }
42
- end
43
-
44
- private
45
-
46
- def build_hash(structure, object)
47
- result = {}
48
-
49
- structure[:attributes].each do |attr|
50
- value = object.send(attr)
51
- result[attr.to_s] = value unless value.nil?
52
- end
53
-
54
- structure[:associations].each do |name, config|
55
- if config[:type] == :has_many
56
- associated_objects = object.send(name).unscope(:order).order(:id)
57
- if associated_objects.any?
58
- result[name.to_s] = associated_objects.map { |item| build_hash(config[:structure], item) }
59
- end
60
- elsif config[:type] == :has_one
61
- associated_object = object.send(name)
62
- result[name.to_s] = build_hash(config[:structure], associated_object) if associated_object
63
- end
64
- end
65
-
66
- result.compact
67
- end
68
-
69
- def validate(yaml)
70
- schema = generate_schema
71
- data = YAML.safe_load(yaml)
72
- JSON::Validator.validate!(schema, data)
73
- rescue JSON::Schema::ValidationError => e
74
- raise "Invalid YAML structure: #{e.message}"
75
- end
76
-
77
- def update_object(data)
78
- @object.transaction do
79
- update_attributes(@object, data, @structure[:attributes])
80
- @structure[:associations].each do |name, config|
81
- if config[:type] == :has_many
82
- update_collection(@object, data[name.to_s], name, config[:structure])
83
- elsif config[:type] == :has_one && data[name.to_s]
84
- update_nested(@object, data[name.to_s], name, config[:structure])
85
- end
86
- end
87
- @object.save!
88
- end
89
- end
90
-
91
- def update_attributes(object, data, attributes)
92
- attributes.each do |attr|
93
- if data.key?(attr.to_s)
94
- object.send("#{attr}=", data[attr.to_s])
95
- else
96
- object.send("#{attr}=", nil)
97
- end
98
- end
99
- end
100
-
101
- def update_collection(parent, items_data, association_name, config)
102
- existing_items = parent.send(association_name).unscope(:order).order(:id).to_a
103
- new_items = []
104
-
105
- items_data&.each_with_index do |item_data, index|
106
- item = existing_items[index] || parent.send(association_name).build
107
- update_attributes(item, item_data, config[:attributes])
108
- config[:associations]&.each do |sub_name, sub_config|
109
- if sub_config[:type] == :has_many
110
- update_collection(item, item_data[sub_name.to_s], sub_name, sub_config[:structure])
111
- elsif sub_config[:type] == :has_one && item_data[sub_name.to_s]
112
- update_nested(item, item_data[sub_name.to_s], sub_name, sub_config[:structure])
113
- end
114
- end
115
- new_items << item
116
- end
117
-
118
- # Entferne Items, die nicht mehr im YAML vorhanden sind
119
- (existing_items - new_items).each(&:mark_for_destruction)
120
-
121
- parent.send("#{association_name}=", new_items)
122
- end
123
-
124
- def update_nested(parent, nested_data, association_name, config)
125
- nested_object = parent.send(association_name) || parent.send("build_#{association_name}")
126
- update_attributes(nested_object, nested_data, config[:attributes])
127
- config[:associations]&.each do |sub_name, sub_config|
128
- if sub_config[:type] == :has_many
129
- update_collection(nested_object, nested_data[sub_name.to_s], sub_name, sub_config[:structure])
130
- elsif sub_config[:type] == :has_one && nested_data[sub_name.to_s]
131
- update_nested(nested_object, nested_data[sub_name.to_s], sub_name, sub_config[:structure])
132
- end
133
- end
134
- end
135
-
136
- def generate_properties(structure, klass)
137
- properties = {}
138
-
139
- structure[:attributes].each do |attr|
140
- column = klass.columns_hash[attr.to_s]
141
- properties[attr.to_s] = { type: infer_type(column) }
142
- end
143
-
144
- structure[:associations].each do |name, config|
145
- if config[:type] == :has_many
146
- properties[name.to_s] = {
147
- type: 'array',
148
- items: {
149
- type: 'object',
150
- properties: generate_properties(config[:structure], association_class(klass, name)),
151
- required: required_attributes(config[:structure], association_class(klass, name))
152
- }
153
- }
154
- elsif config[:type] == :has_one
155
- properties[name.to_s] = {
156
- type: 'object',
157
- properties: generate_properties(config[:structure], association_class(klass, name)),
158
- required: required_attributes(config[:structure], association_class(klass, name))
159
- }
160
- end
161
- end
162
-
163
- properties
164
- end
165
-
166
- def infer_type(column)
167
- case column.type
168
- when :string, :text
169
- 'string'
170
- when :integer, :bigint
171
- 'integer'
172
- when :float, :decimal
173
- 'number'
174
- when :boolean
175
- 'boolean'
176
- when :date, :datetime, :time
177
- 'string'
178
- when :json, :jsonb
179
- 'object'
180
- else
181
- 'string'
182
- end
183
- end
184
-
185
- def association_class(klass, association_name)
186
- klass.reflect_on_association(association_name).klass
187
- end
188
-
189
- def required_attributes(structure, klass)
190
- structure[:attributes].select do |attr|
191
- column = klass.columns_hash[attr.to_s]
192
- column && !column.null
193
- end
194
- end
195
- end
196
- end