schematic 0.0.8 → 0.1.2

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/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -31,7 +31,7 @@ You can include additional elements by defining a ".xsd_methods" on your class:
31
31
  end
32
32
  end
33
33
 
34
- You can exclude elements bby defining a ".xsd_ignore_methods" on your class:
34
+ You can exclude elements by defining a ".xsd_ignore_methods" on your class:
35
35
 
36
36
  class Post < ActiveRecord::Base
37
37
  def self.xsd_ignore_methods
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ * Support numericality validation as pattern restriction
2
+ * Support exclusion validation as a pattern restriction
3
+ * Support exact length validation with length? or is min/max okay?
4
+ * Support uniqueness constraint on primary keys and uniqness validations using unique or key tag?
5
+
@@ -10,6 +10,9 @@ require "builder"
10
10
 
11
11
  require 'active_support/inflector/inflections'
12
12
  require 'active_support/inflections'
13
- require "schematic/serializers/xsd"
13
+
14
+ Dir[File.join(File.dirname(__FILE__), "schematic/**/*.rb")].each do |file|
15
+ require file.gsub(/\/.rb$/,'')
16
+ end
14
17
 
15
18
  ActiveRecord::Base.send(:extend, Schematic::Serializers::Xsd)
@@ -0,0 +1,47 @@
1
+ module Schematic
2
+ module Generator
3
+ class Column
4
+
5
+ def initialize(klass, column, additional_methods = {}, ignored_methods = {})
6
+ @klass = klass
7
+ @column = column
8
+ @additional_methods = additional_methods
9
+ @ignored_methods = ignored_methods
10
+ end
11
+
12
+ def generate(builder)
13
+ return if skip_generation?
14
+
15
+ builder.xs :element, "name" => @column.name.dasherize, "minOccurs" => minimum_occurrences_for_column, "maxOccurs" => "1" do |field|
16
+ field.xs :complexType do |complex_type|
17
+ complex_type.xs :simpleContent do |simple_content|
18
+ simple_content.xs :restriction, "base" => map_type(@column) do |restriction|
19
+ Restrictions::Length.new(@klass, @column).generate(restriction)
20
+ Restrictions::Enumeration.new(@klass, @column).generate(restriction)
21
+ Restrictions::Pattern.new(@klass, @column).generate(restriction)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def minimum_occurrences_for_column
29
+ @klass._validators[@column.name.to_sym].each do |column_validation|
30
+ next unless column_validation.is_a? ActiveModel::Validations::PresenceValidator
31
+ return "1" if column_validation.options[:allow_blank] != true && column_validation.options[:if].nil?
32
+ end
33
+ "0"
34
+ end
35
+
36
+
37
+ def map_type(column)
38
+ Types::COMPLEX[column.type][:complex_type]
39
+ end
40
+
41
+ def skip_generation?
42
+ @additional_methods.keys.map(&:to_s).include?(@column.name) ||
43
+ @ignored_methods.map(&:to_s).include?(@column.name)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ module Schematic
2
+ module Generator
3
+ class Names
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
8
+
9
+ def type
10
+ @klass.name.demodulize
11
+ end
12
+
13
+ def element
14
+ type.underscore.dasherize
15
+ end
16
+
17
+ def element_collection
18
+ element.pluralize
19
+ end
20
+
21
+ def collection_type
22
+ type.pluralize
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Schematic
2
+ module Generator
3
+ class Namespaces
4
+ PROVIDERS = {
5
+ :w3 => {
6
+ :schema => "http://www.w3.org/2001/XMLSchema"
7
+ }
8
+ }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Schematic
2
+ module Generator
3
+ module Restrictions
4
+ class Base
5
+ def initialize(klass, column)
6
+ @klass = klass
7
+ @column = column
8
+ end
9
+
10
+ def for_validator(validator_klass)
11
+ @klass._validators[@column.name.to_sym].each do |column_validation|
12
+ next unless column_validation.is_a? validator_klass
13
+ next unless column_validation.options[:if].nil?
14
+ yield(column_validation)
15
+ return
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+
@@ -0,0 +1,21 @@
1
+ module Schematic
2
+ module Generator
3
+ module Restrictions
4
+ class Enumeration < Base
5
+ def initialize(klass, column)
6
+ @klass = klass
7
+ @column = column
8
+ end
9
+
10
+ def generate(builder)
11
+ for_validator ActiveModel::Validations::InclusionValidator do |validator|
12
+ validator.options[:in].each do |value|
13
+ builder.xs(:enumeration, "value" => value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,19 @@
1
+ module Schematic
2
+ module Generator
3
+ module Restrictions
4
+ class Length < Base
5
+ def initialize(klass, column)
6
+ @klass = klass
7
+ @column = column
8
+ end
9
+
10
+ def generate(builder)
11
+ for_validator ActiveModel::Validations::LengthValidator do |validator|
12
+ builder.xs(:maxLength, "value" => validator.options[:maximum]) if validator.options[:maximum]
13
+ builder.xs(:minLength, "value" => validator.options[:minimum]) if validator.options[:minimum]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Schematic
2
+ module Generator
3
+ module Restrictions
4
+ class Pattern < Base
5
+ def initialize(klass, column)
6
+ @klass = klass
7
+ @column = column
8
+ end
9
+
10
+ def generate(builder)
11
+ for_validator ActiveModel::Validations::FormatValidator do |validator|
12
+ builder.xs(:pattern, "value" => validator.options[:with].source) if validator.options[:with]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+
@@ -0,0 +1,29 @@
1
+ module Schematic
2
+ module Generator
3
+ class Types
4
+ COMPLEX = {
5
+ :integer => { :complex_type => "Integer", :xsd_type => "xs:integer" },
6
+ :float => { :complex_type => "Float", :xsd_type => "xs:float" },
7
+ :string => { :complex_type => "String", :xsd_type => "xs:string" },
8
+ :text => { :complex_type => "Text", :xsd_type => "xs:string" },
9
+ :datetime => { :complex_type => "DateTime", :xsd_type => "xs:dateTime" },
10
+ :date => { :complex_type => "Date", :xsd_type => "xs:date" },
11
+ :boolean => { :complex_type => "Boolean", :xsd_type => "xs:boolean" },
12
+ }
13
+
14
+ def self.xsd(builder)
15
+ Types::COMPLEX.each do |key, value|
16
+ complex_type_name = value[:complex_type]
17
+ xsd_type = value[:xsd_type]
18
+ builder.xs :complexType, "name" => complex_type_name do |complex_type|
19
+ complex_type.xs :simpleContent do |simple_content|
20
+ simple_content.xs :extension, "base" => xsd_type do |extension|
21
+ extension.xs :attribute, "name" => "type", "type" => "xs:string", "use" => "optional"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,111 @@
1
+ module Schematic
2
+ module Generator
3
+ class Xsd
4
+ attr_reader :output, :names
5
+ attr_accessor :options
6
+
7
+ def initialize(klass, options = {})
8
+ @klass = klass
9
+ @names = Names.new(klass)
10
+ @options = options
11
+ end
12
+
13
+ def header(builder)
14
+ builder.instruct!
15
+ end
16
+
17
+ def schema(builder)
18
+ builder.xs :schema, ns("xs", :w3, :schema) do |schema|
19
+ Types.xsd(schema)
20
+ element_for_klass(schema)
21
+ generate(schema, @klass)
22
+ end
23
+ end
24
+
25
+ def element_for_klass(builder)
26
+ builder.xs :element, "name" => @names.element_collection, "type" => @names.collection_type
27
+ end
28
+
29
+ def generate(builder, klass)
30
+ nested_attributes.each do |nested_attribute|
31
+ next if nested_attribute.klass == klass || nested_attribute.klass == klass.superclass
32
+
33
+ nested_attribute.klass.generate_xsd(builder, klass, @options)
34
+ end
35
+
36
+ generate_complex_type_for_collection(builder)
37
+ generate_complex_type_for_model(builder)
38
+ end
39
+
40
+ def generate_complex_type_for_collection(builder)
41
+ builder.xs :complexType, "name" => @names.collection_type do |complex_type|
42
+ complex_type.xs :sequence do |sequence|
43
+ sequence.xs :element, "name" => @names.element, "type" => @names.type, "minOccurs" => "0", "maxOccurs" => "unbounded"
44
+ end
45
+ complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array"
46
+ end
47
+ end
48
+
49
+ def generate_complex_type_for_model(builder)
50
+ builder.xs :complexType, "name" => @names.type do |complex_type|
51
+ additional_methods = @klass.xsd_methods.merge(@options[:methods] || {})
52
+ ignored_methods = @klass.xsd_ignore_methods | (@options[:exclude] || [])
53
+ complex_type.xs :all do |all|
54
+ generate_column_elements(all, additional_methods, ignored_methods)
55
+
56
+ nested_attributes.each do |nested_attribute|
57
+ all.xs :element, "name" => "#{nested_attribute.name.to_s.dasherize}-attributes", "type" => nested_attribute.klass.xsd_generator.names.collection_type, "minOccurs" => "0", "maxOccurs" => "1"
58
+ end
59
+
60
+ generate_additional_methods(all, additional_methods)
61
+ end
62
+ end
63
+ end
64
+
65
+ def generate_column_elements(builder, additional_methods, ignored_methods)
66
+ @klass.columns.each do |column|
67
+ Column.new(@klass, column, additional_methods, ignored_methods).generate(builder)
68
+ end
69
+ end
70
+
71
+ def generate_additional_methods(builder, additional_methods)
72
+ additional_methods.each do |method_name, values|
73
+ method_xsd_name = method_name.to_s.dasherize
74
+ if values.present?
75
+ builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1" do |element|
76
+ element.xs :complexType do |complex_type|
77
+ if values.is_a?(Array)
78
+ complex_type.xs :sequence do |nested_sequence|
79
+ values.each do |value|
80
+ nested_sequence.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0", "maxOccurs" => "unbounded"
81
+ end
82
+ end
83
+ elsif values.is_a?(Hash)
84
+ complex_type.xs :all do |nested_all|
85
+ generate_additional_methods(nested_all, values)
86
+ end
87
+ else
88
+ raise "Additional methods must be a hash of hashes or hash of arrays"
89
+ end
90
+ complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
91
+ end
92
+ end
93
+ else
94
+ builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
95
+ end
96
+ end
97
+ end
98
+
99
+ def nested_attributes
100
+ @klass.reflect_on_all_associations.select do |association|
101
+ @klass.instance_methods.include?("#{association.name}_attributes=".to_sym) && association.options[:polymorphic] != true
102
+ end
103
+ end
104
+
105
+ def ns(ns, provider, key)
106
+ { "xmlns:#{ns}" => Namespaces::PROVIDERS[provider][key] }
107
+ end
108
+
109
+ end
110
+ end
111
+ end
@@ -7,133 +7,22 @@ module Schematic
7
7
  end
8
8
  end
9
9
 
10
+ def xsd_generator
11
+ @xsd_generator ||= Schematic::Generator::Xsd.new(self)
12
+ end
13
+
10
14
  def to_xsd(options = {})
11
15
  output = ""
12
16
  builder = Builder::XmlMarkup.new(:target => output, :indent => 2)
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
17
+ xsd_generator.options = options
18
+ xsd_generator.header(builder)
19
+ xsd_generator.schema(builder)
18
20
  output
19
21
  end
20
22
 
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"
74
- end
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"
88
- end
89
-
90
- generate_xsd_additional_methods(all, additional_methods)
91
- end
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"
104
- end
105
- end
106
- end
107
- end
108
- end
109
- end
110
-
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
- if values.is_a?(Array)
118
- complex_type.xs :sequence do |nested_sequence|
119
- values.each do |value|
120
- nested_sequence.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0", "maxOccurs" => "unbounded"
121
- end
122
- end
123
- elsif values.is_a?(Hash)
124
- complex_type.xs :all do |nested_all|
125
- generate_xsd_additional_methods(nested_all, values)
126
- end
127
- else
128
- raise "Additional methods must be a hash of hashes or hash of arrays"
129
- end
130
- complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
131
- end
132
- end
133
- else
134
- builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
135
- end
136
- end
23
+ def generate_xsd(builder, klass, options)
24
+ xsd_generator.options = options
25
+ xsd_generator.generate(builder, klass)
137
26
  end
138
27
 
139
28
  def xsd_methods
@@ -144,17 +33,6 @@ module Schematic
144
33
  []
145
34
  end
146
35
 
147
- def xsd_nested_attributes
148
- self.reflect_on_all_associations.select do |association|
149
- self.instance_methods.include?("#{association.name}_attributes=".to_sym) && association.options[:polymorphic] != true
150
- end
151
- end
152
-
153
- def xsd_columns
154
- self.columns
155
- end
156
-
157
-
158
36
  end
159
37
  end
160
38
  end