apify 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,14 +1,286 @@
1
- apify
1
+ Apify
2
2
  =====
3
3
 
4
- Installation
5
- ------------
4
+ Apify lets you bolt a [JSON](http://en.wikipedia.org/wiki/JSON) API onto your Rails application.
5
+ Optional features are auto-generated API documentation, request validation with [JSON Schema](http://json-schema.org/)
6
+ and a client class to consume Apify APIs from Ruby code.
7
+
8
+
9
+ Two minute quickstart
10
+ ---------------------
6
11
 
7
12
  Install the gem with
8
13
 
9
14
  sudo gem install apify
10
15
 
11
- ...
16
+ For Rails 2, add the following to your `environment.rb`:
17
+
18
+ config.gem 'apify'
19
+
20
+ For Rails 3, add the following to your `Gemfile`:
21
+
22
+ gem 'apify'
23
+
24
+ Describe your API actions in `models/api.rb`:
25
+
26
+ class Api < Apify::Api
27
+ get :ping do
28
+ respond do
29
+ { 'message' => 'pong' }
30
+ end
31
+ end
32
+ end
33
+
34
+ Create a controller to serve your API in `controllers/api_controller.rb`:
35
+
36
+ class ApiController < Apify::ApiController
37
+ api Api
38
+ end
39
+
40
+ Connect the routes to your API in `config/routes.rb`:
41
+
42
+ ActionController::Routing::Routes.draw do |map|
43
+ Api.draw_routes(map)
44
+ end
45
+
46
+ You have now exposed an API action under `http://host/api/ping`.
47
+
48
+
49
+ Protocol
50
+ --------
51
+
52
+ If you are planning to consume your Apify API with the `Apify::Client` class, you won't need to know most of this. Nonetheless, this is what you're getting into:
53
+
54
+ - Apify is about sending JSON objects (hashes) back and forth. No fancy envelope formats.
55
+ - An Apify API defines actions with a name and HTTP method (`GET, POST, PUT, DELETE`).
56
+ - API actions are accessed over HTTP. Each action gets its own route.
57
+ - API actions can take arguments. Those are serialized into a **single** HTTP parameter `args` as JSON. This is to simplify client code that consumes your API (nested params are hard).
58
+ - Successful responses are returned with a status of 200 (OK).
59
+ - The body of a successful response is always a hash, serialized as JSON.
60
+ - Requests that have errors are returned with a status of 500 (internal server error). The body of a error response is an error message in the response's content type (won't be JSON in most cases).
61
+
62
+
63
+ Defining API actions
64
+ --------------------
65
+
66
+ API actions are defined in a model such as `models/api.rb`:
67
+
68
+ - This model needs to inherit from `Apify::Api`.
69
+ - An API method has a name and a HTTP method (`GET, POST, PUT, DELETE`)
70
+ - An API action always returns a hash. If an API action returns nil, Apify will turn that into an empty hash for you.
71
+
72
+ Here is an example for a simple, reading API action:
73
+
74
+ get :ping do
75
+ respond do
76
+ { 'message' => 'pong' }
77
+ end
78
+ end
79
+
80
+ Action arguments can be accessed through the `args` hash. Its keys are always strings, never symbols.
81
+
82
+ Here is an example for an API action that takes an argument:
83
+
84
+ post :hello do
85
+ respond do
86
+ { 'message' => 'Hello ' + args['name'] }
87
+ end
88
+ end
89
+
90
+
91
+ Schemas
92
+ -------
93
+
94
+ You can describe the expected format of method arguments and response values using [JSON Schema](http://json-schema.org/). This lets you define rules like that an argument is required or must be in a given format.
95
+
96
+ Schemas are an optional feature, but providing schemas has some benefits:
97
+
98
+ - Your actions don't need to handle unexpected arguments (remember that strangers are going to call your code).
99
+ - You don't need to worry about responding with values that break your own contract. Apify will validate your own responses and raise an error if they don't validate.
100
+ - Apify can auto-generate public documentation for actions with schemas. This documentation will contain generated examples for a successful request and response and also offer your schemas for download.
101
+
102
+ You can provide a schema for arguments, a schema for response values, or both. Here is the `hello` action from the above example with schemas:
103
+
104
+ post :hello do
105
+
106
+ schema :args do
107
+ object('name' => string)
108
+ end
109
+
110
+ schema :value do
111
+ object('message' => string)
112
+ end
113
+
114
+ respond do
115
+ { 'message' => 'Hello ' + args['name'] }
116
+ end
117
+
118
+ end
119
+
120
+ Your API now allows to download the schema and an auto-generated example in JSON format:
121
+
122
+ - POST http://host/api/hello?schema=args
123
+ - POST http://host/api/hello?example=args
124
+ - POST http://host/api/hello?schema=value
125
+ - POST http://host/api/hello?example=value
126
+
127
+ Here is another example for a more complex schema:
128
+
129
+ get :contacts do
130
+ schema :value do
131
+ array(
132
+ object(
133
+ 'name' => string,
134
+ 'phone' => integer,
135
+ 'phone_type' => enum(string, 'home', 'office'),
136
+ 'email' => optional(email),
137
+ 'favorite' => boolean
138
+ )
139
+ )
140
+ end
141
+ end
142
+
143
+
144
+ Auto-generated API documentation
145
+ --------------------------------
146
+
147
+ Every Apify API comes with auto-generated HTML documentation:
148
+
149
+ - The documentation can be accessed from the `docs` action of an `ApiController`, e.g. http://host/api/docs
150
+ - The documentation contains parts of this README (protocol, instructions to use the Ruby client)
151
+ - If your actions have schemas, the documentation includes those schemas and request/response examples generated from them.
152
+
153
+ You can give your actions descriptions to make the API documentation even more useful:
154
+
155
+ post :hello do
156
+
157
+ description 'Says hello to the given name.'
158
+
159
+ respond do
160
+ { 'message' => 'Hello ' + args['name'] }
161
+ end
162
+
163
+ end
164
+
165
+
166
+
167
+ Authentication
168
+ --------------
169
+
170
+ If your API uses a single username and password for all requests, you can activate basic authentication like this:
171
+
172
+ class ApiController < ApplicationController::Base
173
+ api Api
174
+ authenticate :user => 'api', :password => 'secret'
175
+ end
176
+
177
+ If your use case is more complex, just roll your own authentication. Your `ApiController` is just a regular controller.
178
+
179
+
180
+ Consuming an API with the Ruby client
181
+ -------------------------------------
182
+
183
+ An easy way to consume an Apify API is to use the <code>Apify::Client</code> class:
184
+
185
+ - The client calls an Apify API with a Ruby hash as argument and returns the response as a Ruby hash.
186
+ - It takes care of the protocol details and lets your users focus on exchanging data.
187
+ - Your users will only need the `apify` gem. There are no dependencies on Rails.
188
+ - The auto-generated documentation contains these instructions on how to install and use the client class.
189
+
190
+ You already know how to require the `apify` gem from a Rails application. This is how you require the client class when your code is not a Rails application:
191
+
192
+ require 'rubygems'
193
+ gem 'apify'
194
+ require 'apify/client'
195
+
196
+ Here is an example for how to use the client class:
197
+
198
+ client = Apify::Client.new(:host => 'localhost:3000', :user => 'api', :password => 'secret')
199
+ client.post('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }
200
+
201
+ Errors can be caught and inspected like this:
202
+
203
+ begin
204
+ client.get('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }
205
+ rescue Apify::RequestFailed => e
206
+ puts "Oh no! The API request failed."
207
+ puts "Message: #{e.message}"
208
+ puts "Response: #{e.response_body}"
209
+ end
210
+
211
+
212
+ Dealing with dates and timestamps
213
+ ---------------------------------
214
+
215
+ Unfortunately dates and timestamps are not among the data types defined by JSON and JSON Schema. You can work around this in any way you like, but Apify supports a default approach:
216
+
217
+ - The workaround supported by Apify is to handle a date as a string formatted like "2011-05-01" and a timestamp as a string formatted like "2011-05-01 12:00:04".
218
+ - Aside from being easy to parse, you can feed such strings into a model's date or time field and ActiveRecord will convert them into `Date` and `Time` objects.
219
+ - Apify gives you helper methods `sql_date` and `sql_datetime` to support this pattern in your schemas and actions.
220
+
221
+ Here are examples for actions that deal with dates and timestamps this way:
222
+
223
+ get :now do
224
+ schema :value do
225
+ object('now' => sql_datetime)
226
+ end
227
+ respond do
228
+ { 'now' => sql_datetime(Time.now) }
229
+ end
230
+ end
231
+
232
+ get :today do
233
+ schema :value do
234
+ object('today' => sql_date)
235
+ end
236
+ respond do
237
+ { 'today' => sql_date(Date.today) }
238
+ end
239
+ end
240
+
241
+
242
+ Testing API actions
243
+ -------------------
244
+
245
+ Since your API actions end up being vanilla Ruby methods that turn argument hashes into value hashes, you can test them without special tools.
246
+
247
+ Here is an example in RSpec:
248
+
249
+ describe Api, 'hello' do
250
+
251
+ it 'should greet the given name' do
252
+ Api.post(:hello, :name => 'Jack').should == { 'message' => 'Hello Jack' }
253
+ end
254
+
255
+ it 'should require a name argument' do
256
+ expect { Api.post(:hello) }.to raise_error(Apify::Invalid)
257
+ end
258
+
259
+ end
260
+
261
+
262
+ Custom routing
263
+ --------------
264
+
265
+ You can create a route for each action of an API by adding this to your `config/routes.rb`:
266
+
267
+ ActionController::Routing::Routes.draw do |map|
268
+ Api.draw_routes(map)
269
+ end
270
+
271
+ This will create URLs like `/api/ping`, `/api/hello`, etc. You can change the `/api` prefix like this:
272
+
273
+ ActionController::Routing::Routes.draw do |map|
274
+ Api.draw_routes(map, :base_path => 'interface/v1')
275
+ end
276
+
277
+ You can also forego the automatic route generation completely and roll your own URLs:
278
+
279
+ ActionController::Routing::Routes.draw do |map|
280
+ map.connect 'api/ping', :controller => 'api', :action => 'ping', :conditions { :method => :get }
281
+ map.connect 'api/hello', :controller => 'api', :action => 'hello', :conditions { :method => :post }
282
+ end
283
+
12
284
 
13
285
  Rails 3 compatibility
14
286
  ---------------------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.4.0
data/apify.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{apify}
8
- s.version = "0.3.3"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Henning Koch"]
12
- s.date = %q{2010-08-28}
12
+ s.date = %q{2010-08-29}
13
13
  s.description = %q{Compact definition of JSON APIs for Rails applications. }
14
14
  s.email = %q{github@makandra.de}
15
15
  s.extra_rdoc_files = [
@@ -97,6 +97,7 @@ Gem::Specification.new do |s|
97
97
  "spec/app_root/lib/console_with_fixtures.rb",
98
98
  "spec/app_root/script/console",
99
99
  "spec/controllers/api_controller_spec.rb",
100
+ "spec/models/api_spec.rb",
100
101
  "spec/rcov.opts",
101
102
  "spec/spec.opts",
102
103
  "spec/spec_helper.rb"
@@ -123,6 +124,7 @@ Gem::Specification.new do |s|
123
124
  "spec/app_root/config/environment.rb",
124
125
  "spec/app_root/config/routes.rb",
125
126
  "spec/controllers/api_controller_spec.rb",
127
+ "spec/models/api_spec.rb",
126
128
  "examples/client/client.rb",
127
129
  "examples/host/app/controllers/application_controller.rb",
128
130
  "examples/host/app/controllers/api_controller.rb",
@@ -11,13 +11,19 @@ First install the Apify gem:
11
11
 
12
12
  <pre><code>sudo gem install apify</code></pre>
13
13
 
14
+ If your client code is not a Rails application:
15
+
16
+ <pre><code>require 'rubygems'
17
+ gem 'apify'
18
+ require 'apify/client'</code></pre>
19
+
14
20
  In Rails 2, add the following to your environment.rb:
15
21
 
16
- <pre><code>config.gem 'aegis'</code></pre>
22
+ <pre><code>config.gem 'apify'</code></pre>
17
23
 
18
24
  In Rails 3, add the following to your Gemfile:
19
25
 
20
- <pre><code>gem 'aegis'</code></pre>
26
+ <pre><code>gem 'apify'</code></pre>
21
27
 
22
28
  <h3>Usage</h3>
23
29
 
@@ -3,7 +3,6 @@
3
3
  <ul>
4
4
  <li>The API works by exchanging messages encoded in <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>.</li>
5
5
  <li>There are no fancy message formats, it's all just sending arbitrary JSON objects back and forth.</li>
6
- <li>There are some optional features like validating requests with schemas, but you don't need to use them if you don't like.</li>
7
- <li>Building a lient that consumes the API is very straightforward. Also if you use Ruby, there is a helpful <a href="#api_client">client class</a> available.</li>
6
+ <li>Building a lient cthat consumes the API is very straightforward. Also if you use Ruby, there is a helpful <a href="#api_client">client class</a> available.</li>
8
7
  </ul>
9
8
 
@@ -3,7 +3,7 @@
3
3
  <h3>Requests</h3>
4
4
 
5
5
  <ul>
6
- <li>The API is accessed over plain HTTP.</li>
6
+ <li>The API is accessed over HTTP.</li>
7
7
  <% if authentication_configured? %>
8
8
  <li>HTTP basic authentication is used.</li>
9
9
  <% end %>
data/lib/apify/action.rb CHANGED
@@ -23,9 +23,7 @@ module Apify
23
23
  if block
24
24
  @responder = block
25
25
  else
26
- Apify::Exchange.new.tap do |exchange|
27
- exchange.respond(args, self)
28
- end
26
+ Apify::Exchange.new.respond(args, self)
29
27
  end
30
28
  end
31
29
 
data/lib/apify/api.rb CHANGED
@@ -2,7 +2,7 @@ module Apify
2
2
  class Api
3
3
  class << self
4
4
 
5
- def action(method, name, &block)
5
+ def action(method, name, args = {}, &block)
6
6
  method = method.to_sym
7
7
  name = name.to_sym
8
8
  if block
@@ -10,24 +10,25 @@ module Apify
10
10
  indexed_actions[name][method] = action
11
11
  actions << action
12
12
  else
13
- indexed_actions[name][method] or raise "Unknown API action: #{name}"
13
+ action = indexed_actions[name][method] or raise "Unknown API action: #{name}"
14
+ action.respond(args)
14
15
  end
15
16
  end
16
17
 
17
- def get(name, &block)
18
- action(:get, name, &block)
18
+ def get(*args, &block)
19
+ action(:get, *args, &block)
19
20
  end
20
21
 
21
- def post(name, &block)
22
- action(:post, name, &block)
22
+ def post(*args, &block)
23
+ action(:post, *args, &block)
23
24
  end
24
25
 
25
- def put(name, &block)
26
- action(:put, name, &block)
26
+ def put(*args, &block)
27
+ action(:put, *args, &block)
27
28
  end
28
29
 
29
- def delete(name, &block)
30
- action(:delete, name, &block)
30
+ def delete(*args, &block)
31
+ action(:delete, *args, &block)
31
32
  end
32
33
 
33
34
  def actions
@@ -59,17 +59,17 @@ module Apify
59
59
 
60
60
  def respond_with_action(action)
61
61
  args = params[:args].present? ? JSON.parse(params[:args]) : {}
62
- exchange = action.respond(args)
63
- render_api_response(exchange, "#{action.name}.json")
62
+ render_successful_request(action.respond(args), "#{action.name}.json")
63
+ rescue Exception => e
64
+ render_failed_request(e.message)
64
65
  end
65
66
 
66
- def render_api_response(exchange, filename)
67
- # p exchange.value
68
- if exchange.successful?
69
- send_data exchange.value.to_json, :status => '200', :type => 'application/json', :filename => filename
70
- else
71
- send_data exchange.value, :status => '500', :type => 'text/plain', :filename => filename
72
- end
67
+ def render_successful_request(value, filename)
68
+ send_data value.to_json, :status => '200', :type => 'application/json', :filename => filename
69
+ end
70
+
71
+ def render_failed_request(message)
72
+ send_data message, :status => '500', :type => 'text/plain'
73
73
  end
74
74
 
75
75
  def render_method_not_allowed(method)
data/lib/apify/client.rb CHANGED
@@ -36,10 +36,12 @@ module Apify
36
36
  def request(method, path, args = nil)
37
37
  url = build_url(path)
38
38
  args ||= {}
39
- json = RestClient.send(method, url, :args => args.to_json)
39
+ params = { :args => args.to_json }
40
+ [:get, :head].include?(method) and params = { :params => params }
41
+ json = RestClient.send(method, url, params)
40
42
  JSON.parse(json)
41
43
  rescue RestClient::Unauthorized => e
42
- raise Apify::RequestFailed.new("Unauthorized")
44
+ raise Apify::Unauthorized.new("Unauthorized")
43
45
  rescue RestClient::ExceptionWithResponse => e
44
46
  raise Apify::RequestFailed.new("API request failed with status #{e.http_code}", e.http_body)
45
47
  end
data/lib/apify/errors.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Apify
2
+
2
3
  class RequestFailed < StandardError
3
4
 
4
5
  attr_reader :response_body
@@ -9,4 +10,11 @@ module Apify
9
10
  end
10
11
 
11
12
  end
13
+
14
+ class Unauthorized < Apify::RequestFailed
15
+ end
16
+
17
+ class Invalid < Apify::RequestFailed
18
+ end
19
+
12
20
  end
@@ -6,22 +6,17 @@ module Apify
6
6
 
7
7
  def initialize
8
8
  @value = nil
9
- @error = false
10
9
  @args = nil
11
10
  end
12
11
 
13
- def successful?
14
- not @error
15
- end
16
-
17
12
  def respond(args, action)
18
- logging_errors do
19
- @args = args
20
- validate(@args, action.schema(:args), 'Invalid request args')
21
- @value = instance_eval(&action.responder) || {}
22
- validate(@value, action.schema(:value), 'Invalid response value')
23
- end
24
- successful?
13
+ # hash_class = defined?(HashWithIndifferentAccess) ? HashWithIndifferentAccess : ActiveSupport::HashWithIndifferentAccess
14
+ @args = args.stringify_keys
15
+ validate(@args, action.schema(:args), 'Invalid request args')
16
+ @value = instance_eval(&action.responder) || {}
17
+ @value.stringify_keys!
18
+ validate(@value, action.schema(:value), 'Invalid response value')
19
+ @value
25
20
  end
26
21
 
27
22
  private
@@ -31,16 +26,10 @@ module Apify
31
26
  def validate(object, schema, message_prefix)
32
27
  JSON::Schema.validate(object, schema) if schema
33
28
  rescue JSON::Schema::ValueError => e
34
- raise "#{message_prefix}: #{e.message}"
29
+ @value = nil
30
+ raise Apify::Invalid.new("#{message_prefix}: #{e.message}")
35
31
  end
36
32
 
37
- def logging_errors(&block)
38
- block.call
39
- rescue Exception => e
40
- @error = true
41
- @value = e.message
42
- end
43
-
44
33
  def sql_datetime(time)
45
34
  time.present? ? time.strftime("%Y-%m-%d %H:%M:%S") : nil
46
35
  end
@@ -4,20 +4,36 @@ describe Apify::Client do
4
4
 
5
5
  it "raise an exception if authorization is missing" do
6
6
  client = Apify::Client.new(:host => 'host')
7
- stub_http_request(:get, 'http://host/api').to_return(:status => 401, :body => 'the body')
8
- expect { client.get '/api' }.to raise_error(Apify::RequestFailed)
7
+ stub_request(:post, 'http://host/api/method').to_return(:status => 401, :body => 'the body')
8
+ expect { client.post '/api/method' }.to raise_error(Apify::RequestFailed)
9
9
  end
10
10
 
11
11
  it "raise an exception if there is a server error" do
12
12
  client = Apify::Client.new(:host => 'host')
13
- stub_http_request(:get, 'http://host/api').to_return(:status => 500, :body => 'the body')
14
- expect { client.get '/api' }.to raise_error(Apify::RequestFailed)
13
+ stub_request(:post, 'http://host/api/method').to_return(:status => 500, :body => 'the body')
14
+ expect { client.post '/api/method' }.to raise_error(Apify::RequestFailed)
15
15
  end
16
16
 
17
17
  it "should return the parsed JSON object if the request went through" do
18
18
  client = Apify::Client.new(:host => 'host')
19
- stub_http_request(:get, 'http://host/api').to_return(:status => 200, :body => '{ "key": "value" }')
20
- client.get('/api').should == { 'key' => 'value' }
19
+ stub_request(:post, 'http://host/api/method').to_return(:status => 200, :body => '{ "key": "value" }')
20
+ client.post('/api/method').should == { 'key' => 'value' }
21
+ end
22
+
23
+ it "should call API methods with arguments" do
24
+ client = Apify::Client.new(:host => 'host')
25
+ stub_request(:post, 'http://host/api/hello').to_return(:status => 200, :body => '{}')
26
+ args = { :name => 'Jack' }
27
+ client.post('/api/hello', args)
28
+ WebMock.should have_requested(:post, "http://host/api/hello").with(:body => { :args => args.to_json })
29
+ end
30
+
31
+ it "should call GET actions correctly" do
32
+ client = Apify::Client.new(:host => 'host')
33
+ args = { :name => 'Jack' }
34
+ stub_request(:get, 'http://host/api/hello').with(:query => { :args => args.to_json }).to_return(:status => 200, :body => '{}')
35
+ client.get('/api/hello', args)
36
+ WebMock.should have_requested(:get, "http://host/api/hello").with(:query => { :args => args.to_json })
21
37
  end
22
38
 
23
39
  end
@@ -12,6 +12,18 @@ class Api < Apify::Api
12
12
  end
13
13
  end
14
14
 
15
+ post :hello do
16
+ schema :args do
17
+ object('name' => string)
18
+ end
19
+ schema :value do
20
+ object('message' => string)
21
+ end
22
+ respond do
23
+ { 'message' => "Hello #{args['name']}" }
24
+ end
25
+ end
26
+
15
27
  post :echo_args do
16
28
  respond do
17
29
  args
@@ -20,19 +32,13 @@ class Api < Apify::Api
20
32
 
21
33
  post :with_args_schema do
22
34
  schema :args do
23
- { "type" => "object",
24
- "properties" => {
25
- "string_arg" => string
26
- }}
35
+ object("string_arg" => string)
27
36
  end
28
37
  end
29
38
 
30
39
  post :with_value_schema do
31
40
  schema :value do
32
- { "type" => "object",
33
- "properties" => {
34
- "string_value" => string
35
- }}
41
+ object("string_value" => string)
36
42
  end
37
43
  respond do
38
44
  args
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Convenient testing of Api models' do
4
+
5
+ it 'should allow to test response objects directly' do
6
+ Api.post(:hello, :name => 'Jack').should == { 'message' => 'Hello Jack' }
7
+ end
8
+
9
+ it 'should raise an exception on errors' do
10
+ expect { Api.post(:hello) }.to raise_error(Apify::Invalid)
11
+ expect { Api.post(:fail) }.to raise_error(StandardError)
12
+ end
13
+
14
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 3
9
- version: 0.3.3
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Henning Koch
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-28 00:00:00 +02:00
17
+ date: 2010-08-29 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -143,6 +143,7 @@ files:
143
143
  - spec/app_root/lib/console_with_fixtures.rb
144
144
  - spec/app_root/script/console
145
145
  - spec/controllers/api_controller_spec.rb
146
+ - spec/models/api_spec.rb
146
147
  - spec/rcov.opts
147
148
  - spec/spec.opts
148
149
  - spec/spec_helper.rb
@@ -193,6 +194,7 @@ test_files:
193
194
  - spec/app_root/config/environment.rb
194
195
  - spec/app_root/config/routes.rb
195
196
  - spec/controllers/api_controller_spec.rb
197
+ - spec/models/api_spec.rb
196
198
  - examples/client/client.rb
197
199
  - examples/host/app/controllers/application_controller.rb
198
200
  - examples/host/app/controllers/api_controller.rb