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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .idea
7
+ junk*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jeff Dean
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ = ActiveApi
2
+
3
+ ActiveApi allows you to define an XML schema in Ruby, and use that schema to convert ruby objects to xml.
4
+
5
+ == Theory
6
+
7
+ I think that exposing your data via XML is not the job of the model. Instead, it's the job of a class (or classes) that is specifically designed to translate your model schema into a schema appropriate for public consumption.
8
+
9
+ A good api tool will have built-in support for:
10
+
11
+ * XSD or DTD generation
12
+ * Versioning
13
+ * The ability to represent your model in a way that is not tightly coupled to the model itself
14
+
15
+ ActiveApi attempts to do this.
16
+
17
+ == Usage
18
+
19
+ You define a schema like so:
20
+
21
+ Schema.version(:v1) do |schema|
22
+ schema.define :article do |t|
23
+ t.attribute :id
24
+ t.string :title
25
+ t.date :published_on
26
+ t.has_many :comments
27
+ end
28
+
29
+ schema.define :comment do |t|
30
+ t.belongs_to :user
31
+ t.string :article_title, :value => proc{|element|
32
+ element.object.article.title
33
+ }
34
+ end
35
+
36
+ schema.define :user do |t|
37
+ t.string :username, :value => :user_name
38
+ end
39
+ end
40
+
41
+ To create xml from this schema:
42
+
43
+ @v1 = ActiveApi::Schema.find(:v1)
44
+ @element = @v1.build_xml [@article1, @article2], :node => :article
45
+ @element.to_xml
46
+
47
+ Which will give you xml output that looks like this:
48
+
49
+ <?xml version="1.0"?>
50
+ <articles>
51
+ <article id="1">
52
+ <title>target efficient applications</title>
53
+ <published_on>2004-08-22</published_on>
54
+ <comments>
55
+ <comment>
56
+ <article_title>target efficient applications</article_title>
57
+ <user>
58
+ <username>foo</username>
59
+ </user>
60
+ </comment>
61
+ </comments>
62
+ </article>
63
+ <article id="2">
64
+ <title>recontextualize viral e-services</title>
65
+ <published_on>2004-12-05</published_on>
66
+ <comments>
67
+ <comment>
68
+ <article_title>recontextualize viral e-services</article_title>
69
+ <user>
70
+ <username>foo</username>
71
+ </user>
72
+ </comment>
73
+ </comments>
74
+ </article>
75
+ </articles>
76
+
77
+ == Extending
78
+
79
+ ActiveApi is highly extensible. The general pattern used to extend ActiveApi is to subclass the default ActiveApi implementation, and specify that you'd like to use your subclass instead.
80
+
81
+ For example, if you are working with a database that has audit columns such as timestamps, you might want to do this:
82
+
83
+ class MyDefinitionClass < ActiveApi::Definition
84
+ def timestamps
85
+ date_time :created_at
86
+ date_time :updated_at
87
+ end
88
+ end
89
+
90
+ @schema = Schema.version(:v1, :definition_class => MyDefinitionClass) do |xsl|
91
+ xsl.define :article do |t|
92
+ t.timestamps
93
+ end
94
+ end
95
+
96
+ Schema.find(:v1).build_xml [@article], :node => :article
97
+
98
+ Which will produce the following xml:
99
+
100
+ <?xml version="1.0"?>
101
+ <articles>
102
+ <article>
103
+ <created_at>1945-12-21T00:00:00+00:00</created_at>
104
+ <updated_at>1992-04-05T00:00:00+00:00</updated_at>
105
+ </article>
106
+ </articles>
107
+
108
+ NOTE: when specifying custom definition classes, those classes must be loaded before calling Schema.version
109
+
110
+ You can also create custom classes for any element you define, like so:
111
+
112
+ @schema = Schema.version(:v1) do |xsl|
113
+ xsl.define :article, :builder_class => "MyCustomClass"
114
+ end
115
+
116
+ class MyCustomClass < ActiveApi::ComplexType
117
+ def build(builder)
118
+ builder.send :foo, :bar => "baz" do |xml|
119
+ xml.send :woot, "lol"
120
+ end
121
+ end
122
+ end
123
+
124
+ Schema.find(:v1).build_xml [@article], :node => :article
125
+
126
+ Which will produce the following xml:
127
+
128
+ <?xml version="1.0"?>
129
+ <articles>
130
+ <foo bar="baz">
131
+ <woot>lol</woot>
132
+ </foo>
133
+ </articles>
134
+
135
+ NOTE: since the builder classes are evaluated at runtime, you can specify a string name for the class, and the class does _not_ have to be loaded before calling Schema.version
136
+
137
+ == Features
138
+
139
+ You define your schema completely separately from your data. So you could in theory render multiple types of objects with the same schema, provided that they have the same interface. You could also render a single object in any number of ways.
140
+
141
+ You can choose to have the builder send methods on your object, or provide more complex values by using the :value => proc{} syntax.
142
+
143
+ The element keeps track of all of it's ancestors, so you can emit certain elements conditionally based on what has come before them.
144
+
145
+ The Schema definition just creates an array of Ruby objects, which you could use to create documentation.
146
+
147
+ You define your schema versions using whatever versioning scheme you want - could be a string, symbol or any other object. You can render the same objects with different schemas easily. This library is totally agnostic as to how or if you version your schemas.
148
+
149
+ The schema defining DSL allows you to define any valid XSL data format.
150
+
151
+ == Implementation
152
+
153
+ ActiveApi uses's Nokogori::XML::Builder to create the xml nodes. As such, the creation of the xml is fast. The rest of the code likely needs major refactoring to be performant and have a small footprint with large datasets.
154
+
155
+ == Authors
156
+
157
+ While I (Jeff Dean) wrote all of the code in this repo, the code was inspired by pairing on a similar project with:
158
+
159
+ * Mike Dalessio
160
+ * Peter Jaros
161
+
162
+ == Development
163
+
164
+ The basic idea here is to create a DSL that can provide:
165
+
166
+ * A valid xml schema that you can link to and distribute
167
+ * A means to document your code
168
+ * A bunch of useful, time-saving ways to easily translate your model to a consumable xml document
169
+
170
+ See the issue tracker on github for a complete list.
171
+
172
+ == Copyright
173
+
174
+ Copyright (c) 2009 Jeff Dean. See LICENSE for details.
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "active_api"
8
+ gem.summary = %Q{An api layer for ruby}
9
+ gem.email = "jeff@zilkey.com"
10
+ gem.homepage = "http://github.com/zilkey/active_api"
11
+ gem.authors = ["Jeff Dean"]
12
+ gem.add_dependency('nokogiri', '>= 1.3.2')
13
+ gem.add_dependency('activesupport', '>= 2.3.2')
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+
34
+ task :default => :spec
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ if File.exist?('VERSION.yml')
39
+ config = YAML.load(File.read('VERSION.yml'))
40
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
41
+ else
42
+ version = ""
43
+ end
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "active_api #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
50
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,73 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{active_api}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jeff Dean"]
9
+ s.date = %q{2009-07-05}
10
+ s.email = %q{jeff@zilkey.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "active_api.gemspec",
23
+ "geminstaller.yml",
24
+ "lib/active_api.rb",
25
+ "lib/active_api/builder.rb",
26
+ "lib/active_api/collection.rb",
27
+ "lib/active_api/complex_type.rb",
28
+ "lib/active_api/definition.rb",
29
+ "lib/active_api/field.rb",
30
+ "lib/active_api/has_definition.rb",
31
+ "lib/active_api/schema.rb",
32
+ "lib/active_api/simple_type.rb",
33
+ "spec/active_api/complex_type_spec.rb",
34
+ "spec/active_api/definition_spec.rb",
35
+ "spec/active_api/field_spec.rb",
36
+ "spec/active_api/schema_spec.rb",
37
+ "spec/active_api/simple_type_spec.rb",
38
+ "spec/api_spec.rb",
39
+ "spec/domain.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/zilkey/active_api}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.3}
46
+ s.summary = %q{An api layer for ruby}
47
+ s.test_files = [
48
+ "spec/active_api/complex_type_spec.rb",
49
+ "spec/active_api/definition_spec.rb",
50
+ "spec/active_api/field_spec.rb",
51
+ "spec/active_api/schema_spec.rb",
52
+ "spec/active_api/simple_type_spec.rb",
53
+ "spec/api_spec.rb",
54
+ "spec/domain.rb",
55
+ "spec/spec_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.3.2"])
64
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.3.2"])
65
+ else
66
+ s.add_dependency(%q<nokogiri>, [">= 1.3.2"])
67
+ s.add_dependency(%q<activesupport>, [">= 2.3.2"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<nokogiri>, [">= 1.3.2"])
71
+ s.add_dependency(%q<activesupport>, [">= 2.3.2"])
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ defaults:
3
+ install_options: --source=http://gems.github.com --source=http://gems.rubyforge.org --include-dependencies --no-ri --no-rdoc
4
+ gems:
5
+ - name: rr
6
+ version: 0.8.1
7
+ - name: activesupport
8
+ version: 2.3.2
9
+ - name: rspec
10
+ version: 1.2.7
11
+ - name: nokogiri
12
+ version: 1.3.2
13
+ - name: faker
14
+ version: 0.3.1
15
+
@@ -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,11 @@
1
+ module ActiveApi
2
+ module Builder
3
+
4
+ def build_xml(builder = Nokogiri::XML::Builder.new)
5
+ builder.tap do |xml|
6
+ build(xml)
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveApi
2
+ class Collection
3
+ include Builder
4
+ include HasDefinition
5
+
6
+ attr_reader :objects, :node, :parents, :schema
7
+
8
+ def initialize(objects, options)
9
+ @objects = objects
10
+ @node = options[:node]
11
+ @parents = options[:parents]
12
+ @schema = options[:schema]
13
+ end
14
+
15
+ protected
16
+
17
+ def build(builder)
18
+ builder.send "#{node.to_s.pluralize}_" do |xml|
19
+ objects.each do |object|
20
+ element = definition.builder_class.constantize.new object,
21
+ :node => node,
22
+ :parents => parents,
23
+ :schema => schema
24
+ element.build_xml(xml)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveApi
2
+ class ComplexType
3
+ include Builder
4
+ include HasDefinition
5
+
6
+ attr_reader :object, :node, :parents, :schema
7
+
8
+ def initialize(object, options = {})
9
+ @object = object
10
+ @node = options[:node]
11
+ @parents = options[:parents] || {}
12
+ @parents[node.to_s.singularize.to_sym] = object
13
+ @schema = options[:schema]
14
+ end
15
+
16
+ protected
17
+
18
+ def build(builder)
19
+ builder.send "#{node.to_s.singularize}_", attributes do |xml|
20
+ definition.elements.each do |field|
21
+ name = field.name_for(object)
22
+ if name.present?
23
+ element = field.klass.new value(field),
24
+ :node => name,
25
+ :parents => parents,
26
+ :format => field.type,
27
+ :schema => schema
28
+ element.build_xml(xml)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def attributes
35
+ {}.tap do |attributes|
36
+ definition.attributes.each do |field|
37
+ name = field.name_for(object)
38
+ attribute = SimpleType.new value(field),
39
+ :node => name,
40
+ :format => field.type
41
+ attribute.append(attributes)
42
+ end
43
+ end
44
+ end
45
+
46
+ def value(field)
47
+ if field.value.nil?
48
+ object.send(field.name)
49
+ elsif field.value.is_a?(Symbol)
50
+ object.send(field.value)
51
+ elsif field.value.is_a?(String)
52
+ field.value
53
+ else
54
+ field.value.call(self)
55
+ end
56
+ end
57
+ end
58
+ end