acts_as_api 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +12 -0
  3. data/History.txt +8 -0
  4. data/README.rdoc +29 -225
  5. data/Rakefile +20 -28
  6. data/acts_as_api.gemspec +31 -0
  7. data/examples/introduction/docco.css +186 -0
  8. data/examples/introduction/index.html +340 -0
  9. data/examples/introduction/index.rb +132 -0
  10. data/examples/introduction/layout.mustache +64 -0
  11. data/lib/acts_as_api.rb +11 -25
  12. data/lib/acts_as_api/api_template.rb +14 -0
  13. data/lib/acts_as_api/base.rb +61 -56
  14. data/lib/acts_as_api/config.rb +34 -0
  15. data/lib/acts_as_api/rails_renderer.rb +15 -0
  16. data/lib/acts_as_api/rendering.rb +11 -8
  17. data/lib/acts_as_api/version.rb +4 -0
  18. data/spec/controllers/respond_with_users_controller_spec.rb +5 -0
  19. data/spec/controllers/users_controller_spec.rb +161 -0
  20. data/spec/models/base_spec.rb +437 -0
  21. data/spec/rails_app/.gitignore +4 -0
  22. data/spec/rails_app/Rakefile +7 -0
  23. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  24. data/spec/rails_app/app/controllers/respond_with_users_controller.rb +15 -0
  25. data/spec/rails_app/app/controllers/users_controller.rb +21 -0
  26. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  27. data/spec/rails_app/app/models/task.rb +3 -0
  28. data/spec/rails_app/app/models/untouched.rb +2 -0
  29. data/spec/rails_app/app/models/user.rb +69 -0
  30. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  31. data/spec/rails_app/config.ru +4 -0
  32. data/spec/rails_app/config/application.rb +42 -0
  33. data/spec/rails_app/config/boot.rb +6 -0
  34. data/spec/rails_app/config/database.yml +23 -0
  35. data/spec/rails_app/config/environment.rb +5 -0
  36. data/spec/rails_app/config/environments/development.rb +26 -0
  37. data/spec/rails_app/config/environments/production.rb +49 -0
  38. data/spec/rails_app/config/environments/test.rb +35 -0
  39. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  41. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  42. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  43. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  44. data/spec/rails_app/config/locales/en.yml +5 -0
  45. data/spec/rails_app/config/routes.rb +7 -0
  46. data/spec/rails_app/db/migrate/20110214201640_create_tables.rb +35 -0
  47. data/spec/rails_app/db/schema.rb +34 -0
  48. data/spec/rails_app/db/seeds.rb +7 -0
  49. data/spec/rails_app/lib/tasks/.gitkeep +0 -0
  50. data/spec/rails_app/public/404.html +26 -0
  51. data/spec/rails_app/public/422.html +26 -0
  52. data/spec/rails_app/public/500.html +26 -0
  53. data/spec/rails_app/public/favicon.ico +0 -0
  54. data/spec/rails_app/public/images/rails.png +0 -0
  55. data/spec/rails_app/public/index.html +239 -0
  56. data/spec/rails_app/public/javascripts/application.js +2 -0
  57. data/spec/rails_app/public/javascripts/controls.js +965 -0
  58. data/spec/rails_app/public/javascripts/dragdrop.js +974 -0
  59. data/spec/rails_app/public/javascripts/effects.js +1123 -0
  60. data/spec/rails_app/public/javascripts/prototype.js +6001 -0
  61. data/spec/rails_app/public/javascripts/rails.js +191 -0
  62. data/spec/rails_app/public/robots.txt +5 -0
  63. data/spec/rails_app/public/stylesheets/.gitkeep +0 -0
  64. data/spec/rails_app/script/rails +6 -0
  65. data/spec/spec_helper.rb +12 -13
  66. data/spec/support/api_test_helpers.rb +23 -0
  67. metadata +137 -35
  68. data/Manifest.txt +0 -15
  69. data/script/console +0 -10
  70. data/script/destroy +0 -14
  71. data/script/generate +0 -14
  72. data/spec/acts_as_api_spec.rb +0 -87
  73. data/tasks/rspec.rake +0 -21
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
5
+ <title>acts_as_api</title>
6
+ <link rel="stylesheet" href="./docco.css">
7
+ <link href="http://fonts.googleapis.com/css?family=Copse:regular" rel="stylesheet" type="text/css" >
8
+ <style>
9
+ h1, h2, h3, h4, h5 {
10
+ font-family: 'Copse', serif;
11
+ font-style: normal;
12
+ font-weight: 700;
13
+ text-shadow: none;
14
+ text-decoration: none;
15
+ text-transform: none;
16
+ letter-spacing: 0em;
17
+ word-spacing: 0em;
18
+ line-height: 1.2;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <div id='container'>
24
+ <div id="background"></div>
25
+ {{#sources?}}
26
+ <div id="jump_to">
27
+ Jump To &hellip;
28
+ <div id="jump_wrapper">
29
+ <div id="jump_page">
30
+ {{#sources}}
31
+ <a class="source" href="{{ url }}">{{ basename }}</a>
32
+ {{/sources}}
33
+ </div>
34
+ </div>
35
+ </div>
36
+ {{/sources?}}
37
+ <table cellspacing=0 cellpadding=0>
38
+ <thead>
39
+ <tr>
40
+ <th class=docs>
41
+ <h1>acts_as_api</h1>
42
+ <p>Makes creating XML/JSON responses in Rails 3 easy and fun.</p>
43
+ </th>
44
+ <th class=code></th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ {{#sections}}
49
+ <tr id='section-{{ section_id }}'>
50
+ <td class=docs>
51
+ <div class="pilwrap">
52
+ <a class="pilcrow" href="#section-{{ section_id }}">&#182;</a>
53
+ </div>
54
+ {{{ docs }}}
55
+ </td>
56
+ <td class=code>
57
+ <div class='highlight'><pre>{{{ code }}}</pre></div>
58
+ </td>
59
+ </tr>
60
+ {{/sections}}
61
+ </table>
62
+ </div>
63
+ <a href="https://github.com/fabrik42/acts_as_api"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://assets1.github.com/img/7afbc8b248c68eb468279e8c17986ad46549fb71?repo=&url=http%3A%2F%2Fs3.amazonaws.com%2Fgithub%2Fribbons%2Fforkme_right_darkblue_121621.png&path=" alt="Fork me on GitHub"></a>
64
+ </body>
@@ -1,11 +1,12 @@
1
- require "rubygems"
1
+ require 'set'
2
+ require 'active_model'
3
+ require 'active_support/core_ext/class'
2
4
 
3
5
  $:.unshift(File.dirname(__FILE__)) unless
4
6
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
-
6
- require "acts_as_api/base"
7
- require "acts_as_api/rendering"
8
- require "acts_as_api/array"
7
+
8
+ require "acts_as_api/array"
9
+ require "acts_as_api/rails_renderer"
9
10
 
10
11
  # acts_as_api is a gem that aims to make the construction of JSON and XML
11
12
  # responses in rails 3 easy and fun.
@@ -16,26 +17,10 @@ require "acts_as_api/array"
16
17
  # acts_as_api uses the default serializers of your rails app and doesn't
17
18
  # force you into more dependencies.
18
19
  module ActsAsApi
19
- VERSION = '0.2.2'
20
-
21
- # The accepted response formats
22
- # Default is +[:xml, :json]+
23
- ACCEPTED_API_FORMATS = [:xml, :json]
24
-
25
- # Holds references to formats that need
26
- # to get added an additional root node
27
- # with the name of the model.
28
- ADD_ROOT_NODE_FOR = [:json]
29
-
30
- # Holds formats that should be dasherized
31
- DASHERIZE_FOR = [:xml]
32
-
33
- # The default name of a root node of a response
34
- # if no root paramter is passed in render_for_api
35
- # and the gem is not able to determine a root name
36
- # automatically
37
- DEFAULT_ROOT = :record
38
-
20
+ autoload :Config, "acts_as_api/config"
21
+ autoload :ApiTemplate, "acts_as_api/api_template"
22
+ autoload :Base, "acts_as_api/base"
23
+ autoload :Rendering, "acts_as_api/rendering"
39
24
  end
40
25
 
41
26
  # Attach ourselves to active record
@@ -46,4 +31,5 @@ end
46
31
  # Attach ourselves to the action controller of rails
47
32
  if defined?(ActionController::Base)
48
33
  ActionController::Base.send :include, ActsAsApi::Rendering
34
+ ActsAsApi::RailsRenderer.setup
49
35
  end
@@ -0,0 +1,14 @@
1
+ module ActsAsApi
2
+ #
3
+ class ApiTemplate < Hash
4
+
5
+ def add(val, options = {})
6
+ self[(options[:as] || val).to_sym] = val
7
+ end
8
+
9
+ def remove(key)
10
+ self.delete(key)
11
+ end
12
+
13
+ end
14
+ end
@@ -16,6 +16,10 @@ module ActsAsApi
16
16
  extend ActsAsApi::Base::ClassMethods
17
17
  end
18
18
 
19
+ if block_given?
20
+ yield ActsAsApi::Config
21
+ end
22
+
19
23
  end
20
24
 
21
25
  module ClassMethods
@@ -28,12 +32,28 @@ module ActsAsApi
28
32
  # *Note*: There is only whitelisting for api accessible attributes.
29
33
  # So once the model acts as api, you have to determine all attributes here that should
30
34
  # be contained in the api responses.
31
- def api_accessible(api_templates)
35
+ def api_accessible_deprecated(api_templates)
32
36
  api_templates.each do |api_template, attributes|
33
37
  write_inheritable_attribute("api_accessible_#{api_template}".to_sym, Set.new(attributes) + (api_accessible_attributes(api_template) || []))
34
38
  end
35
39
  end
36
40
 
41
+ def api_accessible(api_template, options = {}, &block)
42
+
43
+ # return api_accessible_deprecated([api_template]) if api_template.is_a? Hash
44
+
45
+
46
+ attributes = api_accessible_attributes(api_template) || ApiTemplate.new
47
+
48
+ attributes.merge!(api_accessible_attributes(options[:extend])) if options[:extend]
49
+
50
+ if block_given?
51
+ yield attributes
52
+ end
53
+
54
+ write_inheritable_attribute("api_accessible_#{api_template}".to_sym, attributes)
55
+ end
56
+
37
57
  # Returns an array of all the attributes that have been made accessible to the api response.
38
58
  def api_accessible_attributes(api_template)
39
59
  read_inheritable_attribute("api_accessible_#{api_template}".to_sym)
@@ -52,82 +72,67 @@ module ActsAsApi
52
72
 
53
73
  return api_output if api_attributes.nil?
54
74
 
55
- api_attributes.each do |attribute|
56
-
57
- case attribute
58
- when Symbol
59
-
60
- if self.respond_to?(attribute)
61
- out = send attribute
62
-
63
- if out.respond_to?(:as_api_response)
64
- out = out.send(:as_api_response, api_template)
65
- end
66
-
67
- api_output[attribute] = out
68
-
69
- end
70
-
71
- when String
72
- # go up the call chain
73
- out = self
74
- attribute.split(".").each do |method|
75
- out = out.send(method.to_sym)
76
- end
77
- api_output[attribute] = out
78
-
79
- when Hash
80
-
81
- queue = []
82
- queue << { :parent => api_output, :item => attribute}
75
+ queue = []
76
+ queue << { :parent => api_output, :item => api_attributes}
83
77
 
84
- until queue.empty? do
78
+ until queue.empty? do
85
79
 
86
- leaf = queue.pop
80
+ leaf = queue.pop
87
81
 
88
- leaf[:item].each do |k,v|
82
+ leaf[:item].each do |k,v|
89
83
 
90
- case v
91
- when Symbol
84
+ case v
85
+ when Symbol
92
86
 
93
- if self.respond_to?(v)
94
- out = send v
95
-
96
- if out.respond_to?(:as_api_response)
97
- out = out.send(:as_api_response, api_template)
98
- end
99
-
100
- leaf[:parent][k] = out
87
+ if self.respond_to?(v)
88
+ out = send v
101
89
 
90
+ if out.respond_to?(:as_api_response)
91
+ out = out.send(:as_api_response, api_template)
102
92
  end
103
93
 
104
- when String
105
- # go up the call chain
106
- out = self
107
- v.split(".").each do |method|
108
- out = out.send(method.to_sym)
109
- end
110
94
  leaf[:parent][k] = out
111
95
 
112
- when Hash
113
- leaf[:parent][k] ||= {}
114
- queue << { :parent => leaf[:parent][k], :item => v}
115
96
  end
116
97
 
98
+ when Proc
99
+
100
+ out = v.call(self)
101
+
102
+ if out.respond_to?(:as_api_response)
103
+ out = out.send(:as_api_response, api_template)
104
+ end
105
+
106
+ leaf[:parent][k] = out
107
+
108
+ when String
109
+ # go up the call chain
110
+ out = self
111
+ v.split(".").each do |method|
112
+ out = out.send(method.to_sym)
113
+ end
114
+
115
+ if out.respond_to?(:as_api_response)
116
+ out = out.send(:as_api_response, api_template)
117
+ end
118
+
119
+ leaf[:parent][k] = out
120
+
121
+ when Hash
122
+ leaf[:parent][k] ||= {}
123
+ queue << { :parent => leaf[:parent][k], :item => v}
117
124
  end
118
125
 
119
126
  end
120
127
 
121
128
  end
122
129
 
123
- end
130
+ api_output
124
131
 
125
- api_output
132
+ end
126
133
 
127
134
  end
128
135
 
129
136
  end
130
-
131
-
137
+
132
138
  end
133
- end
@@ -0,0 +1,34 @@
1
+ module ActsAsApi
2
+
3
+ module Config
4
+
5
+ class << self
6
+
7
+ # The accepted response formats
8
+ # Default is <tt>[:xml, :json]</tt>
9
+ attr_accessor_with_default :accepted_api_formats, [:xml, :json] # :nodoc:
10
+
11
+ # Holds formats that should be dasherized
12
+ # Default is <tt>[:xml]</tt>
13
+ attr_accessor_with_default :dasherize_for, [:xml]
14
+
15
+ # Holds references to formats that need
16
+ # to get added an additional root node
17
+ # with the name of the model.
18
+ attr_accessor_with_default :add_root_node_for, [:json]
19
+
20
+ # The default name of a root node of a response
21
+ # if no root paramter is passed in render_for_api
22
+ # and the gem is not able to determine a root name
23
+ # automatically
24
+ attr_accessor_with_default :default_root, :record
25
+
26
+ attr_accessor_with_default :allow_jsonp_callback, false
27
+
28
+ attr_accessor_with_default :add_http_status_to_jsonp_response, true
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,15 @@
1
+ module ActsAsApi
2
+ # Contains rails specific renderers used by acts_as_api
3
+ module RailsRenderer
4
+
5
+ def self.setup
6
+ ActionController.add_renderer :acts_as_api_jsonp do |json, options|
7
+ json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
8
+ json = "#{options[:callback]}(#{json}, #{response.status})" unless options[:callback].blank?
9
+ self.content_type ||= Mime::JSON
10
+ self.response_body = json
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -13,7 +13,7 @@ module ActsAsApi
13
13
  # extract the api format and model
14
14
  api_format_options = {}
15
15
 
16
- ActsAsApi::ACCEPTED_API_FORMATS.each do |item|
16
+ ActsAsApi::Config.accepted_api_formats.each do |item|
17
17
  if render_options.has_key?(item)
18
18
  api_format_options[item] = render_options[item]
19
19
  render_options.delete item
@@ -28,13 +28,12 @@ module ActsAsApi
28
28
  # set the params to render
29
29
  output_params = render_options
30
30
 
31
-
32
31
  api_root_name = nil
33
32
 
34
33
  if !output_params[:root].nil?
35
-
36
34
  api_root_name = output_params[:root].to_s
37
-
35
+ elsif api_model.respond_to?(:collection_name)
36
+ api_root_name = api_model.collection_name
38
37
  elsif api_model.respond_to?(:model_name)
39
38
  api_root_name = api_model.model_name
40
39
  elsif api_model.is_a?(Array) && !api_model.empty? && api_model.first.class.respond_to?(:model_name)
@@ -42,7 +41,7 @@ module ActsAsApi
42
41
  elsif api_model.class.respond_to?(:model_name)
43
42
  api_root_name = api_model.class.model_name
44
43
  else
45
- api_root_name = ActsAsApi::DEFAULT_ROOT.to_s
44
+ api_root_name = ActsAsApi::Config.default_root.to_s
46
45
  end
47
46
 
48
47
  api_root_name = api_root_name.underscore.tr('/', '_')
@@ -51,7 +50,7 @@ module ActsAsApi
51
50
  api_root_name = api_root_name.pluralize
52
51
  end
53
52
 
54
- api_root_name = api_root_name.dasherize if ActsAsApi::DASHERIZE_FOR.include? api_format.to_sym
53
+ api_root_name = api_root_name.dasherize if ActsAsApi::Config.dasherize_for.include? api_format.to_sym
55
54
 
56
55
  output_params[:root] = api_root_name
57
56
 
@@ -60,17 +59,21 @@ module ActsAsApi
60
59
 
61
60
  api_response = api_model.as_api_response(api_template)
62
61
 
63
- if meta_hash or ActsAsApi::ADD_ROOT_NODE_FOR.include? api_format
62
+ if meta_hash or ActsAsApi::Config.add_root_node_for.include? api_format
64
63
  api_response = { api_root_name.to_sym => api_response}
65
64
  end
66
65
 
67
66
  api_response = meta_hash.merge api_response if meta_hash
67
+
68
+ if ActsAsApi::Config.allow_jsonp_callback && params[:callback]
69
+ output_params[:callback] = params[:callback]
70
+ api_format = :acts_as_api_jsonp if ActsAsApi::Config.add_http_status_to_jsonp_response
71
+ end
68
72
 
69
73
  # create the Hash as response
70
74
  output_params[api_format] = api_response
71
75
 
72
76
  render output_params
73
-
74
77
  end
75
78
 
76
79
  end
@@ -0,0 +1,4 @@
1
+ module ActsAsApi
2
+
3
+ VERSION = "0.3.0"
4
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe RespondWithUsersController do
4
+ pending "we need to fix the root node for json first"
5
+ end
@@ -0,0 +1,161 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe UsersController do
4
+
5
+ include ApiTestHelpers
6
+
7
+ before(:each) do
8
+ @luke = User.create({ :first_name => 'Luke', :last_name => 'Skywalker', :age => 25, :active => true })
9
+ @han = User.create({ :first_name => 'Han', :last_name => 'Solo', :age => 35, :active => true })
10
+ @leia = User.create({ :first_name => 'Princess', :last_name => 'Leia', :age => 25, :active => false })
11
+ end
12
+
13
+ after(:each) do
14
+ User.delete_all
15
+ end
16
+
17
+ describe 'xml responses' do
18
+
19
+ describe 'get all users' do
20
+
21
+ before(:each) do
22
+ get :index, :format => 'xml', :api_template => :name_only
23
+ end
24
+
25
+ it "should have a root node named users" do
26
+ response_body.should have_selector("users")
27
+ end
28
+
29
+ it "should contain all users" do
30
+ response_body.should have_selector("users > user") do |users|
31
+ users.size.should eql(3)
32
+ end
33
+ end
34
+
35
+ it "should contain the specified attributes" do
36
+ response_body.should have_selector("users > user > first-name")
37
+ response_body.should have_selector("users > user > last-name")
38
+ end
39
+
40
+ end
41
+
42
+ describe 'get a single user' do
43
+
44
+ before(:each) do
45
+ get :show, :format => 'xml', :api_template => :name_only, :id => @luke.id
46
+ end
47
+
48
+ it "should have a root node named user" do
49
+ response_body.should have_selector("user")
50
+ end
51
+
52
+ it "should contain the specified attributes" do
53
+ response_body.should have_selector("user > first-name")
54
+ response_body.should have_selector("user > last-name")
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+
62
+ describe 'json responses' do
63
+
64
+ describe 'get all users' do
65
+
66
+ before(:each) do
67
+ get :index, :format => 'json', :api_template => :name_only
68
+ end
69
+
70
+ it "should have a root node named users" do
71
+ response_body_json.should have_key("users")
72
+ end
73
+
74
+ it "should contain all users" do
75
+ response_body_json["users"].should be_a(Array)
76
+ end
77
+
78
+ it "should contain the specified attributes" do
79
+ response_body_json["users"].first.should have_key("first_name")
80
+ response_body_json["users"].first.should have_key("last_name")
81
+ end
82
+
83
+ it "should contain the specified values" do
84
+ response_body_json["users"].first["first_name"].should eql("Luke")
85
+ response_body_json["users"].first["last_name"].should eql("Skywalker")
86
+ end
87
+
88
+ end
89
+
90
+ describe 'get a single user' do
91
+
92
+ before(:each) do
93
+ get :show, :format => 'json', :api_template => :name_only, :id => @luke.id
94
+ end
95
+
96
+ it "should have a root node named user" do
97
+ response_body_json.should have_key("user")
98
+ end
99
+
100
+ it "should contain the specified attributes" do
101
+ response_body_json["user"].should have_key("first_name")
102
+ response_body_json["user"].should have_key("last_name")
103
+ end
104
+
105
+ it "should contain the specified values" do
106
+ response_body_json["user"]["first_name"].should eql("Luke")
107
+ response_body_json["user"]["last_name"].should eql("Skywalker")
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+
114
+
115
+ describe 'jsonp responses with callback' do
116
+
117
+ it "should be disabled by default" do
118
+ @callback = "mycallback"
119
+ get :index, :format => 'json', :api_template => :name_only, :callback => @callback
120
+ response_body_jsonp(@callback).should be_nil
121
+ end
122
+
123
+ describe "enabled jsonp callbacks" do
124
+
125
+ before(:each) do
126
+ @callback = "mycallback"
127
+
128
+ User.acts_as_api do |config|
129
+ config.allow_jsonp_callback = true
130
+ end
131
+ end
132
+
133
+ describe 'get all users' do
134
+
135
+ before(:each) do
136
+ get :index, :format => 'json', :api_template => :name_only, :callback => @callback
137
+ end
138
+
139
+ it "should wrap the response in the callback" do
140
+ response_body_jsonp(@callback).should_not be_nil
141
+ end
142
+
143
+ end
144
+
145
+ describe 'get a single user' do
146
+
147
+ before(:each) do
148
+ get :show, :format => 'json', :api_template => :name_only, :id => @luke.id, :callback => @callback
149
+ end
150
+
151
+ it "should wrap the response in the callback" do
152
+ response_body_jsonp(@callback).should_not be_nil
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+ end
159
+
160
+
161
+ end