schematic 0.0.4 → 0.0.5
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.
- data/lib/schematic/serializers/xsd.rb +108 -84
- data/lib/schematic/version.rb +1 -1
- data/spec/schematic_serializers_xsd_spec.rb +101 -12
- metadata +1 -1
@@ -7,75 +7,125 @@ module Schematic
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
-
def to_xsd(options = {}
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
10
|
+
def to_xsd(options = {})
|
11
|
+
output = ""
|
12
|
+
builder = Builder::XmlMarkup.new(:target => output)
|
13
|
+
builder.instruct!
|
14
|
+
builder.xs :schema, "xmlns:xs" => "http://www.w3.org/2001/XMLSchema" do |schema|
|
15
|
+
schema.xs :element, "name" => xsd_element_collection_name, "type" => xsd_type_collection_name
|
16
|
+
generate_xsd(options, schema, self)
|
17
|
+
end
|
18
|
+
output
|
19
|
+
end
|
20
|
+
|
21
|
+
def map_column_type_to_xsd_type(column)
|
22
|
+
{
|
23
|
+
:integer => "xs:integer",
|
24
|
+
:float => "xs:float",
|
25
|
+
:string => "xs:string",
|
26
|
+
:text => "xs:string",
|
27
|
+
:datetime => "xs:dateTime",
|
28
|
+
:date => "xs:date",
|
29
|
+
:boolean => "xs:boolean"
|
30
|
+
}[column.type]
|
31
|
+
end
|
32
|
+
|
33
|
+
def xsd_minimum_occurrences_for_column(column)
|
34
|
+
self._validators[column.name.to_sym].each do |column_validation|
|
35
|
+
next unless column_validation.is_a? ActiveModel::Validations::PresenceValidator
|
36
|
+
return "1" if column_validation.options[:allow_blank] != true && column_validation.options[:if].nil?
|
37
|
+
end
|
38
|
+
"0"
|
39
|
+
end
|
40
|
+
|
41
|
+
def xsd_element_collection_name
|
42
|
+
xsd_element_name.pluralize
|
43
|
+
end
|
44
|
+
|
45
|
+
def xsd_type_collection_name
|
46
|
+
xsd_type_name.pluralize
|
47
|
+
end
|
48
|
+
|
49
|
+
def xsd_type_name
|
50
|
+
self.name.demodulize
|
51
|
+
end
|
52
|
+
|
53
|
+
def xsd_element_name
|
54
|
+
xsd_type_name.underscore.dasherize
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_xsd(options, builder, klass)
|
58
|
+
xsd_nested_attributes.each do |nested_attribute|
|
59
|
+
next if nested_attribute.klass == klass || nested_attribute.klass == klass.superclass
|
60
|
+
|
61
|
+
nested_attribute.klass.generate_xsd(options, builder, klass)
|
62
|
+
end
|
63
|
+
|
64
|
+
generate_xsd_complex_type_for_collection(builder)
|
65
|
+
generate_xsd_complex_type_for_model(options, builder)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def generate_xsd_complex_type_for_collection(builder)
|
71
|
+
builder.xs :complexType, "name" => xsd_type_collection_name do |complex_type|
|
72
|
+
complex_type.xs :sequence do |sequence|
|
73
|
+
sequence.xs :element, "name" => xsd_element_name, "type" => xsd_type_name, "minOccurs" => "0", "maxOccurs" => "unbounded"
|
23
74
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
75
|
+
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_xsd_complex_type_for_model(options, builder)
|
80
|
+
builder.xs :complexType, "name" => xsd_type_name do |complex_type|
|
81
|
+
additional_methods = xsd_methods.merge(options[:methods] || {})
|
82
|
+
ignored_methods = xsd_ignore_methods | (options[:exclude] || [])
|
83
|
+
complex_type.xs :all do |all|
|
84
|
+
generate_xsd_column_elements(all, additional_methods, ignored_methods)
|
85
|
+
|
86
|
+
xsd_nested_attributes.each do |nested_attribute|
|
87
|
+
all.xs :element, "name" => "#{nested_attribute.name.to_s.dasherize}-attributes", "type" => nested_attribute.klass.xsd_type_collection_name, "minOccurs" => "0", "maxOccurs" => "1"
|
27
88
|
end
|
28
|
-
|
89
|
+
|
90
|
+
generate_xsd_additional_methods(all, additional_methods)
|
29
91
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
xsd_nested_attributes.each do |nested_attribute|
|
48
|
-
all.xs :element, "name" => "#{nested_attribute.name.to_s.dasherize}-attributes", "type" => nested_attribute.klass.xsd_type_collection_name, "minOccurs" => "0", "maxOccurs" => "1"
|
49
|
-
end
|
50
|
-
additional_methods.each do |method_name, values|
|
51
|
-
method_xsd_name = method_name.to_s.dasherize
|
52
|
-
if values.present?
|
53
|
-
all.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1" do |element|
|
54
|
-
element.xs :complexType do |complex_type|
|
55
|
-
complex_type.xs :all do |nested_all|
|
56
|
-
values.each do |value|
|
57
|
-
nested_all.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
else
|
64
|
-
all.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_xsd_column_elements(builder, additional_methods, ignored_methods)
|
96
|
+
xsd_columns.each do |column|
|
97
|
+
next if additional_methods.keys.map(&:to_s).include?(column.name) || ignored_methods.map(&:to_s).include?(column.name)
|
98
|
+
|
99
|
+
builder.xs :element, "name" => column.name.dasherize, "minOccurs" => xsd_minimum_occurrences_for_column(column), "maxOccurs" => "1" do |field|
|
100
|
+
field.xs :complexType do |complex_type|
|
101
|
+
complex_type.xs :simpleContent do |simple_content|
|
102
|
+
simple_content.xs :extension, "base" => map_column_type_to_xsd_type(column) do |extension|
|
103
|
+
extension.xs :attribute, "name" => "type", "type" => "xs:string", "use" => "optional"
|
65
104
|
end
|
66
105
|
end
|
67
106
|
end
|
68
107
|
end
|
69
|
-
builder
|
70
108
|
end
|
71
109
|
end
|
72
110
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
111
|
+
def generate_xsd_additional_methods(builder, additional_methods)
|
112
|
+
additional_methods.each do |method_name, values|
|
113
|
+
method_xsd_name = method_name.to_s.dasherize
|
114
|
+
if values.present?
|
115
|
+
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1" do |element|
|
116
|
+
element.xs :complexType do |complex_type|
|
117
|
+
complex_type.xs :all do |nested_all|
|
118
|
+
values.each do |value|
|
119
|
+
nested_all.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
|
127
|
+
end
|
77
128
|
end
|
78
|
-
"0"
|
79
129
|
end
|
80
130
|
|
81
131
|
def xsd_methods
|
@@ -96,33 +146,7 @@ module Schematic
|
|
96
146
|
self.columns
|
97
147
|
end
|
98
148
|
|
99
|
-
def map_column_type_to_xsd_type(column)
|
100
|
-
{
|
101
|
-
:integer => "xs:integer",
|
102
|
-
:float => "xs:float",
|
103
|
-
:string => "xs:string",
|
104
|
-
:text => "xs:string",
|
105
|
-
:datetime => "xs:dateTime",
|
106
|
-
:date => "xs:date",
|
107
|
-
:boolean => "xs:boolean"
|
108
|
-
}[column.type]
|
109
|
-
end
|
110
|
-
|
111
|
-
def xsd_type_name
|
112
|
-
self.name.demodulize
|
113
|
-
end
|
114
149
|
|
115
|
-
def xsd_type_collection_name
|
116
|
-
xsd_type_name.pluralize
|
117
|
-
end
|
118
|
-
|
119
|
-
def xsd_element_name
|
120
|
-
xsd_type_name.underscore.dasherize
|
121
|
-
end
|
122
|
-
|
123
|
-
def xsd_element_collection_name
|
124
|
-
xsd_element_name.pluralize
|
125
|
-
end
|
126
150
|
end
|
127
151
|
end
|
128
152
|
end
|
data/lib/schematic/version.rb
CHANGED
@@ -59,13 +59,7 @@ describe Schematic::Serializers::Xsd do
|
|
59
59
|
|
60
60
|
end
|
61
61
|
it "should generate a valid XSD" do
|
62
|
-
|
63
|
-
meta_xsd = Nokogiri::XML::Schema(File.open(xsd_schema_file))
|
64
|
-
|
65
|
-
doc = Nokogiri::XML.parse(subject)
|
66
|
-
meta_xsd.validate(doc).each do |error|
|
67
|
-
error.message.should be_nil
|
68
|
-
end
|
62
|
+
validate_xsd(subject)
|
69
63
|
end
|
70
64
|
end
|
71
65
|
|
@@ -93,14 +87,69 @@ describe Schematic::Serializers::Xsd do
|
|
93
87
|
end
|
94
88
|
|
95
89
|
it "should generate a valid XSD" do
|
96
|
-
|
97
|
-
|
90
|
+
validate_xsd(subject)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when the model has a nested attribute on a subclass with a reference to the superclass" do
|
95
|
+
with_model :parent do
|
96
|
+
table {}
|
97
|
+
model do
|
98
|
+
has_many :children
|
99
|
+
accepts_nested_attributes_for :children
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
with_model :child do
|
104
|
+
table do |t|
|
105
|
+
t.integer :parent_id
|
106
|
+
end
|
107
|
+
|
108
|
+
model do
|
109
|
+
belongs_to :parent
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
before do
|
114
|
+
module Namespace; end
|
115
|
+
class Namespace::Child < Child
|
116
|
+
accepts_nested_attributes_for :parent
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
subject { Namespace::Child.to_xsd }
|
121
|
+
|
122
|
+
it "should generate a valid XSD" do
|
123
|
+
validate_xsd(subject)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when the model has a circular nested attribute reference" do
|
128
|
+
with_model :blog do
|
129
|
+
table {}
|
130
|
+
model do
|
131
|
+
has_many :posts
|
132
|
+
accepts_nested_attributes_for :posts
|
133
|
+
end
|
134
|
+
end
|
98
135
|
|
99
|
-
|
100
|
-
|
101
|
-
|
136
|
+
with_model :post do
|
137
|
+
table do |t|
|
138
|
+
t.integer :blog_id
|
102
139
|
end
|
140
|
+
|
141
|
+
model do
|
142
|
+
belongs_to :blog
|
143
|
+
accepts_nested_attributes_for :blog
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
subject { Post.to_xsd }
|
148
|
+
|
149
|
+
it "should generate a valid XSD" do
|
150
|
+
validate_xsd(subject)
|
103
151
|
end
|
152
|
+
|
104
153
|
end
|
105
154
|
end
|
106
155
|
|
@@ -255,6 +304,36 @@ describe Schematic::Serializers::Xsd do
|
|
255
304
|
subject.should == sanitize_xml(xsd)
|
256
305
|
end
|
257
306
|
end
|
307
|
+
|
308
|
+
context "when there is a condition" do
|
309
|
+
with_model :some_model do
|
310
|
+
table :id => false do |t|
|
311
|
+
t.string "title"
|
312
|
+
end
|
313
|
+
|
314
|
+
model do
|
315
|
+
validates :title, :presence => true, :if => lambda { |model| false }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should mark that the field minimum occurrences is 0" do
|
320
|
+
xsd = generate_xsd_for_model(SomeModel) do
|
321
|
+
<<-XML
|
322
|
+
<xs:element name="title" minOccurs="0" maxOccurs="1">
|
323
|
+
<xs:complexType>
|
324
|
+
<xs:simpleContent>
|
325
|
+
<xs:extension base="xs:string">
|
326
|
+
<xs:attribute name="type" type="xs:string" use="optional"/>
|
327
|
+
</xs:extension>
|
328
|
+
</xs:simpleContent>
|
329
|
+
</xs:complexType>
|
330
|
+
</xs:element>
|
331
|
+
XML
|
332
|
+
end
|
333
|
+
|
334
|
+
subject.should == sanitize_xml(xsd)
|
335
|
+
end
|
336
|
+
end
|
258
337
|
end
|
259
338
|
|
260
339
|
describe "length validation" do
|
@@ -368,6 +447,16 @@ describe Schematic::Serializers::Xsd do
|
|
368
447
|
|
369
448
|
private
|
370
449
|
|
450
|
+
def validate_xsd(xml)
|
451
|
+
xsd_schema_file = File.join(File.dirname(__FILE__), "xsd", "XMLSchema.xsd")
|
452
|
+
meta_xsd = Nokogiri::XML::Schema(File.open(xsd_schema_file))
|
453
|
+
|
454
|
+
doc = Nokogiri::XML.parse(xml)
|
455
|
+
meta_xsd.validate(doc).each do |error|
|
456
|
+
error.message.should be_nil
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
371
460
|
def sanitize_xml(xml)
|
372
461
|
xml.split("\n").map(&:strip).join("")
|
373
462
|
end
|