zilkey-active_api 0.1.0

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.
@@ -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