batch_api 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/changelog.md +17 -0
  2. data/lib/batch_api.rb +6 -1
  3. data/lib/batch_api/batch_error.rb +41 -0
  4. data/lib/batch_api/configuration.rb +31 -21
  5. data/lib/batch_api/error_wrapper.rb +44 -0
  6. data/lib/batch_api/internal_middleware.rb +87 -0
  7. data/lib/batch_api/internal_middleware/decode_json_body.rb +24 -0
  8. data/lib/batch_api/internal_middleware/response_filter.rb +27 -0
  9. data/lib/batch_api/operation/rack.rb +4 -5
  10. data/lib/batch_api/processor.rb +22 -20
  11. data/lib/batch_api/processor/executor.rb +18 -0
  12. data/lib/batch_api/processor/sequential.rb +29 -0
  13. data/lib/batch_api/{middleware.rb → rack_middleware.rb} +2 -2
  14. data/lib/batch_api/response.rb +10 -8
  15. data/lib/batch_api/version.rb +1 -1
  16. data/readme.md +179 -106
  17. data/spec/dummy/README.rdoc +261 -0
  18. data/spec/dummy/Rakefile +15 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  20. data/spec/dummy/app/assets/javascripts/endpoints.js +2 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/assets/stylesheets/endpoints.css +4 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  24. data/spec/dummy/app/controllers/endpoints_controller.rb +36 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/helpers/endpoints_helper.rb +2 -0
  27. data/spec/dummy/app/views/endpoints/get.html.erb +2 -0
  28. data/spec/dummy/app/views/endpoints/post.html.erb +2 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/config.ru +4 -0
  31. data/spec/dummy/config/application.rb +63 -0
  32. data/spec/dummy/config/boot.rb +10 -0
  33. data/spec/dummy/config/database.yml +25 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +37 -0
  36. data/spec/dummy/config/environments/production.rb +67 -0
  37. data/spec/dummy/config/environments/test.rb +37 -0
  38. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  39. data/spec/dummy/config/initializers/inflections.rb +15 -0
  40. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  41. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  42. data/spec/dummy/config/initializers/session_store.rb +8 -0
  43. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  44. data/spec/dummy/config/locales/en.yml +5 -0
  45. data/spec/dummy/config/routes.rb +64 -0
  46. data/spec/dummy/db/development.sqlite3 +0 -0
  47. data/spec/dummy/db/test.sqlite3 +0 -0
  48. data/spec/dummy/log/development.log +1742 -0
  49. data/spec/dummy/log/test.log +48237 -0
  50. data/spec/dummy/public/404.html +26 -0
  51. data/spec/dummy/public/422.html +26 -0
  52. data/spec/dummy/public/500.html +25 -0
  53. data/spec/dummy/public/favicon.ico +0 -0
  54. data/spec/dummy/script/rails +6 -0
  55. data/spec/dummy/test/functional/endpoints_controller_test.rb +14 -0
  56. data/spec/dummy/test/unit/helpers/endpoints_helper_test.rb +4 -0
  57. data/spec/integration/rails_spec.rb +10 -0
  58. data/spec/integration/shared_examples.rb +256 -0
  59. data/spec/integration/sinatra_integration_spec.rb +14 -0
  60. data/spec/lib/batch_api_spec.rb +20 -0
  61. data/spec/lib/batch_error_spec.rb +23 -0
  62. data/spec/lib/configuration_spec.rb +30 -0
  63. data/spec/lib/error_wrapper_spec.rb +68 -0
  64. data/spec/lib/internal_middleware/decode_json_body_spec.rb +37 -0
  65. data/spec/lib/internal_middleware/response_filter_spec.rb +61 -0
  66. data/spec/lib/internal_middleware_spec.rb +91 -0
  67. data/spec/lib/operation/rack_spec.rb +243 -0
  68. data/spec/lib/operation/rails_spec.rb +100 -0
  69. data/spec/lib/processor/executor_spec.rb +22 -0
  70. data/spec/lib/processor/sequential_spec.rb +39 -0
  71. data/spec/lib/processor_spec.rb +134 -0
  72. data/spec/lib/rack_middleware_spec.rb +103 -0
  73. data/spec/lib/response_spec.rb +53 -0
  74. data/spec/spec_helper.rb +28 -0
  75. data/spec/support/sinatra_app.rb +54 -0
  76. metadata +148 -12
  77. data/lib/batch_api/error.rb +0 -3
  78. data/lib/batch_api/errors/base.rb +0 -45
  79. data/lib/batch_api/errors/operation.rb +0 -7
  80. data/lib/batch_api/errors/request.rb +0 -26
  81. data/lib/batch_api/processor/strategies/sequential.rb +0 -18
@@ -0,0 +1,18 @@
1
+ module BatchApi
2
+ class Processor
3
+ # Public: a simple middleware that lives at the end of the internal chain
4
+ # and simply executes each batch operation.
5
+ class Executor
6
+
7
+ # Public: initialize the middleware.
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ # Public: execute the batch operation.
13
+ def call(env)
14
+ env[:op].execute
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ module BatchApi
2
+ class Processor
3
+ class Sequential
4
+ # Public: initialize with the app.
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ # Public: execute all operations sequentially.
10
+ #
11
+ # ops - a set of BatchApi::Operations
12
+ # options - a set of options
13
+ #
14
+ # Returns an array of BatchApi::Response objects.
15
+ def call(env)
16
+ env[:ops].collect do |op|
17
+ # set the current op
18
+ env[:op] = op
19
+
20
+ # execute the individual request inside the operation-specific
21
+ # middeware, then clear out the current op afterward
22
+ middleware = InternalMiddleware.operation_stack
23
+ middleware.call(env).tap {|r| env.delete(:op) }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -1,5 +1,5 @@
1
1
  module BatchApi
2
- class Middleware
2
+ class RackMiddleware
3
3
  def initialize(app, &block)
4
4
  @app = app
5
5
  yield BatchApi.config if block
@@ -12,7 +12,7 @@ module BatchApi
12
12
  result = BatchApi::Processor.new(request, @app).execute!
13
13
  [200, self.class.content_type, [MultiJson.dump(result)]]
14
14
  rescue => err
15
- BatchApi::Errors::Request.new(err).render
15
+ ErrorWrapper.new(err).render
16
16
  end
17
17
  else
18
18
  @app.call(env)
@@ -1,5 +1,3 @@
1
- require 'batch_api/error'
2
-
3
1
  module BatchApi
4
2
  # Public: a response from an internal operation in the Batch API.
5
3
  # It contains all the details that are needed to describe the call's
@@ -15,6 +13,15 @@ module BatchApi
15
13
  @body = process_body(response[2])
16
14
  end
17
15
 
16
+ # Public: convert the response to JSON. nil values are ignored.
17
+ def as_json(options = {})
18
+ {}.tap do |result|
19
+ result[:body] = @body unless @body.nil?
20
+ result[:headers] = @headers unless @headers.nil?
21
+ result[:status] = @status unless @status.nil?
22
+ end
23
+ end
24
+
18
25
  private
19
26
 
20
27
  def process_body(body_pieces)
@@ -24,12 +31,7 @@ module BatchApi
24
31
  # so turn it into a string
25
32
  base_body = ""
26
33
  body_pieces.each {|str| base_body << str}
27
- should_decode? ? MultiJson.load(base_body) : base_body
28
- end
29
-
30
- def should_decode?
31
- @headers["Content-Type"] =~ /^application\/json/ &&
32
- BatchApi.config.decode_json_responses
34
+ base_body
33
35
  end
34
36
  end
35
37
  end
@@ -1,3 +1,3 @@
1
1
  module BatchApi
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/readme.md CHANGED
@@ -1,7 +1,52 @@
1
- A proposal for a Batch API endpoint.
1
+ [![Build Status](https://secure.travis-ci.org/arsduo/batch_api.png?branch=master)](http://travis-ci.org/arsduo/batch_api)
2
2
 
3
- Batch requests take the form of a series of REST API requests,
4
- each containing the following arguments:
3
+ ## What's this?
4
+
5
+ A gem that provides a RESTful Batch API for Rails and other Rack applications.
6
+ In this system, batch requests are simply collections of regular REST calls,
7
+ whose results are returned as an equivalent collection of regular REST results.
8
+
9
+ This is heavily inspired by [Facebook's Batch API](http://developers.facebook.com/docs/reference/api/batch/).
10
+
11
+ ## A Quick Example
12
+
13
+ Making a batch request:
14
+
15
+ ```
16
+ # POST /batch
17
+ # Content-Type: application/json
18
+
19
+ {
20
+ ops: [
21
+ {method: "get", url: "/patrons"},
22
+ {method: "post", url: "/orders/new", params: {dish_id: 123}},
23
+ {method: "get", url: "/oh/no/error", headers: {break: "fast"}},
24
+ {method: "delete", url: "/patrons/456"}
25
+ ],
26
+ sequential: true
27
+ }
28
+ ```
29
+
30
+ Reading the response:
31
+
32
+ ```
33
+ {
34
+ results: [
35
+ {status: 200, body: [{id: 1, name: "Jim-Bob"}, ...], headers: {}},
36
+ {status: 201, body: {id: 4, dish_name: "Spicy Crab Legs"}, headers: {}},
37
+ {status: 500, body: {error: {oh: "noes!"}}, headers: {Problem: "woops"}},
38
+ {status: 200, body: null, headers: {}}}
39
+ ]
40
+ }
41
+ ```
42
+
43
+ ### How It Works
44
+
45
+ #### Requests
46
+
47
+ As you can see from the example above, each request in the batch (an
48
+ "operation", in batch parlance) describes the same features any HTTP request
49
+ would include:
5
50
 
6
51
  * _url_ - the API endpoint to hit, formatted exactly as you would for a regular
7
52
  REST API request (e.g. leading /, etc.)
@@ -9,137 +54,165 @@ REST API request (e.g. leading /, etc.)
9
54
  * _args_ - a hash of arguments to the API. This can be used for both GET and
10
55
  PUT/POST/PATCH requests.
11
56
  * _headers_ - a hash of request-specific headers. (The headers sent in the
12
- request will be included as well, with request-specific headers taking
57
+ request will be included as well, with operation-specific headers taking
13
58
  precendence.)
14
- * _options_ - a hash of additional batch request options. There are currently
15
- none supported, but we plan to introduce some for dependency management,
16
- supressing output, etc. in the future.
17
59
 
18
- The Batch API endpoint itself (which lives at POST /batch) takes the
19
- following arguments:
60
+ These individual operations are supplied as the "ops" parameter in the
61
+ overall request. Other options include:
20
62
 
21
- * _ops_ - an array of operations to perform, specified as described above.
22
63
  * _sequential_ - execute all operations sequentially, rather than in parallel.
23
- *THIS PARAMETER IS CURRENTLY REQUIRED AND MUST BE SET TO TRUE.* (In the future
24
- we'll offer parallel processing by default, and hence this parameter must be
25
- supplied in order topreserve expected behavior.
64
+ *This parameter is currently REQUIRED and must be set to true.* (In the future
65
+ the Batch API will offer parallel processing for thread-safe apps, and hence
66
+ this parameter must be supplied in order to explicitly preserve expected
67
+ behavior.)
26
68
 
27
- Other options may be defined in the future.
69
+ Other options may be provided in the future for both the global request
70
+ and individual operations.
28
71
 
29
- Users must be logged in to use the Batch API.
72
+ ### Responses
30
73
 
31
- The Batch API returns an array of results in the same order the operations are
32
- specified. Each result contains:
74
+ The Batch API will always return a 200, with a JSON body containing the
75
+ individual responses under the "results" key. Those responses, in turn,
76
+ contain the same main components of any HTTP response:
33
77
 
34
78
  * _status_ - the HTTP status (200, 201, 400, etc.)
35
79
  * _body_ - the rendered body
36
80
  * _headers_ - any response headers
37
- * _cookies_ - any cookies set by the request. (These will in the future be
38
- pulled into the main response to be processed by the client.)
81
+
82
+ ### Errors
39
83
 
40
84
  Errors in individual Batch API requests will be returned inline, with the
41
- same status code and body they would return as individual requests. If the
42
- Batch API itself returns a non-200 status code, that indicates a global
43
- problem:
85
+ same status code and body they would return as individual requests.
44
86
 
45
- * _403_ - if the user isn't logged in
46
- * _422_ - if the batch request isn't properly formatted
47
- * _500_ - if there's an application error in the Batch API code
87
+ If the Batch API itself returns a non-200 status code, that indicates a global
88
+ problem.
48
89
 
49
- ** Examples **
90
+ ## Why a Batch API?
50
91
 
51
- Given the following request:
92
+ Batch APIs, though unRESTful, are useful for reducing HTTP overhead
93
+ by combining requests; this is particularly valuable for mobile clients,
94
+ which may generate groups of offline actions and which desire to
95
+ reduce battery consumption while connected by making fewer, better-compressed
96
+ requests.
52
97
 
53
- ```ruby
54
- {
55
- ops: [
56
- {
57
- method: "post",
58
- url: "/resource/create",
59
- args: {title: "bar", data: "foo"}
60
- },
61
- {
62
- method: "get",
63
- url: "/other_resource/123/connections"
64
- },
65
- {
66
- method: "get",
67
- url: "/i/gonna/throw/an/error",
68
- header: { some: "headers" }
69
- }
70
- ]
71
- }
72
- ```
98
+ ### Why not HTTP Pipelining?
99
+
100
+ HTTP pipelining is an awesome and promising technology, and would provide a
101
+ simple and effortless way to parallel process many requests; however, using
102
+ pipelining raised several issues for us, one of which was a blocker:
103
+
104
+ * [Lack of browser
105
+ support](http://en.wikipedia.org/wiki/HTTP_pipelining#Implementation_in_web_browsers):
106
+ a number of key browsers do not yet support HTTP pipelining (or have it
107
+ disabled by default). This will of course change in time,
108
+ but for now this takes pipelining out of consideration. (There a similar but
109
+ more minor issue
110
+ with [many web
111
+ proxies](http://en.wikipedia.org/wiki/HTTP_pipelining#Implementation_in_web_proxies).)
112
+ * The HTTP pipelining specification states that non-idempotent requests (e.g.
113
+ [POST](http://en.wikipedia.org/wiki/HTTP_pipelining) and
114
+ [in some
115
+ descriptions](http://www-archive.mozilla.org/projects/netlib/http/pipelining-faq.html) PUT)
116
+ shouldn't be made via pipelining. Though I have heard that some server
117
+ implementations do support POST requests (putting all subsequent requests on
118
+ hold until it's done), for applications that submit a lot of POSTs this raised
119
+ concerns as well.
120
+
121
+ Given this state of affairs -- and my desire to hack up a Batch API gem :P --,
122
+ we decided to implement an API-based solution.
123
+
124
+ ### Why this Approach?
125
+
126
+ There are two main approaches to writing batch APIs:
127
+
128
+ * A limited, specialized batch endpoint (or endpoints), which usually handles
129
+ updates and creates. DHH sketched out such a bulk update/create endpoint
130
+ for Rails 3.2 [in a gist](https://gist.github.com/981520) last year.
131
+ * A general-purpose RESTful API that can handle anything in your application,
132
+ a la the Facebook Batch API.
133
+
134
+ The second approach, IMO, minimizes code duplication and complexity. Rather
135
+ than have two systems that manage resources (or a more complicated one that
136
+ can handle both batch and individual requests), we simply route requests as we
137
+ always would.
73
138
 
74
- You'd get the following back:
139
+ This solution has several specific benefits:
75
140
 
76
- ```ruby
77
- [
78
- {status: 201, body: "{json:\"data\"}", headers: {}, cookies: {}},
79
- {status: 200, body: "[{json:\"data\"}, {more:\"data\"}]", headers: {}, cookies: {}},
80
- {status: 500, body: "{error:\"message\"}", headers: {}, cookies: {}},
81
- ]
82
- ```
141
+ * Less complexity - non-batch endpoints don't need any extra code, which means
142
+ less to maintain on your end.
143
+ * Complete flexibility - as you add new features to your application,
144
+ they become immediately and automatically available via the Batch API.
145
+ * More RESTful - as individual operations are simply actions on RESTful
146
+ resources, you preserve an important characteristic of your API.
83
147
 
84
- ** Implementation**
148
+ As well as the general benefits of all batch operations:
85
149
 
86
- For each request, we:
87
- * attempt to route it as Rails would (identifying controller and action)
88
- * create a customized request.env hash with the appropriate details
89
- * instantiate the controller and invoke the action
90
- * parse and process the result
150
+ * Reuse of state - user authentication, request stack processing, and
151
+ similar processing only needs to be done once.
152
+ * Better for clients - clients need to make fewer requests, as described above.
153
+ * Parallelizable - in the future, we could run requests in parallel (if
154
+ our app is thread-safe). Clients would be able to explicitly specify
155
+ dependencies between operations (or simply run all sequentially). This
156
+ should make for some fun experimentation :)
91
157
 
92
- The overall result is then returned to the client.
158
+ There's only one downside I can think of to this approach as opposed to a
159
+ specialized endpoint:
93
160
 
94
- **Background**
161
+ * Reduced ability to optimize - unlike a specialized API endpoint, each request
162
+ will be treated in isolation, which makes it harder to optimize the
163
+ underlying database queries via more efficient (read: complicated) SQL logic.
164
+ (Better identity maps would help with this, and since the main pain point
165
+ this approach addresses is at the HTTP connection layer, I submit we can
166
+ accept this.)
95
167
 
96
- Batch APIs, though unRESTful, are useful for reducing HTTP overhead
97
- by combining requests; this is particularly valuable for mobile clients,
98
- which may generate groups of offline actions and which desire to
99
- reduce battery consumption while connected by making fewer, better-compressed
100
- requests.
168
+ ## Implementation
101
169
 
102
- Generally, such interfaces fall into two categories:
170
+ The Batch API is implemented as a Rack middleware. Here's how it works:
103
171
 
104
- * a set of limited, specialized instructions, usually to manage resources
105
- * a general-purpose API that can take any operation the main API can
106
- handle
172
+ First, if the request isn't a batch request (as defined by the endpoint and
173
+ method in BatchApi.config), it gets processed normally by your app.
107
174
 
108
- The second approach minimizes code duplication and complexity. Rather than
109
- have two systems that manage resources (or a more complicated one that can
110
- handle both batch and individual requests), we simply route requests as we
111
- always would.
175
+ If it is a batch request, we:
176
+ * Read and validate the parameters for the request, constructing a
177
+ representation of the operation.
178
+ * Compile a customized Rack environment hash with the appropriate parameters,
179
+ so that your app interprets the request as being for the appropriate action.
180
+ (This is requires a bit of extra processing for Rails.)
181
+ * Send each request up the middleware stack as normal, collecting the results.
182
+ Errors are caught and recorded appropriately.
183
+ * Send you back the results.
112
184
 
113
- This approach has several benefits:
185
+ At both the batch level (processing all requests) and the individual operation
186
+ request, there is an internal, customizable midleware stack that you can
187
+ customize to insert additional custom behavior, such as handling authentication
188
+ or decoding JSON bodies for individual requests (this latter comes
189
+ pre-included). Check out the lib/batch_api/internal_middleware.rb for more
190
+ information.
114
191
 
115
- * Less complexity - non-batch endpoints don't need any extra code
116
- * Complete flexibility - as we add new features or endpoints to the API,
117
- they become immediately available via the Batch API.
118
- * More RESTful - as individual operations are simply actions on RESTful
119
- resources, we preserve an important characteristic of the API.
192
+ ## To Do
120
193
 
121
- As well as general benefits of using the Batch API:
194
+ The core of the Batch API is complete and solid, and so ready to go that it's
195
+ in use at 6Wunderkinder already :P
122
196
 
123
- * Parallelizable - in the future, we could run requests in parallel (if
124
- our Rails app is running in thread-safe mode), allowing clients to
125
- specify explicit dependencies between operations (or run all
126
- sequentially).
127
- * Reuse of state - user authentication, request stack processing, and
128
- similar processing only needs to be done once.
129
- * Better for clients - fewer requests, better compressibility, etc.
130
- (as described above)
131
-
132
- There are two main downsides to our implementation:
133
-
134
- * Rails dependency - we use only public Rails interfaces, but these could
135
- still change with major updates. (_Resolution:_ with good testing we
136
- can identify changes and update code as needed.)
137
- * Reduced ability to optimize cross-request - unlike a specialized API,
138
- each request will be treated in isolation, and so you couldn't minimize
139
- DB updates through more complicated SQL logic. (_Resolution:_ none, but
140
- the main pain point currently is at the HTTP connection layer, so we
141
- accept this.)
142
-
143
- Once the Batch API is more developed, we'll spin it off into a gem, and
144
- possibly make it easy to create versions for Sinatra or other frameworks,
145
- if desired.
197
+ Here are some immediate tasks:
198
+
199
+ * Test against additional frameworks (beyond Rails and Sinatra)
200
+ * Write more usage docs / create a wiki.
201
+ * Add additional features inspired by the Facebook API, such as the ability to
202
+ surpress output for individual requests, etc.
203
+ * Add RDoc to the spec task and ensure all methods are documented.
204
+ * Research and implement parallelization and dependency management.
205
+
206
+ ## Thanks
207
+
208
+ To 6Wunderkinder, for all their support for this open-source project, and their
209
+ general awesomeness.
210
+
211
+ To Facebook, for providing inspiration and a great implementation in this and
212
+ many other things.
213
+
214
+ To [JT Archie](http://github.com/jtarchie) for his help and feedback.
215
+
216
+ ## Issues? Questions? Ideas?
217
+
218
+ Open a ticket or send a pull request!
@@ -0,0 +1,261 @@
1
+ == Welcome to Rails
2
+
3
+ Rails is a web-application framework that includes everything needed to create
4
+ database-backed web applications according to the Model-View-Control pattern.
5
+
6
+ This pattern splits the view (also called the presentation) into "dumb"
7
+ templates that are primarily responsible for inserting pre-built data in between
8
+ HTML tags. The model contains the "smart" domain objects (such as Account,
9
+ Product, Person, Post) that holds all the business logic and knows how to
10
+ persist themselves to a database. The controller handles the incoming requests
11
+ (such as Save New Account, Update Product, Show Post) by manipulating the model
12
+ and directing data to the view.
13
+
14
+ In Rails, the model is handled by what's called an object-relational mapping
15
+ layer entitled Active Record. This layer allows you to present the data from
16
+ database rows as objects and embellish these data objects with business logic
17
+ methods. You can read more about Active Record in
18
+ link:files/vendor/rails/activerecord/README.html.
19
+
20
+ The controller and view are handled by the Action Pack, which handles both
21
+ layers by its two parts: Action View and Action Controller. These two layers
22
+ are bundled in a single package due to their heavy interdependence. This is
23
+ unlike the relationship between the Active Record and Action Pack that is much
24
+ more separate. Each of these packages can be used independently outside of
25
+ Rails. You can read more about Action Pack in
26
+ link:files/vendor/rails/actionpack/README.html.
27
+
28
+
29
+ == Getting Started
30
+
31
+ 1. At the command prompt, create a new Rails application:
32
+ <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
33
+
34
+ 2. Change directory to <tt>myapp</tt> and start the web server:
35
+ <tt>cd myapp; rails server</tt> (run with --help for options)
36
+
37
+ 3. Go to http://localhost:3000/ and you'll see:
38
+ "Welcome aboard: You're riding Ruby on Rails!"
39
+
40
+ 4. Follow the guidelines to start developing your application. You can find
41
+ the following resources handy:
42
+
43
+ * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
44
+ * Ruby on Rails Tutorial Book: http://www.railstutorial.org/
45
+
46
+
47
+ == Debugging Rails
48
+
49
+ Sometimes your application goes wrong. Fortunately there are a lot of tools that
50
+ will help you debug it and get it back on the rails.
51
+
52
+ First area to check is the application log files. Have "tail -f" commands
53
+ running on the server.log and development.log. Rails will automatically display
54
+ debugging and runtime information to these files. Debugging info will also be
55
+ shown in the browser on requests from 127.0.0.1.
56
+
57
+ You can also log your own messages directly into the log file from your code
58
+ using the Ruby logger class from inside your controllers. Example:
59
+
60
+ class WeblogController < ActionController::Base
61
+ def destroy
62
+ @weblog = Weblog.find(params[:id])
63
+ @weblog.destroy
64
+ logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
65
+ end
66
+ end
67
+
68
+ The result will be a message in your log file along the lines of:
69
+
70
+ Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
71
+
72
+ More information on how to use the logger is at http://www.ruby-doc.org/core/
73
+
74
+ Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
75
+ several books available online as well:
76
+
77
+ * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
78
+ * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
79
+
80
+ These two books will bring you up to speed on the Ruby language and also on
81
+ programming in general.
82
+
83
+
84
+ == Debugger
85
+
86
+ Debugger support is available through the debugger command when you start your
87
+ Mongrel or WEBrick server with --debugger. This means that you can break out of
88
+ execution at any point in the code, investigate and change the model, and then,
89
+ resume execution! You need to install ruby-debug to run the server in debugging
90
+ mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
91
+
92
+ class WeblogController < ActionController::Base
93
+ def index
94
+ @posts = Post.all
95
+ debugger
96
+ end
97
+ end
98
+
99
+ So the controller will accept the action, run the first line, then present you
100
+ with a IRB prompt in the server window. Here you can do things like:
101
+
102
+ >> @posts.inspect
103
+ => "[#<Post:0x14a6be8
104
+ @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
105
+ #<Post:0x14a6620
106
+ @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
107
+ >> @posts.first.title = "hello from a debugger"
108
+ => "hello from a debugger"
109
+
110
+ ...and even better, you can examine how your runtime objects actually work:
111
+
112
+ >> f = @posts.first
113
+ => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
114
+ >> f.
115
+ Display all 152 possibilities? (y or n)
116
+
117
+ Finally, when you're ready to resume execution, you can enter "cont".
118
+
119
+
120
+ == Console
121
+
122
+ The console is a Ruby shell, which allows you to interact with your
123
+ application's domain model. Here you'll have all parts of the application
124
+ configured, just like it is when the application is running. You can inspect
125
+ domain models, change values, and save to the database. Starting the script
126
+ without arguments will launch it in the development environment.
127
+
128
+ To start the console, run <tt>rails console</tt> from the application
129
+ directory.
130
+
131
+ Options:
132
+
133
+ * Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
134
+ made to the database.
135
+ * Passing an environment name as an argument will load the corresponding
136
+ environment. Example: <tt>rails console production</tt>.
137
+
138
+ To reload your controllers and models after launching the console run
139
+ <tt>reload!</tt>
140
+
141
+ More information about irb can be found at:
142
+ link:http://www.rubycentral.org/pickaxe/irb.html
143
+
144
+
145
+ == dbconsole
146
+
147
+ You can go to the command line of your database directly through <tt>rails
148
+ dbconsole</tt>. You would be connected to the database with the credentials
149
+ defined in database.yml. Starting the script without arguments will connect you
150
+ to the development database. Passing an argument will connect you to a different
151
+ database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
152
+ PostgreSQL and SQLite 3.
153
+
154
+ == Description of Contents
155
+
156
+ The default directory structure of a generated Ruby on Rails application:
157
+
158
+ |-- app
159
+ | |-- assets
160
+ | |-- images
161
+ | |-- javascripts
162
+ | `-- stylesheets
163
+ | |-- controllers
164
+ | |-- helpers
165
+ | |-- mailers
166
+ | |-- models
167
+ | `-- views
168
+ | `-- layouts
169
+ |-- config
170
+ | |-- environments
171
+ | |-- initializers
172
+ | `-- locales
173
+ |-- db
174
+ |-- doc
175
+ |-- lib
176
+ | `-- tasks
177
+ |-- log
178
+ |-- public
179
+ |-- script
180
+ |-- test
181
+ | |-- fixtures
182
+ | |-- functional
183
+ | |-- integration
184
+ | |-- performance
185
+ | `-- unit
186
+ |-- tmp
187
+ | |-- cache
188
+ | |-- pids
189
+ | |-- sessions
190
+ | `-- sockets
191
+ `-- vendor
192
+ |-- assets
193
+ `-- stylesheets
194
+ `-- plugins
195
+
196
+ app
197
+ Holds all the code that's specific to this particular application.
198
+
199
+ app/assets
200
+ Contains subdirectories for images, stylesheets, and JavaScript files.
201
+
202
+ app/controllers
203
+ Holds controllers that should be named like weblogs_controller.rb for
204
+ automated URL mapping. All controllers should descend from
205
+ ApplicationController which itself descends from ActionController::Base.
206
+
207
+ app/models
208
+ Holds models that should be named like post.rb. Models descend from
209
+ ActiveRecord::Base by default.
210
+
211
+ app/views
212
+ Holds the template files for the view that should be named like
213
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
214
+ eRuby syntax by default.
215
+
216
+ app/views/layouts
217
+ Holds the template files for layouts to be used with views. This models the
218
+ common header/footer method of wrapping views. In your views, define a layout
219
+ using the <tt>layout :default</tt> and create a file named default.html.erb.
220
+ Inside default.html.erb, call <% yield %> to render the view using this
221
+ layout.
222
+
223
+ app/helpers
224
+ Holds view helpers that should be named like weblogs_helper.rb. These are
225
+ generated for you automatically when using generators for controllers.
226
+ Helpers can be used to wrap functionality for your views into methods.
227
+
228
+ config
229
+ Configuration files for the Rails environment, the routing map, the database,
230
+ and other dependencies.
231
+
232
+ db
233
+ Contains the database schema in schema.rb. db/migrate contains all the
234
+ sequence of Migrations for your schema.
235
+
236
+ doc
237
+ This directory is where your application documentation will be stored when
238
+ generated using <tt>rake doc:app</tt>
239
+
240
+ lib
241
+ Application specific libraries. Basically, any kind of custom code that
242
+ doesn't belong under controllers, models, or helpers. This directory is in
243
+ the load path.
244
+
245
+ public
246
+ The directory available for the web server. Also contains the dispatchers and the
247
+ default HTML files. This should be set as the DOCUMENT_ROOT of your web
248
+ server.
249
+
250
+ script
251
+ Helper scripts for automation and generation.
252
+
253
+ test
254
+ Unit and functional tests along with fixtures. When using the rails generate
255
+ command, template test files will be generated for you and placed in this
256
+ directory.
257
+
258
+ vendor
259
+ External libraries that the application depends on. Also includes the plugins
260
+ subdirectory. If the app has frozen rails, those gems also go here, under
261
+ vendor/rails/. This directory is in the load path.