moviepilot-restful 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGES.markdown +40 -0
  2. data/LICENSE.markdown +22 -0
  3. data/README.markdown +126 -0
  4. data/Rakefile +22 -0
  5. data/TODO.markdown +10 -0
  6. data/init.rb +1 -0
  7. data/lib/restful/apimodel/attribute.rb +17 -0
  8. data/lib/restful/apimodel/collection.rb +22 -0
  9. data/lib/restful/apimodel/link.rb +21 -0
  10. data/lib/restful/apimodel/map.rb +41 -0
  11. data/lib/restful/apimodel/resource.rb +23 -0
  12. data/lib/restful/converters/active_record.rb +159 -0
  13. data/lib/restful/rails/action_controller.rb +14 -0
  14. data/lib/restful/rails/active_record/configuration.rb +219 -0
  15. data/lib/restful/rails/active_record/metadata_tools.rb +102 -0
  16. data/lib/restful/rails.rb +22 -0
  17. data/lib/restful/serializers/atom_like_serializer.rb +51 -0
  18. data/lib/restful/serializers/base.rb +58 -0
  19. data/lib/restful/serializers/hash_serializer.rb +46 -0
  20. data/lib/restful/serializers/json_serializer.rb +18 -0
  21. data/lib/restful/serializers/params_serializer.rb +46 -0
  22. data/lib/restful/serializers/xml_serializer.rb +161 -0
  23. data/lib/restful.rb +65 -0
  24. data/rails/init.rb +1 -0
  25. data/restful.gemspec +17 -0
  26. data/test/converters/active_record_converter_test.rb +147 -0
  27. data/test/converters/basic_types_converter_test.rb +99 -0
  28. data/test/fixtures/models/paginated_collection.rb +3 -0
  29. data/test/fixtures/models/person.rb +29 -0
  30. data/test/fixtures/models/pet.rb +5 -0
  31. data/test/fixtures/models/wallet.rb +5 -0
  32. data/test/fixtures/people.json.yaml +107 -0
  33. data/test/fixtures/people.xml.yaml +117 -0
  34. data/test/fixtures/pets.json.yaml +20 -0
  35. data/test/fixtures/pets.xml.yaml +31 -0
  36. data/test/rails/active_record_metadata_test.rb +23 -0
  37. data/test/rails/configuration_test.rb +47 -0
  38. data/test/rails/restful_publish_test.rb +54 -0
  39. data/test/serializers/atom_serializer_test.rb +33 -0
  40. data/test/serializers/json_serializer_test.rb +90 -0
  41. data/test/serializers/params_serializer_test.rb +76 -0
  42. data/test/serializers/xml_serializer_test.rb +51 -0
  43. data/test/test_helper.rb +154 -0
  44. metadata +97 -0
data/CHANGES.markdown ADDED
@@ -0,0 +1,40 @@
1
+ 17. Aug 2009 - 0.2.6
2
+
3
+ * added :include option in to_restful.
4
+
5
+ 18. Aug 2009 - 0.2.7
6
+
7
+ * fixed issue where configurations where overwriting each other.
8
+ * added hash#to_restful
9
+
10
+ 19. Aug 2009 - 0.2.12
11
+
12
+ * added ability to publish :wallet-restful-url (explicitly collapsed)
13
+
14
+ 20. Aug 2009
15
+
16
+ - 0.2.13
17
+
18
+ * hash serializer no longer dereferences ids
19
+
20
+ - 0.2.14
21
+
22
+ * arrays names now use base_class of content models
23
+ * restful_path defaults to using base_class in path
24
+ * if array responds to name, use this as collection name
25
+
26
+ - 0.2.15
27
+
28
+ * :includes like active record to_xml to_json
29
+
30
+ 21. Aug 2009
31
+
32
+ * refactored HashSerializer to be much clearer
33
+
34
+ 26. Aug 2009
35
+
36
+ * fixed issue with link collapsing
37
+
38
+ 28. Aug 2009
39
+
40
+ * fixed cascading of :includes
data/LICENSE.markdown ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 SalesKing GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.markdown ADDED
@@ -0,0 +1,126 @@
1
+ # Disclaimer
2
+
3
+ !!! Refactor this shice. Seriously, this has devolved into some nasty-ass code.
4
+
5
+ # Why?
6
+
7
+ Aims to provide a production quality Rest API to your Rails app, with the following features:
8
+
9
+ * whitelisting
10
+ * flexible xml formats with good defaults
11
+ * all resources are referred to by url and not by id; expose a "web of resources"
12
+
13
+ # Serializers
14
+
15
+ Getting started
16
+ ============================
17
+ In order to make your models apiable add
18
+
19
+ `apiable`
20
+
21
+ to your model. Next, define which properties you want to export, so within the model write something like:
22
+
23
+ `self.restful_publish(:name, :current-location, :pets)`
24
+
25
+ Configuration
26
+ =============
27
+
28
+ Some example configurations:
29
+
30
+ # Person
31
+ restful_publish :name, :pets, :restful_options => { :expansion => :expanded } # default on level 1-2: expanded. default above: collapsed.
32
+ restful_publish :name, :pets, :wallet => :contents, :restful_options => { :expansion => :expanded } # combined options and expansion rules
33
+ restful_publish :name, :pets, :restful_options => { :collapsed => :pets } # collapsed pets, even though they are on the second level.
34
+ restful_publish :name, :pets, :restful_options => { :force_expanded => [:pets, :wallet] }
35
+
36
+ ## explicitly collapsed
37
+ restful_options :wallet-restful-url
38
+
39
+ # Pet
40
+ restful_publish :name, :person # expands person per default because it is on the second level. Does not expand person.pets.first.person, since this is higher than second level.
41
+
42
+ Options
43
+ =======
44
+
45
+ You can add includes to your call like this:
46
+
47
+ pet.to_restful_json :include => :owner.
48
+
49
+ Rails-like
50
+ ==========
51
+
52
+ This format sticks to xml_simple, adding links as `<association-name-restful-url>` nodes of type "link".
53
+
54
+
55
+ `Person.last.to_restful.serialize(:xml)` OR
56
+ `Person.last.to_restful_xml` results in something like...
57
+
58
+ <?xml version="1.0" encoding="UTF-8"?>
59
+ <person>
60
+ <restful-url type="link">http://example.com:3000/people/1</restful-url>
61
+ <name>Joe Bloggs</name>
62
+ <current-location>Under a tree</current-location>
63
+ <pets type="array">
64
+ <pet>
65
+ <restful-url type="link">http://example.com:3000/pets/1</restful-url>
66
+ <person-restful-url type="link">http://example.com:3000/people/1</person-restful-url>
67
+ <name nil="true"></name>
68
+ </pet>
69
+ </pets>
70
+ <sex>
71
+ <restful-url type="link">http://example.com:3000/sexes/1</restful-url>
72
+ <sex>male</sex>
73
+ </sex>
74
+ </person>
75
+
76
+
77
+ Atom-like
78
+ =========
79
+
80
+ `Person.last.to_restful.serialize(:atom_like)` OR
81
+ `Person.last.to_restful_atom_like` results in something like...
82
+
83
+ <?xml version="1.0" encoding="UTF-8"?>
84
+ <person xml:base="http://example.com:3000">
85
+ <link rel="self" href="/people/1"/>
86
+ <name>Joe Bloggs</name>
87
+ <current-location>Under a tree</current-location>
88
+ <pets>
89
+ <pet>
90
+ <link rel="self" href="/pets/1"/>
91
+ <link rel="person_id" href="/people/1"/>
92
+ <name></name>
93
+ </pet>
94
+ </pets>
95
+ <sex>
96
+ <link rel="self" href="/sexes/1"/>
97
+ <sex>male</sex>
98
+ </sex>
99
+ </person>
100
+
101
+ Params-like
102
+ ===========
103
+
104
+ `Person.last.to_restful.serialize(:params)` OR
105
+ `Person.last.to_restful_params` results in something like...
106
+
107
+ {:sex_attributes => {:sex=>"male"},
108
+ :current_location=>"Under a tree",
109
+ :name=>"Joe Bloggs",
110
+ :pets_attributes=> [ {:person_id=>1, :name=>nil} ]
111
+ }
112
+
113
+ Other Serializers
114
+ =================
115
+
116
+ Hash. Spits out a plain ole hash, no nested attributes or such like. Useful for further conversions.
117
+
118
+ Deserializing
119
+ =============
120
+
121
+ Use `Restful.from_atom_like(xml).serialize(:hash)` to convert from an atom-like formatted xml create to a params hash. Takes care of dereferencing the urls back to ids. Generally, use `Restful.from_<serializer name>(xml)` to get a Resource.
122
+
123
+ Nested Attributes
124
+ =================
125
+ Serializing uses Rails 2.3 notation of nested attributes. For deserializing you will need Rails 2.3 for having nested attributes support and the respective model must have the
126
+ `accepts_nested_attributes_for :<table name>` set accordingly.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the restful plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the restful plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Restful'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README.markdown')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
data/TODO.markdown ADDED
@@ -0,0 +1,10 @@
1
+ Refactor this shice. Seriously, this has devolved into some nasty-ass code.
2
+
3
+ * the metamodel is kind of weird. make a better metamodel - how about just using activemodel?
4
+ * remove requirement to call apiable in model classes; replace with restful_publish with no args (or with args.)
5
+ * move configuration object out of rails folder - this is general stuff.
6
+ * remove xml serialization here and test resource directly (in active_record_converter_test)
7
+ * get rid of to_a warning
8
+ * convert underscores to dashes (or not) in serializers instead of converter
9
+ * implement xml serialization of hashes
10
+ * write tests to show that [Person.new].to_restful_xml(:include => :wallet) fails to preserve Person whitelist.
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,17 @@
1
+ #
2
+ # Attribute model.
3
+ #
4
+ module Restful
5
+ module ApiModel
6
+ class Attribute
7
+ attr_accessor :name, :value, :type, :extended_type
8
+
9
+ def initialize(name, value, extended_type)
10
+ self.name = name
11
+ self.value = value
12
+ self.extended_type = extended_type
13
+ self.type = :simple_attribute
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Collection model. A collection is a named array of Resources.
3
+ #
4
+ module Restful
5
+ module ApiModel
6
+ class Collection < Attribute
7
+ attr_accessor :total_entries
8
+
9
+ def initialize(name, resources, extended_type)
10
+ super
11
+
12
+ self.type = :collection
13
+ end
14
+
15
+ # invoke serialization
16
+ def serialize(type)
17
+ serializer = Restful::Serializers::Base.serializer(type)
18
+ serializer.serialize(self)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # Link model.
3
+ #
4
+ module Restful
5
+ module ApiModel
6
+ class Link < Attribute
7
+ attr_accessor :base, :path
8
+
9
+ def initialize(name, base, path, extended_type)
10
+ self.base = base
11
+ self.path = path
12
+ super(name, self.full_url, extended_type)
13
+ self.type = :link
14
+ end
15
+
16
+ def full_url
17
+ base.blank? ? path : "#{ base }#{ path }"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Resource model. Something like a DOM model for the api.
3
+ #
4
+ module Restful
5
+ module ApiModel
6
+ class Map
7
+ attr_accessor :values, :name, :type
8
+
9
+ def initialize(name)
10
+ self.name = name
11
+ self.type = :map
12
+ self.values = []
13
+ end
14
+
15
+ def links
16
+ self.values.select { |attribute| attribute.type == :link }
17
+ end
18
+
19
+ def simple_attributes
20
+ self.values.select { |attribute| attribute.type == :simple_attribute }
21
+ end
22
+
23
+ def collections
24
+ self.values.select { |attribute| attribute.type == :collection }
25
+ end
26
+
27
+ # invoke serialization
28
+ def serialize(type)
29
+ serializer = Restful::Serializers::Base.serializer(type)
30
+ serializer.serialize(self)
31
+ end
32
+
33
+ # invoke deserialization
34
+ def deserialize_from(type)
35
+ serializer = Restful::Serializers::Base.serializer(type)
36
+ serializer.deserialize(self)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,23 @@
1
+ #
2
+ # Resource model. Something like a DOM model for the api.
3
+ #
4
+ module Restful
5
+ module ApiModel
6
+ class Resource < Map
7
+ attr_accessor :base, :path, :url
8
+
9
+ def initialize(name, url)
10
+ super(name)
11
+
12
+ self.url = url[:url]
13
+ self.path = url[:path]
14
+ self.base = url[:base]
15
+ self.type = :resource
16
+ end
17
+
18
+ def full_url
19
+ base.blank? ? url : "#{ base }#{ path }"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,159 @@
1
+ #
2
+ # Converts an ActiveRecord model into an ApiModel
3
+ #
4
+ module Restful
5
+ module Converters
6
+ class ActiveRecord
7
+
8
+ def self.convert(model, config, options = {})
9
+ published = []
10
+ nested = config.nested?
11
+
12
+ resource = Restful.resource(
13
+ model.class.to_s.tableize.demodulize.singularize, {
14
+ :base => Restful::Rails.api_hostname,
15
+ :path => model.restful_path,
16
+ :url => model.restful_url
17
+ })
18
+
19
+ explicit_links = config.whitelisted.select { |x| x.class == Symbol && x.to_s.ends_with?("_restful_url") }
20
+ explicit_links.each { |link| config.whitelisted.delete(link) }
21
+ explicit_links.map! { |link| link.to_s.chomp("_restful_url").to_sym }
22
+
23
+ # simple attributes
24
+ resource.values += Restful::Rails.tools.simple_attributes_on(model).map do |key, value|
25
+ convert_to_simple_attribute(key, value, config, published, model)
26
+ end.compact
27
+
28
+ # has_many, has_one, belongs_to
29
+ resource.values += model.class.reflections.keys.map do |key|
30
+ explicit_link = !!explicit_links.include?(key)
31
+
32
+ if config.published?(key.to_sym) || explicit_link
33
+ nested_config = config.nested(key.to_sym)
34
+ published << key.to_sym
35
+
36
+ if has_many?(model, key) && config.expanded?(key, nested)
37
+ convert_to_collection(model, key, nested_config, published) do |key, resources, extended_type|
38
+ Restful.collection(key, resources, extended_type)
39
+ end
40
+ elsif has_one?(model, key) or belongs_to?(model, key)
41
+ if config.expanded?(key, nested) && !explicit_link
42
+ convert_to_collection(model, key, nested_config, published) do |key, resources, extended_type|
43
+ returning(resources.first) do |res|
44
+ res.name = key
45
+ end
46
+ end
47
+ else
48
+ link_to(model, key)
49
+ end
50
+ end
51
+ end
52
+ end.compact
53
+
54
+ # Links
55
+ if model.class.apiable_association_table
56
+
57
+ resource.values += model.class.apiable_association_table.keys.map do |key|
58
+
59
+ if config.published?(key.to_sym)
60
+ published << key.to_sym
61
+ base, path = model.resolve_association_restful_url(key)
62
+ Restful.link(key.to_sym, base, path, compute_extended_type(model, key))
63
+ end
64
+ end.compact
65
+ end
66
+
67
+ # public methods
68
+ resource.values += (model.public_methods - Restful::Rails.tools.simple_attributes_on(model).keys.map(&:to_s)).map do |method_name|
69
+
70
+ explicit_link = !!explicit_links.include?(method_name.to_sym)
71
+
72
+ if !published.include?(method_name.to_sym) && (config.published?(method_name.to_sym) || explicit_link)
73
+ value = model.send(method_name.to_sym)
74
+ sanitized_method_name = method_name.tr("!?", "").tr("_", "-").to_sym
75
+
76
+ if value.is_a? ::ActiveRecord::Base
77
+ if config.expanded?(method_name.to_sym, nested) && !explicit_link
78
+ returning Restful::Rails.tools.expand(value, config.nested(method_name.to_sym)) do |expanded|
79
+ expanded.name = sanitized_method_name
80
+ end
81
+ else
82
+ Restful.link("#{ sanitized_method_name }-restful-url", Restful::Rails.api_hostname, value ? value.restful_path : "", compute_extended_type(model, method_name.to_sym))
83
+ end
84
+ else
85
+ Restful.attr(sanitized_method_name, value, compute_extended_type(model, method_name))
86
+ end
87
+ end
88
+ end.compact
89
+
90
+ resource
91
+ end
92
+
93
+ def self.has_one?(model, key)
94
+ macro(model, key) == :has_one
95
+ end
96
+
97
+ def self.has_many?(model, key)
98
+ macro(model, key) == :has_many
99
+ end
100
+
101
+ def self.belongs_to?(model, key)
102
+ macro(model, key) == :belongs_to
103
+ end
104
+
105
+ def self.link_to(model, key)
106
+ value = model.send(key)
107
+ restful_path = value ? value.restful_path : nil
108
+ basename = value ? Restful::Rails.api_hostname : nil
109
+
110
+ Restful.link("#{ key }-restful-url", basename, restful_path, compute_extended_type(model, key))
111
+ end
112
+
113
+ def self.convert_to_simple_attribute(key, value, config, published, model = nil)
114
+ if config.published?(key.to_sym)
115
+ published << key.to_sym
116
+ ext_type = (model ? compute_extended_type(model, key) : value.class.to_s.underscore.to_sym)
117
+ Restful.attr(key.to_sym, value, ext_type)
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def self.macro(model, key)
124
+ model.class.reflections[key].macro
125
+ end
126
+
127
+ def self.convert_to_collection(model, key, nested_config, published)
128
+ if resources = Restful::Rails.tools.convert_collection_to_resources(model, key, nested_config)
129
+ yield key.to_sym, resources, compute_extended_type(model, key)
130
+ else
131
+ published << key.to_sym
132
+ Restful.attr(key.to_sym, nil, :notype)
133
+ end
134
+ end
135
+
136
+ def self.compute_extended_type(record, attribute_name)
137
+ type_symbol = :yaml if record.class.serialized_attributes.has_key?(attribute_name)
138
+
139
+ if column = record.class.columns_hash[attribute_name]
140
+ type_symbol = column.send(:simplified_type, column.sql_type)
141
+ else
142
+
143
+ type_symbol = record.send(attribute_name).class.to_s.underscore.to_sym
144
+ end
145
+
146
+ case type_symbol
147
+ when :text
148
+ :string
149
+ when :time
150
+ :datetime
151
+ when :date
152
+ :date
153
+ else
154
+ type_symbol
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,14 @@
1
+ module Restful
2
+ module Rails
3
+ module ActionController
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ ActionController::Base.send :include, Restful::Rails::ActionController