zilkey-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,102 @@
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
+ (self.class.format_procs[format] || self.class.default_format_proc).call(text)
99
+ end
100
+
101
+ end
102
+ end
data/lib/active_api.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require 'nokogiri'
4
+
5
+ require 'uri'
6
+
7
+ require 'active_api/builder'
8
+ require 'active_api/has_definition'
9
+ require 'active_api/collection'
10
+ require 'active_api/complex_type'
11
+ require 'active_api/simple_type'
12
+ require 'active_api/field'
13
+ require 'active_api/schema'
14
+ require 'active_api/definition'
@@ -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
data/spec/api_spec.rb ADDED
@@ -0,0 +1,343 @@
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 the correctly formatted element" do
91
+ @schema = Schema.version(:v1) do |xsl|
92
+ xsl.define :article do |t|
93
+ t.attribute :published_on, :type => :date
94
+ end
95
+ end
96
+ element = Collection.new [@article], :node => :article, :schema => @schema
97
+ doc = element.build_xml.doc
98
+ doc.xpath("/articles/article[@published_on=1956-03-05]").should be
99
+ end
100
+ end
101
+
102
+ describe "with a has_many element" do
103
+ before do
104
+ @schema = Schema.version(:v1) do |xsl|
105
+ xsl.define :article do |t|
106
+ t.has_many :comments
107
+ end
108
+ end
109
+
110
+ @article = Article.new :title => Faker::Company.bs
111
+ @comment = Comment.new :article => @article, :text => Faker::Company.bs
112
+ @article.comments = [@comment]
113
+ end
114
+
115
+ it "emits each of the child objects" do
116
+ @schema.define :comment do |t|
117
+ t.string :text
118
+ end
119
+
120
+ element = Collection.new [@article],
121
+ :node => :article,
122
+ :schema => @schema
123
+ doc = element.build_xml.doc
124
+ doc.xpath("/articles/article/comments/comment/text").first.inner_text.should == @comment.text
125
+ end
126
+ end
127
+
128
+ describe "with custom value procs" do
129
+ before do
130
+ @schema = Schema.version(:v1) do |xsl|
131
+ xsl.define :article do |t|
132
+ t.attribute :id, :value => proc {|attribute| "foo" }
133
+ t.string :id, :value => proc {|element| "foo" }
134
+ t.has_many :comments
135
+ end
136
+
137
+ xsl.define :comment do |t|
138
+ t.string :article_title, :value => proc{|element| element.object.article.title }
139
+ t.belongs_to :user
140
+ end
141
+
142
+ xsl.define :user do |t|
143
+ t.string :title, :value => proc{|element| element.parents[:article].title }
144
+ end
145
+ end
146
+
147
+ @article1 = Article.new :title => Faker::Company.bs
148
+ @article2 = Article.new :title => Faker::Lorem.sentence
149
+
150
+ @user = User.new
151
+ @comment1 = Comment.new :article => @article1, :user => @user
152
+ @comment2 = Comment.new :article => @article2, :user => @user
153
+
154
+ @article1.comments = [@comment1]
155
+ @article2.comments = [@comment2]
156
+ end
157
+
158
+ it "emits the value of the value proc for attributes" do
159
+ element = Collection.new [@article1],
160
+ :node => :article,
161
+ :schema => @schema
162
+ doc = element.build_xml.doc
163
+ doc.xpath("/articles/article[@id=foo]").should be
164
+ end
165
+
166
+ it "emits the value of the value proc for elements" do
167
+ element = Collection.new [@article1],
168
+ :node => :article,
169
+ :schema => @schema
170
+ doc = element.build_xml.doc
171
+ doc.xpath("/articles/article/id").first.inner_text.should == "foo"
172
+ end
173
+
174
+ it "emits the value of the value proc, which is passed an element containing a reference to the object" do
175
+ element = Collection.new [@article1],
176
+ :node => :article,
177
+ :schema => @schema
178
+ doc = element.build_xml.doc
179
+ doc.xpath("/articles/article/comments/comment/article_title").first.inner_text.should == @article1.title
180
+ end
181
+
182
+ it "emits the value of the value proc, which is passed an element containing a reference all ancestor objects" do
183
+ element = Collection.new [@article1, @article2],
184
+ :node => :article,
185
+ :schema => @schema
186
+ doc = element.build_xml.doc
187
+ doc.xpath("/articles/article").length.should == 2
188
+ doc.xpath("/articles/article/comments/comment/user/title").first.inner_text.should == @article1.title
189
+ doc.xpath("/articles/article/comments/comment/user/title").last.inner_text.should == @article2.title
190
+ end
191
+ end
192
+
193
+ describe "with belongs_to elements marked as choice" do
194
+ before do
195
+ @schema = Schema.version(:v1) do |xsl|
196
+ xsl.define :article do |t|
197
+ t.string :title
198
+ end
199
+
200
+ xsl.define :user do |t|
201
+ t.string :username
202
+ end
203
+
204
+ xsl.define :comment do |t|
205
+ t.belongs_to :commentable, :choice => {
206
+ "Article" => :article,
207
+ "User" => :user
208
+ }
209
+ end
210
+ end
211
+
212
+ @article = Article.new :title => Faker::Company.bs
213
+ @user = User.new :username => Faker::Internet.user_name
214
+ @comment1 = Comment.new :commentable => @article
215
+ @comment2 = Comment.new :commentable => @user
216
+ @comment3 = Comment.new :commentable => nil
217
+ end
218
+
219
+ it "uses the name of the class to lookup the definition to be used" do
220
+ element = Collection.new [@comment1, @comment2, @comment3], :node => :comment, :schema => @schema
221
+ doc = element.build_xml.doc
222
+ doc.xpath("/comments/comment").length.should == 3
223
+ doc.xpath("/comments/comment/article/title").inner_text.should == @article.title
224
+ doc.xpath("/comments/comment/user/username").inner_text.should == @user.username
225
+ doc.xpath("/comments/comment")[2].inner_text.should be_empty
226
+ end
227
+ end
228
+
229
+ describe "custom builders" do
230
+ before do
231
+ @schema = Schema.version(:v1) do |xsl|
232
+ xsl.define :article, :builder_class => "ActiveApi::MyCustomClass"
233
+ end
234
+
235
+ class MyCustomClass < ActiveApi::ComplexType
236
+ def build(builder)
237
+ builder.send :foo, :bar => "baz" do |xml|
238
+ xml.send :woot, "lol"
239
+ end
240
+ end
241
+ end
242
+
243
+ @article = Article.new :id => 456
244
+ end
245
+
246
+ it "uses the custom builder class" do
247
+ element = Schema.find(:v1).build_xml [@article], :node => :article
248
+ doc = element.doc
249
+ doc.xpath("/articles/foo[@bar='baz']/woot").first.inner_text.should == "lol"
250
+ end
251
+ end
252
+
253
+ describe "specifying a symbol as a value proc" do
254
+ before do
255
+ @schema = Schema.version(:v1) do |xsl|
256
+ xsl.define :article do |t|
257
+ t.string :foo, :value => :title
258
+ end
259
+ end
260
+
261
+ @article = Article.new :title => Faker::Company.bs
262
+ end
263
+
264
+ it "uses the custom builder class" do
265
+ element = Schema.find(:v1).build_xml [@article], :node => :article
266
+ doc = element.doc
267
+ doc.xpath("/articles/article/foo").first.inner_text.should == @article.title
268
+ end
269
+ end
270
+
271
+ describe "specifying a string literal as a value" do
272
+ before do
273
+ @schema = Schema.version(:v1) do |xsl|
274
+ xsl.define :article do |t|
275
+ t.string :foo, :value => "some value"
276
+ end
277
+ end
278
+
279
+ @article = Article.new :title => Faker::Company.bs
280
+ end
281
+
282
+ it "uses the custom builder class" do
283
+ element = Schema.find(:v1).build_xml [@article], :node => :article
284
+ doc = element.doc
285
+ doc.xpath("/articles/article/foo").first.inner_text.should == "some value"
286
+ end
287
+ end
288
+
289
+ describe "specifying custom definition classes with a string" do
290
+ before do
291
+ class MyDefinitionClass < Definition
292
+ def timestamps
293
+ date_time :created_at
294
+ date_time :updated_at
295
+ end
296
+ end
297
+
298
+ @schema = Schema.version(:v1, :definition_class => "ActiveApi::MyDefinitionClass") do |xsl|
299
+ xsl.define :article do |t|
300
+ t.timestamps
301
+ end
302
+ end
303
+
304
+ @article = Article.new :created_at => Date.parse("12/21/1945"), :updated_at => Date.parse("4/5/1992")
305
+ end
306
+
307
+ it "uses the custom builder class" do
308
+ element = Schema.find(:v1).build_xml [@article], :node => :article
309
+ doc = element.doc
310
+ doc.xpath("/articles/article/created_at").first.inner_text.should be_starts_with("1945-12-21")
311
+ doc.xpath("/articles/article/updated_at").first.inner_text.should be_starts_with("1992-04-05")
312
+ end
313
+ end
314
+
315
+ describe "specifying custom definition classes with a class" do
316
+ before do
317
+ class MyDefinitionClass < Definition
318
+ def timestamps
319
+ date_time :created_at
320
+ date_time :updated_at
321
+ end
322
+ end
323
+
324
+ @schema = Schema.version(:v1, :definition_class => MyDefinitionClass) do |xsl|
325
+ xsl.define :article do |t|
326
+ t.timestamps
327
+ end
328
+ end
329
+
330
+ @article = Article.new :created_at => Date.parse("12/21/1945"), :updated_at => Date.parse("4/5/1992")
331
+ end
332
+
333
+ it "uses the custom builder class" do
334
+ element = Schema.find(:v1).build_xml [@article], :node => :article
335
+ doc = element.doc
336
+ doc.xpath("/articles/article/created_at").first.inner_text.should be_starts_with("1945-12-21")
337
+ doc.xpath("/articles/article/updated_at").first.inner_text.should be_starts_with("1992-04-05")
338
+ end
339
+ end
340
+
341
+ end
342
+ end
343
+
data/spec/domain.rb ADDED
@@ -0,0 +1,42 @@
1
+ class ObjectHelper
2
+ class << self
3
+ def fields(*args)
4
+ args.each do |field|
5
+ field(field)
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ def field(field_name)
12
+ define_method field_name do
13
+ attributes[field_name]
14
+ end
15
+ define_method "#{field_name}=" do |value|
16
+ attributes[field_name] = value
17
+ end
18
+ end
19
+ end
20
+
21
+ attr_reader :attributes
22
+ def initialize(options = {})
23
+ @attributes = options
24
+ end
25
+
26
+ end
27
+
28
+ class Author < ObjectHelper
29
+ fields :id, :name
30
+ end
31
+
32
+ class Article < ObjectHelper
33
+ fields :id, :author, :title, :published_on, :comments, :created_at, :updated_at
34
+ end
35
+
36
+ class User < ObjectHelper
37
+ fields :id, :username
38
+ end
39
+
40
+ class Comment < ObjectHelper
41
+ fields :id, :article, :user, :text, :commentable
42
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'active_api'
6
+ require 'faker'
7
+ require 'rr'
8
+ require 'domain'
9
+
10
+ module SchemaHelper
11
+ def reset_schema
12
+ ActiveApi::Schema.reset_inheritable_attributes
13
+ ActiveApi::Schema.versions = []
14
+ end
15
+ end
16
+
17
+ Spec::Runner.configure do |config|
18
+ config.mock_with :rr
19
+ config.include SchemaHelper
20
+ config.before(:each) { reset_schema }
21
+ end