restful_serializer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/LICENSE +19 -0
- data/README.rdoc +75 -0
- data/Rakefile +32 -0
- data/lib/restful.rb +120 -0
- data/lib/restful/serializer.rb +160 -0
- data/lib/restful/version.rb +3 -0
- data/restful_serializer.gemspec +30 -0
- data/spec/database.rb +12 -0
- data/spec/restful_spec.rb +72 -0
- data/spec/serializer_spec.rb +322 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +18 -0
- metadata +174 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Joshua Partlow
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= Restful Serialization
|
2
|
+
|
3
|
+
A gem to help with serializing activerecord instances as Restful resources, including hrefs.
|
4
|
+
|
5
|
+
If the model has a name column, it will be used to describe the resource.
|
6
|
+
|
7
|
+
This library does not attempt to provide href info for transitions, or deal much with questions of authorization beyond what is specified in the serialization configuration lines. It assumes that these issues would be resolved in the controller. It assumes standard naming conventions for routes.
|
8
|
+
|
9
|
+
So far only tested with Rails 2.x
|
10
|
+
|
11
|
+
== Example
|
12
|
+
|
13
|
+
Models:
|
14
|
+
|
15
|
+
# Columns:
|
16
|
+
# name
|
17
|
+
# column1
|
18
|
+
# column2
|
19
|
+
# secret
|
20
|
+
class Foo < ActiveRecord::Base
|
21
|
+
has_many :bars
|
22
|
+
end
|
23
|
+
|
24
|
+
# Columns:
|
25
|
+
# bar1
|
26
|
+
# bar2
|
27
|
+
# system
|
28
|
+
class Bar < ActiveRecord::Base
|
29
|
+
has_one :foo
|
30
|
+
end
|
31
|
+
|
32
|
+
Example configuration (config/initializers/restful.rb:
|
33
|
+
|
34
|
+
# url prefix for calls to the web service
|
35
|
+
Restful.api_prefix = 'client_api'
|
36
|
+
Restful.model_configuration = {
|
37
|
+
# This configuration provides information which anyone authorized to see a
|
38
|
+
# given object at the most basic level should be able to see.
|
39
|
+
|
40
|
+
# Note: it is advisable to set :serialization => :only for all models, so that
|
41
|
+
# new attributes do not automatically become available through the api.
|
42
|
+
|
43
|
+
:foo => {
|
44
|
+
:serialization => {
|
45
|
+
:only => [:id, :column1, :column2],
|
46
|
+
},
|
47
|
+
:associations => {:bars => nil}
|
48
|
+
},
|
49
|
+
:bar => {
|
50
|
+
:serialization => {
|
51
|
+
:only => [:id, :bar1, :bar2],
|
52
|
+
:include => {
|
53
|
+
:foo => { :only => [:column1] }
|
54
|
+
},
|
55
|
+
}
|
56
|
+
:associations => {:foo => nil}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
Note that the serialization configuration is the same which you would normally pass to ActiveRecord::Serialization (in a model.to_json call, for instance).
|
61
|
+
|
62
|
+
Example output:
|
63
|
+
|
64
|
+
foo = Foo.create(:name => 'A foo', :column1 => 1, :column2 => 2, :secret => "very secret")
|
65
|
+
pp foo.restful
|
66
|
+
# =>
|
67
|
+
# {"href"=>"http://test.app/client_api/foos/1",
|
68
|
+
# "name"=>"A foo",
|
69
|
+
# "bars_href"=>"http://test.app/client_api/foos/1/bars",
|
70
|
+
# "foo"=>
|
71
|
+
# {"id"=>1,
|
72
|
+
# "name"=>"A foo",
|
73
|
+
# "column1"=>1,
|
74
|
+
# "column2"=>2,}}
|
75
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
require 'restful/version'
|
7
|
+
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
10
|
+
spec.libs << 'lib' << 'spec'
|
11
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
12
|
+
end
|
13
|
+
|
14
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
15
|
+
spec.libs << 'lib' << 'spec'
|
16
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
17
|
+
spec.rcov = true
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => :spec
|
21
|
+
|
22
|
+
require 'rake/rdoctask'
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
24
|
+
version = Restful::VERSION
|
25
|
+
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
27
|
+
rdoc.title = "restful #{version}"
|
28
|
+
rdoc.main = 'README.rdoc'
|
29
|
+
rdoc.rdoc_files.include('README*')
|
30
|
+
rdoc.rdoc_files.include('LICENSE*')
|
31
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
32
|
+
end
|
data/lib/restful.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# This file is part of restful_serializer. Copyright 2011 Joshua Partlow. This is free software, see the LICENSE file for details.
|
2
|
+
require 'active_support'
|
3
|
+
require 'restful/serializer'
|
4
|
+
|
5
|
+
# This library is used to decorate ActiveRecord with methods to assist in generating
|
6
|
+
# Restful content for Web Services.
|
7
|
+
#
|
8
|
+
# It produces a hash of reference, object and href information for an ActiveRecord instance
|
9
|
+
# or association. Output is highly configurable both through Rails initialization and
|
10
|
+
# method calls.
|
11
|
+
#
|
12
|
+
# = Options
|
13
|
+
#
|
14
|
+
# The following options may be set in the Restful.model_configuration hash on a per model class
|
15
|
+
# basis.
|
16
|
+
#
|
17
|
+
# * :name => method to call on an instance to produce a human meaningful reference for the instance.
|
18
|
+
# Defaults to :name.
|
19
|
+
# * :serialization => options to be passed to ActiveRecord::Serialization::Serializer to configure
|
20
|
+
# serialization of the ActiveRecord instance itself. See ActiveRecord::Serialization.to_json
|
21
|
+
# * :url_for => if the named_route helper method cannot be guessed from normal Rails restful
|
22
|
+
# syntax, it may be overriden here.
|
23
|
+
# * :associations => you may include href references to the instance's associations
|
24
|
+
# * :shallow => if you are serializing an association, by default member includes and association
|
25
|
+
# references are stripped. Set this to false to traverse deeply.
|
26
|
+
# * :no_inherited_options => normally a subclass inherits and overrides its base class settings.
|
27
|
+
# Setting this to true prevents this so that only the options specifically set for the class
|
28
|
+
# will be used. Default false.
|
29
|
+
#
|
30
|
+
# = Usage
|
31
|
+
#
|
32
|
+
# # :first_name => :string
|
33
|
+
# # :last_name => :string
|
34
|
+
# # :age => :int
|
35
|
+
# # :secrets => :string
|
36
|
+
# class Person < ActiveRecord::Base
|
37
|
+
# has_many :books
|
38
|
+
#
|
39
|
+
# def name
|
40
|
+
# "#{first_name} #{last_name}"
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # :title => :string
|
45
|
+
# # :pages => :int
|
46
|
+
# # :person_id => :int
|
47
|
+
# class Book < ActiveRecord::Base
|
48
|
+
# belongs_to :person
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# ActionController::Routing::Routes.draw do |map|
|
52
|
+
# map.resources :people do |people|
|
53
|
+
# people.resources :books
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Restful.model_configuration = {
|
58
|
+
# :person => {
|
59
|
+
# :serialization => { :except => :secrets }
|
60
|
+
# :associations => :books,
|
61
|
+
# }
|
62
|
+
# :book => {
|
63
|
+
# :name => :title,
|
64
|
+
# :associations => :person,
|
65
|
+
# }
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
# bob = Person.new(:first_name => 'Bob', :last_name => 'Smith', :age => 41, :secrets => 'untold')
|
69
|
+
# bob.restful
|
70
|
+
# # => {
|
71
|
+
# # 'name' => 'Bob Smith'
|
72
|
+
# # 'person' => {
|
73
|
+
# # 'id' => 1,
|
74
|
+
# # 'first_name' => 'Bob',
|
75
|
+
# # 'last_name' => 'Bob',
|
76
|
+
# # 'age' : 17,
|
77
|
+
# # 'href' => 'http://www.example.com/web_service/people/1' }
|
78
|
+
# # 'books_href' => 'http://www.example.com/web_service/people/1/books' }
|
79
|
+
# # }
|
80
|
+
#
|
81
|
+
module Restful
|
82
|
+
# Route prefix for api calls.
|
83
|
+
mattr_accessor :api_prefix
|
84
|
+
|
85
|
+
# Hash for configuration Restful models.
|
86
|
+
mattr_accessor :model_configuration
|
87
|
+
self.model_configuration = {}
|
88
|
+
|
89
|
+
def self.model_configuration=(options)
|
90
|
+
@@model_configuration = options.symbolize_keys
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.model_configuration_for(key)
|
94
|
+
config = case key
|
95
|
+
when Symbol
|
96
|
+
model_configuration[key]
|
97
|
+
when String
|
98
|
+
model_configuration[key.to_sym]
|
99
|
+
when Class
|
100
|
+
model_configuration[key.name.underscore.to_sym]
|
101
|
+
else
|
102
|
+
model_configuration[key.class.name.underscore.to_sym]
|
103
|
+
end
|
104
|
+
return Marshal.load(Marshal.dump(config)) || {} # deep clone or empty
|
105
|
+
end
|
106
|
+
|
107
|
+
module Extensions
|
108
|
+
def restful(*args)
|
109
|
+
Restful::Serializer.new(self, *args).serialize
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module AssociationExtensions
|
114
|
+
def restful(*args)
|
115
|
+
Restful::Serializer.new(self, *args).serialize
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
ActiveRecord::Base.send(:include, Restful::Extensions)
|
120
|
+
ActiveRecord::Associations::AssociationProxy.send(:include, Restful::AssociationExtensions)
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# This file is part of restful_serializer. Copyright 2011 Joshua Partlow. This is free software, see the LICENSE file for details.
|
2
|
+
require 'active_record'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module Restful
|
6
|
+
module UrlForHelpers
|
7
|
+
|
8
|
+
# Used to construct and attempt to call named routes be providing resource strings out
|
9
|
+
# of which an named route method name will be constructed:
|
10
|
+
#
|
11
|
+
# Options:
|
12
|
+
#
|
13
|
+
# * :resources => a symbol, string or array of same describing segments of the helper method name.
|
14
|
+
# e.g. [:user, :comments] => user_comments (to which _url will be appended...)
|
15
|
+
# * :method => overrides the use of :resources and :prefix
|
16
|
+
# * :args => any arguments to be passed to the named_route when it is called
|
17
|
+
#
|
18
|
+
# = Example
|
19
|
+
#
|
20
|
+
# get_url_for(:resources => [:user, :comments], :args => 1)
|
21
|
+
# # => send("#{Restful.api_prefix}_user_comments_url", 1)
|
22
|
+
# # which if it exists is likely to return something like:
|
23
|
+
# # "http://example.com/user/1/comments"
|
24
|
+
#
|
25
|
+
def get_url_for(options)
|
26
|
+
url_for = [options[:method], 'url'] if options.include?(:method)
|
27
|
+
url_for ||= [Restful.api_prefix, options[:resources], 'url'].flatten.compact
|
28
|
+
url_for = url_for.join('_').downcase
|
29
|
+
send(url_for, *Array(options[:args])) if respond_to?(url_for)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class Serializer
|
35
|
+
include ActionController::UrlWriter
|
36
|
+
include UrlForHelpers
|
37
|
+
attr_accessor :subject, :base_klass, :klass, :options, :shallow
|
38
|
+
|
39
|
+
def initialize(subject, *args)
|
40
|
+
self.subject = subject
|
41
|
+
|
42
|
+
self.base_klass = subject.class.base_class.name.underscore if subject.class.respond_to?(:base_class)
|
43
|
+
self.klass = subject.class.name.underscore
|
44
|
+
|
45
|
+
passed_options = (args.pop || {}).symbolize_keys
|
46
|
+
base_options = Restful.model_configuration_for(base_klass) || {}
|
47
|
+
class_options = Restful.model_configuration_for(klass) || {}
|
48
|
+
self.options = (klass == base_klass || class_options[:no_inherited_options]) ?
|
49
|
+
class_options :
|
50
|
+
base_options.merge(class_options)
|
51
|
+
self.options.merge!(passed_options)
|
52
|
+
|
53
|
+
self.shallow = options[:shallow]
|
54
|
+
end
|
55
|
+
|
56
|
+
def serialize
|
57
|
+
case
|
58
|
+
when subject.respond_to?(:attribute_names) then _serialize_active_record
|
59
|
+
when subject.kind_of?(Array) then _serialize_array
|
60
|
+
else ActiveSupport::JSON.decode(subject.to_json) # just capture the hash of the object structure
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def active_record_serialization_options
|
65
|
+
ar_options = (options[:serialization] || {}).clone
|
66
|
+
ar_options.delete(:include) if shallow
|
67
|
+
return ar_options
|
68
|
+
end
|
69
|
+
|
70
|
+
def name
|
71
|
+
name_method = options[:name] || :name
|
72
|
+
subject.send(name_method) if subject.respond_to?(name_method)
|
73
|
+
end
|
74
|
+
|
75
|
+
def associations
|
76
|
+
unless @associations
|
77
|
+
@associations = case options[:associations]
|
78
|
+
when Array,Hash
|
79
|
+
options[:associations].map do |name,assoc|
|
80
|
+
Association.new(subject, klass, (assoc.nil? ? name : assoc), name)
|
81
|
+
end
|
82
|
+
when nil
|
83
|
+
[]
|
84
|
+
else
|
85
|
+
[Association.new(subject, klass, options[:associations])]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
return @associations
|
89
|
+
end
|
90
|
+
|
91
|
+
def href
|
92
|
+
unless @href
|
93
|
+
@href = get_url_for(:method => options[:url_for], :args => subject.id) if options.include?(:url_for)
|
94
|
+
@href = get_url_for(:resources => klass, :args => subject.id) unless @href
|
95
|
+
@href = get_url_for(:resources => base_klass, :args => subject.id) unless @href || base_klass == klass
|
96
|
+
end
|
97
|
+
return @href
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def _serialize_active_record
|
103
|
+
restful = {
|
104
|
+
klass => ActiveRecord::Serialization::Serializer.new(subject, active_record_serialization_options).serializable_record,
|
105
|
+
}
|
106
|
+
restful['name'] = name if name
|
107
|
+
restful['href'] = href
|
108
|
+
associations.each do |association|
|
109
|
+
restful["#{association.name}_href"] = association.href
|
110
|
+
end unless shallow
|
111
|
+
|
112
|
+
return restful
|
113
|
+
end
|
114
|
+
|
115
|
+
def _serialize_array
|
116
|
+
restful = subject.map do |e|
|
117
|
+
array_options = options.clone
|
118
|
+
array_options.merge!(:shallow => true) unless array_options.include?(:shallow)
|
119
|
+
Serializer.new(e, array_options).serialize
|
120
|
+
end
|
121
|
+
return restful
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Handle for information about an ActiveRecord association.
|
126
|
+
class Association
|
127
|
+
include ActionController::UrlWriter
|
128
|
+
include UrlForHelpers
|
129
|
+
attr_accessor :name, :association_name, :association, :subject, :subject_klass
|
130
|
+
|
131
|
+
def initialize(subject, subject_klass, association_name, name = nil)
|
132
|
+
self.subject = subject
|
133
|
+
self.subject_klass = subject_klass
|
134
|
+
self.association_name = association_name
|
135
|
+
self.name = name || association_name
|
136
|
+
self.association = subject.class.reflect_on_association(association_name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def singular?
|
140
|
+
[:belongs_to, :has_one].include?(association.macro)
|
141
|
+
end
|
142
|
+
|
143
|
+
def href
|
144
|
+
if singular?
|
145
|
+
href = get_url_for(:resources => association_name, :args => subject.send(association.name).id)
|
146
|
+
else
|
147
|
+
href = collective_href
|
148
|
+
end
|
149
|
+
return href
|
150
|
+
end
|
151
|
+
|
152
|
+
def collective_href
|
153
|
+
# try url_for nested resources first
|
154
|
+
unless href = get_url_for(:resources => [subject_klass, association_name], :args => subject.id)
|
155
|
+
href = get_url_for(:resources => association_name)
|
156
|
+
end
|
157
|
+
return href
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'restful/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{restful_serializer}
|
8
|
+
s.version = Restful::VERSION
|
9
|
+
s.required_rubygems_version = ">= 1.3.6"
|
10
|
+
|
11
|
+
s.authors = ["Josh Partlow"]
|
12
|
+
s.email = %q{jpartlow@glatisant.org}
|
13
|
+
s.summary = %q{Helps with serializing activerecord instances as Restful resources.}
|
14
|
+
s.description = %q{This library is used to decorate ActiveRecord with methods to assist in generating Restful content for Web Services.}
|
15
|
+
s.homepage = %q{http://github.com/jpartlow/restful_serializer}
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.rdoc_options = ["--main=README.rdoc", "--charset=UTF-8"]
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
s.rubygems_version = %q{1.3.6}
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files spec/*`.split("\n")
|
25
|
+
|
26
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
27
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 2.1.0", "<3.0.0"])
|
28
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.1.0", "<3.0.0"])
|
29
|
+
s.add_runtime_dependency(%q<actionpack>, [">= 2.1.0", "<3.0.0"])
|
30
|
+
end
|
data/spec/database.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'activerecord', '>=2.1.0'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
|
7
|
+
ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/active_record.log")
|
8
|
+
|
9
|
+
def create_schema(&block)
|
10
|
+
connection = ActiveRecord::Base.connection
|
11
|
+
yield connection if block_given?
|
12
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe Restful do
|
4
|
+
|
5
|
+
it "should have an api_prefix accessor" do
|
6
|
+
Restful.api_prefix.should be_nil
|
7
|
+
Restful.api_prefix = 'foo'
|
8
|
+
Restful.api_prefix.should == 'foo'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should have a model_configuration accessor" do
|
12
|
+
Restful.model_configuration.should == {}
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should automatically symbolize keys for model_configuration" do
|
16
|
+
Restful.model_configuration = { 'foo' => 'bar' }
|
17
|
+
Restful.model_configuration.should == { :foo => 'bar' }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "looking up model configurations" do
|
21
|
+
|
22
|
+
class Foo; end
|
23
|
+
class FooBar < Foo; end
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
Restful.model_configuration = {
|
27
|
+
:foo => :foo_config,
|
28
|
+
:foo_bar => :foo_bar_config,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return an empty hash if no configuration" do
|
33
|
+
Restful.model_configuration_for(nil).should == {}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should lookup entries by symbol" do
|
37
|
+
Restful.model_configuration_for(:foo).should == :foo_config
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should lookup entries from a string" do
|
41
|
+
Restful.model_configuration_for('foo').should == :foo_config
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should lookup entries by Class" do
|
45
|
+
Restful.model_configuration_for(Foo).should == :foo_config
|
46
|
+
Restful.model_configuration_for(FooBar).should == :foo_bar_config
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should lookup entries by instance class" do
|
50
|
+
Restful.model_configuration_for(Foo.new).should == :foo_config
|
51
|
+
Restful.model_configuration_for(FooBar.new).should == :foo_bar_config
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with deep structures" do
|
55
|
+
|
56
|
+
before(:each) do
|
57
|
+
Restful.model_configuration = {
|
58
|
+
:foo => { :setting => {:with => :nested_hash } }
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should provide deep clones of model_configuration elements" do
|
63
|
+
config = Restful.model_configuration_for(:foo)
|
64
|
+
config[:setting][:with] = "changed"
|
65
|
+
Restful.model_configuration.should == {
|
66
|
+
:foo => { :setting => {:with => :nested_hash } }
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
ActionController::Routing::Routes.draw do |map|
|
4
|
+
map.resources :foos
|
5
|
+
map.prefix_foo 'prefix/foos/:id', :controller => 'foos', :action => 'show'
|
6
|
+
map.custom_foo 'custom_foo/:id', :controller => 'foos', :action => 'show'
|
7
|
+
map.resources :bars do |bars|
|
8
|
+
bars.resources :dingos
|
9
|
+
end
|
10
|
+
map.resources :things
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Restful::Serializer do
|
14
|
+
|
15
|
+
class Foo < ActiveRecord::Base
|
16
|
+
has_many :bars
|
17
|
+
def fancy_name
|
18
|
+
"fancy: #{name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def a_method
|
22
|
+
"calculated value"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Bar < ActiveRecord::Base
|
27
|
+
belongs_to :foo
|
28
|
+
has_one :one
|
29
|
+
has_many :dingos
|
30
|
+
end
|
31
|
+
|
32
|
+
class Dingo < ActiveRecord::Base
|
33
|
+
belongs_to :bar
|
34
|
+
end
|
35
|
+
|
36
|
+
class Thing < ActiveRecord::Base; end
|
37
|
+
class Sub < Thing; end
|
38
|
+
|
39
|
+
def generate_instances
|
40
|
+
@foo = Foo.create!(:name => 'A foo')
|
41
|
+
@bar1 = Bar.create!(:name => 'The bar1', :foo => @foo)
|
42
|
+
@bar2 = Bar.create!(:name => 'The bar2', :foo => @foo)
|
43
|
+
@dingo1 = Dingo.create!(:name => 'The dingo1', :bar => @bar1)
|
44
|
+
@dingo2 = Dingo.create!(:name => 'The dingo2', :bar => @bar1)
|
45
|
+
@dingo3 = Dingo.create!(:name => 'The dingo3', :bar => @bar2)
|
46
|
+
@dingo4 = Dingo.create!(:name => 'The dingo4', :bar => @bar2)
|
47
|
+
end
|
48
|
+
|
49
|
+
before(:all) do
|
50
|
+
create_schema do |conn|
|
51
|
+
conn.create_table(:foos, :force => true) do |t|
|
52
|
+
t.string :name
|
53
|
+
end
|
54
|
+
conn.create_table(:bars, :force => true) do |t|
|
55
|
+
t.string :name
|
56
|
+
t.references :foo
|
57
|
+
end
|
58
|
+
conn.create_table(:dingos, :force => true) do |t|
|
59
|
+
t.string :name
|
60
|
+
t.references :bar
|
61
|
+
end
|
62
|
+
conn.create_table(:things, :force => true) do |t|
|
63
|
+
t.string :name
|
64
|
+
t.string :type
|
65
|
+
t.string :secret
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
@foo = Foo.new(:name => 'A foo')
|
72
|
+
Restful::Serializer.default_url_options = { :host => 'test.org' }
|
73
|
+
end
|
74
|
+
|
75
|
+
after(:each) do
|
76
|
+
Restful.api_prefix = nil
|
77
|
+
Restful.model_configuration = {}
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should require a subject" do
|
81
|
+
lambda { Restful::Serializer.new }.should raise_error(ArgumentError)
|
82
|
+
Restful::Serializer.new('foo').should be_kind_of(Restful::Serializer)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should set klass" do
|
86
|
+
rs = Restful::Serializer.new(@foo)
|
87
|
+
rs.klass.should == 'foo'
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should serialize" do
|
91
|
+
@foo.save!
|
92
|
+
rs = Restful::Serializer.new(@foo)
|
93
|
+
rs.serialize.should == {
|
94
|
+
'name' => @foo.name,
|
95
|
+
'foo' => {
|
96
|
+
'id' => @foo.id,
|
97
|
+
'name' => @foo.name,
|
98
|
+
},
|
99
|
+
'href' => "http://test.org/foos/#{@foo.id}",
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "with configuration options" do
|
104
|
+
|
105
|
+
before(:each) do
|
106
|
+
Restful.api_prefix = 'prefix'
|
107
|
+
Restful.model_configuration = {
|
108
|
+
:foo => {
|
109
|
+
:name => :fancy_name,
|
110
|
+
:serialization => { :only => [:name], :methods => [:a_method] },
|
111
|
+
}
|
112
|
+
}
|
113
|
+
@foo.save!
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should take options from configuration" do
|
117
|
+
rs = Restful::Serializer.new(@foo)
|
118
|
+
rs.serialize.should == {
|
119
|
+
'name' => @foo.fancy_name,
|
120
|
+
'foo' => {
|
121
|
+
'name' => @foo.name,
|
122
|
+
:a_method => @foo.a_method,
|
123
|
+
},
|
124
|
+
'href' => "http://test.org/prefix/foos/#{@foo.id}",
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should override options during initialization" do
|
129
|
+
rs = Restful::Serializer.new(@foo,
|
130
|
+
:name => :name,
|
131
|
+
:serialization => { :only => [:id] },
|
132
|
+
:url_for => :custom_foo
|
133
|
+
)
|
134
|
+
rs.serialize.should == {
|
135
|
+
'name' => @foo.name,
|
136
|
+
'foo' => {
|
137
|
+
'id' => @foo.id,
|
138
|
+
},
|
139
|
+
'href' => "http://test.org/custom_foo/#{@foo.id}",
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "with associations" do
|
146
|
+
|
147
|
+
before(:each) do
|
148
|
+
Restful.model_configuration = {
|
149
|
+
:foo => {
|
150
|
+
:associations => :bars,
|
151
|
+
},
|
152
|
+
:bar => {
|
153
|
+
:associations => { :special => :foo, :dingos => nil },
|
154
|
+
},
|
155
|
+
}
|
156
|
+
generate_instances
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should include references to associations" do
|
160
|
+
rs = Restful::Serializer.new(@foo)
|
161
|
+
rs.serialize.should == {
|
162
|
+
'name' => @foo.name,
|
163
|
+
'foo' => {
|
164
|
+
'id' => @foo.id,
|
165
|
+
'name' => @foo.name,
|
166
|
+
},
|
167
|
+
'href' => "http://test.org/foos/#{@foo.id}",
|
168
|
+
'bars_href' => "http://test.org/bars",
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should handle multiple and nested associations" do
|
173
|
+
rs = Restful::Serializer.new(@bar1)
|
174
|
+
rs.serialize.should == {
|
175
|
+
'name' => @bar1.name,
|
176
|
+
'bar' => {
|
177
|
+
'id' => @bar1.id,
|
178
|
+
'name' => @bar1.name,
|
179
|
+
'foo_id' => @bar1.foo_id,
|
180
|
+
},
|
181
|
+
'href' => "http://test.org/bars/#{@bar1.id}",
|
182
|
+
'dingos_href' => "http://test.org/bars/#{@bar1.id}/dingos",
|
183
|
+
'special_href' => "http://test.org/foos/#{@bar1.foo_id}",
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "with subclasses" do
|
190
|
+
|
191
|
+
before(:each) do
|
192
|
+
Restful.model_configuration = {
|
193
|
+
:thing => {
|
194
|
+
:serialization => { :except => :secret }
|
195
|
+
}
|
196
|
+
}
|
197
|
+
@thing = Thing.create!(:name => 'a thing', :secret => 'a secret')
|
198
|
+
@sub = Sub.create!(:name => 'a sub thing', :secret => 'another secret')
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should pull superclass configuration up into a subclass serialization" do
|
202
|
+
rs = Restful::Serializer.new(@sub)
|
203
|
+
rs.serialize.should == {
|
204
|
+
'name' => @sub.name,
|
205
|
+
'href' => "http://test.org/things/#{@sub.id}",
|
206
|
+
'sub' => {
|
207
|
+
'id' => @sub.id,
|
208
|
+
'name' => @sub.name,
|
209
|
+
}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe "with arrays" do
|
215
|
+
before(:each) do
|
216
|
+
Restful.model_configuration = {
|
217
|
+
:foo => {
|
218
|
+
:associations => :bars,
|
219
|
+
},
|
220
|
+
:bar => {
|
221
|
+
:serialization => { :only => :name, :include => { :dingos => { :only => [ :name, :id] } } },
|
222
|
+
:associations => [:foo, :dingos],
|
223
|
+
},
|
224
|
+
}
|
225
|
+
generate_instances
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should serialize arrays" do
|
229
|
+
rs = Restful::Serializer.new(@foo.bars)
|
230
|
+
rs.serialize.should == [
|
231
|
+
{
|
232
|
+
'name' => @bar1.name,
|
233
|
+
'href' => "http://test.org/bars/#{@bar1.id}",
|
234
|
+
'bar' => {
|
235
|
+
'name' => @bar1.name,
|
236
|
+
},
|
237
|
+
},
|
238
|
+
{
|
239
|
+
'name' => @bar2.name,
|
240
|
+
'href' => "http://test.org/bars/#{@bar2.id}",
|
241
|
+
'bar' => {
|
242
|
+
'name' => @bar2.name,
|
243
|
+
},
|
244
|
+
},
|
245
|
+
]
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should deeply serialize arrays if told to" do
|
249
|
+
rs = Restful::Serializer.new(@foo.bars, :shallow => false)
|
250
|
+
rs.serialize.should == [
|
251
|
+
{
|
252
|
+
'name' => @bar1.name,
|
253
|
+
'bar' => {
|
254
|
+
'name' => @bar1.name,
|
255
|
+
:dingos => [
|
256
|
+
{ 'name' => @dingo1.name, 'id' => @dingo1.id, },
|
257
|
+
{ 'name' => @dingo2.name, 'id' => @dingo2.id, },
|
258
|
+
],
|
259
|
+
},
|
260
|
+
'href' => "http://test.org/bars/#{@bar1.id}",
|
261
|
+
'dingos_href' => "http://test.org/bars/#{@bar1.id}/dingos",
|
262
|
+
'foo_href' => "http://test.org/foos/#{@bar1.foo_id}",
|
263
|
+
},
|
264
|
+
{
|
265
|
+
'name' => @bar2.name,
|
266
|
+
'bar' => {
|
267
|
+
'name' => @bar2.name,
|
268
|
+
:dingos => [
|
269
|
+
{ 'name' => @dingo3.name, 'id' => @dingo3.id, },
|
270
|
+
{ 'name' => @dingo4.name, 'id' => @dingo4.id, },
|
271
|
+
],
|
272
|
+
},
|
273
|
+
'href' => "http://test.org/bars/#{@bar2.id}",
|
274
|
+
'dingos_href' => "http://test.org/bars/#{@bar2.id}/dingos",
|
275
|
+
'foo_href' => "http://test.org/foos/#{@bar2.foo_id}",
|
276
|
+
},
|
277
|
+
]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should hook into activerecord" do
|
282
|
+
@foo.save!
|
283
|
+
@foo.should respond_to(:restful)
|
284
|
+
@foo.restful(:serialization => { :only => :name }).should == {
|
285
|
+
'name' => @foo.name,
|
286
|
+
'foo' => {
|
287
|
+
'name' => @foo.name,
|
288
|
+
},
|
289
|
+
'href' => "http://test.org/foos/#{@foo.id}",
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
it "Should hook into associations" do
|
294
|
+
generate_instances
|
295
|
+
@foo.bars.restful.should == [
|
296
|
+
{
|
297
|
+
"href" => "http://test.org/bars/#{@bar1.id}",
|
298
|
+
"name" => @bar1.name,
|
299
|
+
"bar" => {
|
300
|
+
"name" => @bar1.name,
|
301
|
+
"foo_id" => @bar1.foo_id,
|
302
|
+
"id" => @bar1.id,
|
303
|
+
},
|
304
|
+
},
|
305
|
+
{
|
306
|
+
"href" => "http://test.org/bars/#{@bar2.id}",
|
307
|
+
"name" => @bar2.name,
|
308
|
+
"bar" => {
|
309
|
+
"name" => @bar2.name,
|
310
|
+
"foo_id" => @bar2.foo_id,
|
311
|
+
"id" => @bar2.id,
|
312
|
+
},
|
313
|
+
},
|
314
|
+
]
|
315
|
+
end
|
316
|
+
|
317
|
+
it "should work with AssociationProxy.respond_to" do
|
318
|
+
pending('associations should respond_to :restful (AR 2.1 issue or AR issue in general?)') do
|
319
|
+
@foo.bars.should respond_to(:restful)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'restful'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
require 'database'
|
9
|
+
|
10
|
+
#class TestLogger
|
11
|
+
# [:debug, :info, :warn, :error].each do |m|
|
12
|
+
# define_method(m) { |message| puts "#{m.to_s.upcase}: #{message}" }
|
13
|
+
# end
|
14
|
+
#end
|
15
|
+
|
16
|
+
Spec::Runner.configure do |config|
|
17
|
+
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restful_serializer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Josh Partlow
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-08 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 11
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 1
|
49
|
+
- 0
|
50
|
+
version: 2.1.0
|
51
|
+
- - <
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 7
|
54
|
+
segments:
|
55
|
+
- 3
|
56
|
+
- 0
|
57
|
+
- 0
|
58
|
+
version: 3.0.0
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id002
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: activesupport
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 11
|
70
|
+
segments:
|
71
|
+
- 2
|
72
|
+
- 1
|
73
|
+
- 0
|
74
|
+
version: 2.1.0
|
75
|
+
- - <
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 7
|
78
|
+
segments:
|
79
|
+
- 3
|
80
|
+
- 0
|
81
|
+
- 0
|
82
|
+
version: 3.0.0
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id003
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: actionpack
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 11
|
94
|
+
segments:
|
95
|
+
- 2
|
96
|
+
- 1
|
97
|
+
- 0
|
98
|
+
version: 2.1.0
|
99
|
+
- - <
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 7
|
102
|
+
segments:
|
103
|
+
- 3
|
104
|
+
- 0
|
105
|
+
- 0
|
106
|
+
version: 3.0.0
|
107
|
+
type: :runtime
|
108
|
+
version_requirements: *id004
|
109
|
+
description: This library is used to decorate ActiveRecord with methods to assist in generating Restful content for Web Services.
|
110
|
+
email: jpartlow@glatisant.org
|
111
|
+
executables: []
|
112
|
+
|
113
|
+
extensions: []
|
114
|
+
|
115
|
+
extra_rdoc_files:
|
116
|
+
- LICENSE
|
117
|
+
- README.rdoc
|
118
|
+
files:
|
119
|
+
- .gitignore
|
120
|
+
- LICENSE
|
121
|
+
- README.rdoc
|
122
|
+
- Rakefile
|
123
|
+
- lib/restful.rb
|
124
|
+
- lib/restful/serializer.rb
|
125
|
+
- lib/restful/version.rb
|
126
|
+
- restful_serializer.gemspec
|
127
|
+
- spec/database.rb
|
128
|
+
- spec/restful_spec.rb
|
129
|
+
- spec/serializer_spec.rb
|
130
|
+
- spec/spec.opts
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
has_rdoc: true
|
133
|
+
homepage: http://github.com/jpartlow/restful_serializer
|
134
|
+
licenses: []
|
135
|
+
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options:
|
138
|
+
- --main=README.rdoc
|
139
|
+
- --charset=UTF-8
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 23
|
157
|
+
segments:
|
158
|
+
- 1
|
159
|
+
- 3
|
160
|
+
- 6
|
161
|
+
version: 1.3.6
|
162
|
+
requirements: []
|
163
|
+
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 1.6.2
|
166
|
+
signing_key:
|
167
|
+
specification_version: 3
|
168
|
+
summary: Helps with serializing activerecord instances as Restful resources.
|
169
|
+
test_files:
|
170
|
+
- spec/database.rb
|
171
|
+
- spec/restful_spec.rb
|
172
|
+
- spec/serializer_spec.rb
|
173
|
+
- spec/spec.opts
|
174
|
+
- spec/spec_helper.rb
|