active_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,74 @@
1
+ module ActiveApi
2
+ class Definition
3
+ attr_reader :definition_name, :fields, :builder_class
4
+
5
+ def initialize(options)
6
+ @definition_name = options[:definition_name]
7
+ @builder_class = options[:builder_class] || ComplexType.to_s
8
+ @fields = options[:fields] || []
9
+ end
10
+
11
+ def attribute(name, type = :string, options = {})
12
+ field options.merge(:name => name, :type => type, :field_type => :attribute)
13
+ end
14
+
15
+ def element(name, type = :string, options = {})
16
+ send type, name, options
17
+ end
18
+
19
+ SimpleType.formats.each do |hash|
20
+ hash.each do |standard_name, xml_name|
21
+ define_method standard_name do |name, *options|
22
+ options = options.first || {}
23
+ field options.merge(:name => name, :type => standard_name, :klass => SimpleType)
24
+ end
25
+
26
+ if standard_name != xml_name
27
+ define_method xml_name do |name, *options|
28
+ options = options.first || {}
29
+ field options.merge(:name => name, :type => standard_name, :klass => SimpleType)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def belongs_to(name, options = {})
36
+ field options.merge(:name => name, :type => :belongs_to, :klass => ComplexType)
37
+ end
38
+
39
+ def has_one(name, options = {})
40
+ field options.merge(:name => name, :type => :has_one, :klass => ComplexType)
41
+ end
42
+
43
+ def has_many(name, options = {})
44
+ field options.merge(:name => name, :type => :has_many, :klass => Collection)
45
+ end
46
+
47
+ def attributes
48
+ fields.select do |field|
49
+ field.field_type == :attribute
50
+ end
51
+ end
52
+
53
+ def elements
54
+ fields.select do |field|
55
+ field.field_type == :element
56
+ end
57
+ end
58
+
59
+ def singular_name
60
+ name.to_s.singularize
61
+ end
62
+
63
+ def plural_name
64
+ name.to_s.pluralize
65
+ end
66
+
67
+ private
68
+
69
+ def field(options)
70
+ self.fields << Field.new(options)
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveApi
2
+ class Field
3
+ attr_reader :type, :name, :klass, :value, :choice, :field_type
4
+
5
+ def initialize(options = {})
6
+ @type = options[:type]
7
+ @name = options[:name]
8
+ @klass = options[:klass]
9
+ @value = options[:value]
10
+ @choice = options[:choice]
11
+ @field_type = options[:field_type] || :element
12
+ end
13
+
14
+ def name_for(object)
15
+ if choice
16
+ value = object.send(name)
17
+ return nil if value.nil?
18
+ return choice[value.class.to_s]
19
+ else
20
+ name
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveApi
2
+ module HasDefinition
3
+ def definition
4
+ schema.definitions.detect do |definition|
5
+ definition.definition_name == node.to_s.singularize.to_sym
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveApi
2
+
3
+ class Schema
4
+ class_inheritable_array :versions
5
+
6
+ class << self
7
+ def version(version, options = {})
8
+ options[:definition_class] ||= Definition
9
+ schema = Schema.new version, options
10
+ yield schema
11
+ write_inheritable_array :versions, [schema]
12
+ schema
13
+ end
14
+
15
+ def find(version)
16
+ versions.detect { |schema| schema.version == version }
17
+ end
18
+ end
19
+
20
+ attr_reader :version, :definitions, :definition_class
21
+ def initialize(version, options = {})
22
+ @version = version
23
+ @definitions = []
24
+ @definition_class = options[:definition_class]
25
+ end
26
+
27
+ def define(name, options = {})
28
+ options[:definition_name] = name
29
+ definition = definition_class.to_s.constantize.new options
30
+ yield definition if block_given?
31
+ definitions << definition
32
+ end
33
+
34
+ def build_xml(objects, options)
35
+ Collection.new(objects, options.merge(:schema => self)).build_xml
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,103 @@
1
+ module ActiveApi
2
+ class SimpleType
3
+ include Builder
4
+
5
+ class << self
6
+ def formats
7
+ standard_names = [
8
+ :base64Binary,
9
+ :boolean,
10
+ :date,
11
+ :dateTime,
12
+ :dateTimeStamp,
13
+ :decimal,
14
+ :integer,
15
+ :long,
16
+ :int,
17
+ :short,
18
+ :byte,
19
+ :nonNegativeInteger,
20
+ :positiveInteger,
21
+ :unsignedLong,
22
+ :unsignedInt,
23
+ :unsignedShort,
24
+ :unsignedByte,
25
+ :nonPositiveInteger,
26
+ :negativeInteger,
27
+ :double,
28
+ :duration,
29
+ :dayTimeDuration,
30
+ :yearMonthDuration,
31
+ :float,
32
+ :gDay,
33
+ :gMonth,
34
+ :gMonthDay,
35
+ :gYear,
36
+ :gYearMonth,
37
+ :hexBinary,
38
+ :precisionDecimal,
39
+ :string,
40
+ :normalizedString,
41
+ :token,
42
+ :language,
43
+ :time
44
+ ].map do |format|
45
+ {format.to_s.underscore.to_sym => format}
46
+ end
47
+
48
+ custom_names = [
49
+ {:any_uri => :anyURI},
50
+ {:notation => :NOTATION},
51
+ {:qname => :QName},
52
+ {:name => :Name},
53
+ {:nc_name => :NCName},
54
+ {:entity => :ENTITY},
55
+ {:id => :ID},
56
+ {:idref => :IDREF},
57
+ {:nmtoken => :NMTOKEN},
58
+ ]
59
+
60
+ standard_names + custom_names
61
+ end
62
+
63
+ def default_format_proc
64
+ proc { |value| value }
65
+ end
66
+
67
+ def format_procs
68
+ {
69
+ :normalized_string => proc {|value| value },
70
+ :token => proc {|value| value },
71
+ :date_time => proc {|value| value.strftime("%Y-%m-%dT%H:%M:%S%Z") },
72
+ :time => proc {|value| value.strftime("%H:%M:%S%Z") },
73
+ :date => proc {|value| value.strftime("%Y-%m-%d") },
74
+ :any_uri => proc {|value| URI.escape(value) }
75
+ }
76
+ end
77
+ end
78
+
79
+ attr_reader :text, :node, :format
80
+
81
+ def initialize(text, options)
82
+ @text = text
83
+ @node = options[:node]
84
+ @format = options[:format]
85
+ end
86
+
87
+ def append(hash)
88
+ hash[node] = formatted_text
89
+ end
90
+
91
+ protected
92
+
93
+ def build(builder)
94
+ builder.send "#{node}_", formatted_text
95
+ end
96
+
97
+ def formatted_text
98
+ return nil if text.blank?
99
+ (self.class.format_procs[format] || self.class.default_format_proc).call(text)
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec/spec_helper'
2
+
3
+ module ActiveApi
4
+ describe ComplexType do
5
+
6
+ before do
7
+ @article = Article.new
8
+ @article.id = 1
9
+ @article.title = "Some title"
10
+
11
+ @schema = Schema.version(:v1) do |xsl|
12
+ xsl.define :article do |t|
13
+ t.string :title
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "with a definition with fields" do
19
+ it "emits the node and all fields within the node" do
20
+ element = ComplexType.new @article, :node => :article, :schema => @schema
21
+ doc = element.build_xml.doc
22
+ doc.xpath("/article/title").first.inner_text.should == @article.title
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec/spec_helper'
2
+
3
+ module ActiveApi
4
+ describe Definition do
5
+
6
+ describe "#class_symbol" do
7
+ it "returns what is passed in" do
8
+ Definition.new(:definition_name => :article).definition_name.should == :article
9
+ end
10
+ end
11
+
12
+ describe "#fields" do
13
+ it "is empty by default" do
14
+ Definition.new(:definition_name => :article).fields.should be_empty
15
+ end
16
+ end
17
+
18
+ [:string, :has_many, :belongs_to, :has_one].each do |method_name|
19
+ describe "##{method_name}" do
20
+ it "adds a #{method_name} field with the options" do
21
+ definition = Definition.new(:definition_name => :article)
22
+ definition.send method_name, :title
23
+ definition.fields.length.should == 1
24
+ definition.fields.first.type.should == method_name
25
+ definition.fields.first.name.should == :title
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module ActiveApi
4
+ describe Field do
5
+ it "has a type" do
6
+ definition = Field.new :type => :string
7
+ definition.type.should == :string
8
+ end
9
+
10
+ it "has a name" do
11
+ definition = Field.new :name => :title
12
+ definition.name.should == :title
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec/spec_helper'
2
+
3
+ module ActiveApi
4
+ describe Schema do
5
+
6
+ before do
7
+ Schema.version(:v1) do |schema|
8
+ schema.define :article do |t|
9
+ t.string :title
10
+ t.has_many :comments
11
+ end
12
+ end
13
+ end
14
+
15
+ describe ".define" do
16
+ describe "with a single field" do
17
+
18
+ it "adds one definition of the correct type" do
19
+ schema = Schema.find(:v1)
20
+ schema.definitions.length.should == 1
21
+ definition = schema.definitions.first
22
+ definition.should be_kind_of(Definition)
23
+ definition.definition_name.should == :article
24
+ end
25
+
26
+ it "sets the fields on the definition correctly" do
27
+ definition = Schema.find(:v1).definitions.first
28
+ definition.fields.length.should == 2
29
+ field = definition.fields.first
30
+ field.type.should == :string
31
+ field.name.should == :title
32
+ end
33
+ end
34
+
35
+ describe "with a multiple fields" do
36
+
37
+ it "adds one definition of the correct type article" do
38
+ schema = Schema.find(:v1)
39
+ schema.definitions.length.should == 1
40
+ definition = schema.definitions.first
41
+ definition.should be_kind_of(Definition)
42
+ definition.definition_name.should == :article
43
+ end
44
+
45
+ it "sets the fields on the definition correctly" do
46
+ definition = Schema.find(:v1).definitions.first
47
+ definition.fields.length.should == 2
48
+ field1 = definition.fields.first
49
+ field1.type.should == :string
50
+ field1.name.should == :title
51
+ field2 = definition.fields.last
52
+ field2.type.should == :has_many
53
+ field2.name.should == :comments
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec/spec_helper'
2
+
3
+ module ActiveApi
4
+ describe SimpleType do
5
+
6
+ it "builds the node with the value" do
7
+ element = SimpleType.new "foo", :node => :bar
8
+ doc = element.build_xml.doc
9
+ doc.xpath("/bar").first.inner_text.should == "foo"
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,367 @@
1
+ require 'spec/spec_helper'
2
+
3
+ module ActiveApi
4
+ describe "Defining a schema" do
5
+
6
+ describe "calling a collection from a schema" do
7
+ before do
8
+ @schema = Schema.version(:v1) do |xsl|
9
+ xsl.define :article do |t|
10
+ t.element :id, :string
11
+ end
12
+ end
13
+ @article = Article.new :id => 456
14
+ end
15
+
16
+ it "emits the string element" do
17
+ element = Schema.find(:v1).build_xml [@article], :node => :article
18
+ doc = element.doc
19
+ doc.xpath("/articles/article/id").inner_text.should == "456"
20
+ end
21
+ end
22
+
23
+ describe "with an element of type string" do
24
+ before do
25
+ @schema = Schema.version(:v1) do |xsl|
26
+ xsl.define :article do |t|
27
+ t.element :id, :string
28
+ end
29
+ end
30
+ @article = Article.new :id => 456
31
+ end
32
+
33
+ it "emits the string element" do
34
+ element = Collection.new [@article], :node => :article, :schema => @schema
35
+ doc = element.build_xml.doc
36
+ doc.xpath("/articles/article/id").inner_text.should == "456"
37
+ end
38
+ end
39
+
40
+ describe "with a string" do
41
+ before do
42
+ @schema = Schema.version(:v1) do |xsl|
43
+ xsl.define :article do |t|
44
+ t.string :title
45
+ end
46
+ end
47
+ @article = Article.new :title => Faker::Company.bs
48
+ end
49
+
50
+ it "emits the string element" do
51
+ element = Collection.new [@article], :node => :article, :schema => @schema
52
+ doc = element.build_xml.doc
53
+ doc.xpath("/articles/article/title").first.inner_text.should == @article.title
54
+ end
55
+ end
56
+
57
+ describe "with an attribute of type string" do
58
+ before do
59
+ @schema = Schema.version(:v1) do |xsl|
60
+ xsl.define :article do |t|
61
+ t.attribute :id
62
+ end
63
+ end
64
+ @article = Article.new :id => 456
65
+ end
66
+
67
+ it "emits the string attribute" do
68
+ element = Collection.new [@article], :node => :article, :schema => @schema
69
+ doc = element.build_xml.doc
70
+ doc.xpath("/articles/article[@id=456]").should be
71
+ end
72
+ end
73
+
74
+ describe "with other data types" do
75
+ before do
76
+ @article = Article.new :published_on => Date.parse("3/5/1956")
77
+ end
78
+
79
+ it "emits the correctly formatted element" do
80
+ @schema = Schema.version(:v1) do |xsl|
81
+ xsl.define :article do |t|
82
+ t.date :published_on
83
+ end
84
+ end
85
+ element = Collection.new [@article], :node => :article, :schema => @schema
86
+ doc = element.build_xml.doc
87
+ doc.xpath("/articles/article/published_on").first.inner_text.should == "1956-03-05"
88
+ end
89
+
90
+ it "emits nil when the value is nil" do
91
+ @schema = Schema.version(:v1) do |xsl|
92
+ xsl.define :article do |t|
93
+ t.date :published_on
94
+ end
95
+ end
96
+ @article.published_on = nil
97
+ element = Collection.new [@article], :node => :article, :schema => @schema
98
+ doc = element.build_xml.doc
99
+ doc.xpath("/articles/article/published_on").first.inner_text.should == ""
100
+ end
101
+
102
+ it "emits the correctly formatted attribute" do
103
+ @schema = Schema.version(:v1) do |xsl|
104
+ xsl.define :article do |t|
105
+ t.attribute :published_on, :type => :date
106
+ end
107
+ end
108
+ element = Collection.new [@article], :node => :article, :schema => @schema
109
+ doc = element.build_xml.doc
110
+ doc.xpath("/articles/article[@published_on=1956-03-05]").should be
111
+ end
112
+
113
+ it "emits nil when the attribute value is nil" do
114
+ @schema = Schema.version(:v1) do |xsl|
115
+ xsl.define :article do |t|
116
+ t.attribute :published_on, :type => :date
117
+ end
118
+ end
119
+ @article.published_on = nil
120
+ element = Collection.new [@article], :node => :article, :schema => @schema
121
+ doc = element.build_xml.doc
122
+ doc.xpath("/articles/article[@published_on='']").should be
123
+ end
124
+ end
125
+
126
+ describe "with a has_many element" do
127
+ before do
128
+ @schema = Schema.version(:v1) do |xsl|
129
+ xsl.define :article do |t|
130
+ t.has_many :comments
131
+ end
132
+ end
133
+
134
+ @article = Article.new :title => Faker::Company.bs
135
+ @comment = Comment.new :article => @article, :text => Faker::Company.bs
136
+ @article.comments = [@comment]
137
+ end
138
+
139
+ it "emits each of the child objects" do
140
+ @schema.define :comment do |t|
141
+ t.string :text
142
+ end
143
+
144
+ element = Collection.new [@article],
145
+ :node => :article,
146
+ :schema => @schema
147
+ doc = element.build_xml.doc
148
+ doc.xpath("/articles/article/comments/comment/text").first.inner_text.should == @comment.text
149
+ end
150
+ end
151
+
152
+ describe "with custom value procs" do
153
+ before do
154
+ @schema = Schema.version(:v1) do |xsl|
155
+ xsl.define :article do |t|
156
+ t.attribute :id, :value => proc {|attribute| "foo" }
157
+ t.string :id, :value => proc {|element| "foo" }
158
+ t.has_many :comments
159
+ end
160
+
161
+ xsl.define :comment do |t|
162
+ t.string :article_title, :value => proc{|element| element.object.article.title }
163
+ t.belongs_to :user
164
+ end
165
+
166
+ xsl.define :user do |t|
167
+ t.string :title, :value => proc{|element| element.parents[:article].title }
168
+ end
169
+ end
170
+
171
+ @article1 = Article.new :title => Faker::Company.bs
172
+ @article2 = Article.new :title => Faker::Lorem.sentence
173
+
174
+ @user = User.new
175
+ @comment1 = Comment.new :article => @article1, :user => @user
176
+ @comment2 = Comment.new :article => @article2, :user => @user
177
+
178
+ @article1.comments = [@comment1]
179
+ @article2.comments = [@comment2]
180
+ end
181
+
182
+ it "emits the value of the value proc for attributes" do
183
+ element = Collection.new [@article1],
184
+ :node => :article,
185
+ :schema => @schema
186
+ doc = element.build_xml.doc
187
+ doc.xpath("/articles/article[@id=foo]").should be
188
+ end
189
+
190
+ it "emits the value of the value proc for elements" do
191
+ element = Collection.new [@article1],
192
+ :node => :article,
193
+ :schema => @schema
194
+ doc = element.build_xml.doc
195
+ doc.xpath("/articles/article/id").first.inner_text.should == "foo"
196
+ end
197
+
198
+ it "emits the value of the value proc, which is passed an element containing a reference to the object" do
199
+ element = Collection.new [@article1],
200
+ :node => :article,
201
+ :schema => @schema
202
+ doc = element.build_xml.doc
203
+ doc.xpath("/articles/article/comments/comment/article_title").first.inner_text.should == @article1.title
204
+ end
205
+
206
+ it "emits the value of the value proc, which is passed an element containing a reference all ancestor objects" do
207
+ element = Collection.new [@article1, @article2],
208
+ :node => :article,
209
+ :schema => @schema
210
+ doc = element.build_xml.doc
211
+ doc.xpath("/articles/article").length.should == 2
212
+ doc.xpath("/articles/article/comments/comment/user/title").first.inner_text.should == @article1.title
213
+ doc.xpath("/articles/article/comments/comment/user/title").last.inner_text.should == @article2.title
214
+ end
215
+ end
216
+
217
+ describe "with belongs_to elements marked as choice" do
218
+ before do
219
+ @schema = Schema.version(:v1) do |xsl|
220
+ xsl.define :article do |t|
221
+ t.string :title
222
+ end
223
+
224
+ xsl.define :user do |t|
225
+ t.string :username
226
+ end
227
+
228
+ xsl.define :comment do |t|
229
+ t.belongs_to :commentable, :choice => {
230
+ "Article" => :article,
231
+ "User" => :user
232
+ }
233
+ end
234
+ end
235
+
236
+ @article = Article.new :title => Faker::Company.bs
237
+ @user = User.new :username => Faker::Internet.user_name
238
+ @comment1 = Comment.new :commentable => @article
239
+ @comment2 = Comment.new :commentable => @user
240
+ @comment3 = Comment.new :commentable => nil
241
+ end
242
+
243
+ it "uses the name of the class to lookup the definition to be used" do
244
+ element = Collection.new [@comment1, @comment2, @comment3], :node => :comment, :schema => @schema
245
+ doc = element.build_xml.doc
246
+ doc.xpath("/comments/comment").length.should == 3
247
+ doc.xpath("/comments/comment/article/title").inner_text.should == @article.title
248
+ doc.xpath("/comments/comment/user/username").inner_text.should == @user.username
249
+ doc.xpath("/comments/comment")[2].inner_text.should be_empty
250
+ end
251
+ end
252
+
253
+ describe "custom builders" do
254
+ before do
255
+ @schema = Schema.version(:v1) do |xsl|
256
+ xsl.define :article, :builder_class => "ActiveApi::MyCustomClass"
257
+ end
258
+
259
+ class MyCustomClass < ActiveApi::ComplexType
260
+ def build(builder)
261
+ builder.send :foo, :bar => "baz" do |xml|
262
+ xml.send :woot, "lol"
263
+ end
264
+ end
265
+ end
266
+
267
+ @article = Article.new :id => 456
268
+ end
269
+
270
+ it "uses the custom builder class" do
271
+ element = Schema.find(:v1).build_xml [@article], :node => :article
272
+ doc = element.doc
273
+ doc.xpath("/articles/foo[@bar='baz']/woot").first.inner_text.should == "lol"
274
+ end
275
+ end
276
+
277
+ describe "specifying a symbol as a value proc" do
278
+ before do
279
+ @schema = Schema.version(:v1) do |xsl|
280
+ xsl.define :article do |t|
281
+ t.string :foo, :value => :title
282
+ end
283
+ end
284
+
285
+ @article = Article.new :title => Faker::Company.bs
286
+ end
287
+
288
+ it "uses the custom builder class" do
289
+ element = Schema.find(:v1).build_xml [@article], :node => :article
290
+ doc = element.doc
291
+ doc.xpath("/articles/article/foo").first.inner_text.should == @article.title
292
+ end
293
+ end
294
+
295
+ describe "specifying a string literal as a value" do
296
+ before do
297
+ @schema = Schema.version(:v1) do |xsl|
298
+ xsl.define :article do |t|
299
+ t.string :foo, :value => "some value"
300
+ end
301
+ end
302
+
303
+ @article = Article.new :title => Faker::Company.bs
304
+ end
305
+
306
+ it "uses the custom builder class" do
307
+ element = Schema.find(:v1).build_xml [@article], :node => :article
308
+ doc = element.doc
309
+ doc.xpath("/articles/article/foo").first.inner_text.should == "some value"
310
+ end
311
+ end
312
+
313
+ describe "specifying custom definition classes with a string" do
314
+ before do
315
+ class MyDefinitionClass < Definition
316
+ def timestamps
317
+ date_time :created_at
318
+ date_time :updated_at
319
+ end
320
+ end
321
+
322
+ @schema = Schema.version(:v1, :definition_class => "ActiveApi::MyDefinitionClass") do |xsl|
323
+ xsl.define :article do |t|
324
+ t.timestamps
325
+ end
326
+ end
327
+
328
+ @article = Article.new :created_at => Date.parse("12/21/1945"), :updated_at => Date.parse("4/5/1992")
329
+ end
330
+
331
+ it "uses the custom builder class" do
332
+ element = Schema.find(:v1).build_xml [@article], :node => :article
333
+ doc = element.doc
334
+ doc.xpath("/articles/article/created_at").first.inner_text.should be_starts_with("1945-12-21")
335
+ doc.xpath("/articles/article/updated_at").first.inner_text.should be_starts_with("1992-04-05")
336
+ end
337
+ end
338
+
339
+ describe "specifying custom definition classes with a class" do
340
+ before do
341
+ class MyDefinitionClass < Definition
342
+ def timestamps
343
+ date_time :created_at
344
+ date_time :updated_at
345
+ end
346
+ end
347
+
348
+ @schema = Schema.version(:v1, :definition_class => MyDefinitionClass) do |xsl|
349
+ xsl.define :article do |t|
350
+ t.timestamps
351
+ end
352
+ end
353
+
354
+ @article = Article.new :created_at => Date.parse("12/21/1945"), :updated_at => Date.parse("4/5/1992")
355
+ end
356
+
357
+ it "uses the custom builder class" do
358
+ element = Schema.find(:v1).build_xml [@article], :node => :article
359
+ doc = element.doc
360
+ doc.xpath("/articles/article/created_at").first.inner_text.should be_starts_with("1945-12-21")
361
+ doc.xpath("/articles/article/updated_at").first.inner_text.should be_starts_with("1992-04-05")
362
+ end
363
+ end
364
+
365
+ end
366
+ end
367
+