mirror of https://github.com/rails/rails
Remove --http.
This commit is contained in:
parent
1efeb072bf
commit
6db930cb5b
|
@ -1,7 +1,5 @@
|
|||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Support API apps http://edgeguides.rubyonrails.org/api_app.html *Santiago Pastorino and Carlos Antonio da Silva*
|
||||
|
||||
* Add `include_hidden` option to select tag. With `:include_hidden => false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
|
||||
|
||||
* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt*
|
||||
|
|
|
@ -6,7 +6,6 @@ module ActionController
|
|||
|
||||
autoload :Base
|
||||
autoload :Caching
|
||||
autoload :HTTP
|
||||
autoload :Metal
|
||||
autoload :Middleware
|
||||
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
require "action_controller/log_subscriber"
|
||||
|
||||
module ActionController
|
||||
# HTTP Controller is a lightweight version of <tt>ActionController::Base</tt>,
|
||||
# created for applications that don't require all functionality that a complete
|
||||
# \Rails controller provides, allowing you to create faster controllers. The
|
||||
# main scenario where HTTP Controllers could be used is API only applications.
|
||||
#
|
||||
# An HTTP Controller is different from a normal controller in the sense that
|
||||
# by default it doesn't include a number of features that are usually required
|
||||
# by browser access only: layouts and templates rendering, cookies, sessions,
|
||||
# flash, assets, and so on. This makes the entire controller stack thinner and
|
||||
# faster, suitable for API applications. It doesn't mean you won't have such
|
||||
# features if you need them: they're all available for you to include in
|
||||
# your application, they're just not part of the default HTTP Controller stack.
|
||||
#
|
||||
# By default, only the ApplicationController in a \Rails application inherits
|
||||
# from <tt>ActionController::HTTP</tt>. All other controllers in turn inherit
|
||||
# from ApplicationController.
|
||||
#
|
||||
# A sample controller could look like this:
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# render json: @posts
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Request, response and parameters objects all work the exact same way as
|
||||
# <tt>ActionController::Base</tt>.
|
||||
#
|
||||
# == Renders
|
||||
#
|
||||
# The default HTTP Controller stack includes all renderers, which means you
|
||||
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
|
||||
# in mind that templates are not going to be rendered, so you need to ensure
|
||||
# your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
|
||||
# all actions.
|
||||
#
|
||||
# def show
|
||||
# @post = Post.find(params[:id])
|
||||
# render json: @post
|
||||
# end
|
||||
#
|
||||
# == Redirects
|
||||
#
|
||||
# Redirects are used to move from one action to another. You can use the
|
||||
# <tt>redirect</tt> method in your controllers in the same way as
|
||||
# <tt>ActionController::Base</tt>. For example:
|
||||
#
|
||||
# def create
|
||||
# redirect_to root_url and return if not_authorized?
|
||||
# # do stuff here
|
||||
# end
|
||||
#
|
||||
# == Adding new behavior
|
||||
#
|
||||
# In some scenarios you may want to add back some functionality provided by
|
||||
# <tt>ActionController::Base</tt> that is not present by default in
|
||||
# <tt>ActionController::HTTP</tt>, for instance <tt>MimeResponds</tt>. This
|
||||
# module gives you the <tt>respond_to</tt> and <tt>respond_with</tt> methods.
|
||||
# Adding it is quite simple, you just need to include the module in a specific
|
||||
# controller or in <tt>ApplicationController</tt> in case you want it
|
||||
# available to your entire app:
|
||||
#
|
||||
# class ApplicationController < ActionController::HTTP
|
||||
# include ActionController::MimeResponds
|
||||
# end
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# respond_to :json, :xml
|
||||
#
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# respond_with @posts
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
|
||||
# available modules if you want to include any other functionality that is
|
||||
# not provided by <tt>ActionController::HTTP</tt> out of the box.
|
||||
class HTTP < Metal
|
||||
abstract!
|
||||
|
||||
# Shortcut helper that returns all the ActionController::HTTP modules except the ones passed in the argument:
|
||||
#
|
||||
# class MetalController
|
||||
# ActionController::HTTP.without_modules(:ParamsWrapper, :Streaming).each do |left|
|
||||
# include left
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This gives better control over what you want to exclude and makes it easier
|
||||
# to create a bare controller class, instead of listing the modules required manually.
|
||||
def self.without_modules(*modules)
|
||||
modules = modules.map do |m|
|
||||
m.is_a?(Symbol) ? ActionController.const_get(m) : m
|
||||
end
|
||||
|
||||
MODULES - modules
|
||||
end
|
||||
|
||||
MODULES = [
|
||||
HideActions,
|
||||
UrlFor,
|
||||
Redirecting,
|
||||
Rendering,
|
||||
Renderers::All,
|
||||
ConditionalGet,
|
||||
RackDelegation,
|
||||
|
||||
ForceSSL,
|
||||
DataStreaming,
|
||||
|
||||
# Before callbacks should also be executed the earliest as possible, so
|
||||
# also include them at the bottom.
|
||||
AbstractController::Callbacks,
|
||||
|
||||
# Append rescue at the bottom to wrap as much as possible.
|
||||
Rescue,
|
||||
|
||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
||||
# all the methods properly.
|
||||
Instrumentation
|
||||
]
|
||||
|
||||
MODULES.each do |mod|
|
||||
include mod
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:action_controller, self)
|
||||
end
|
||||
end
|
|
@ -293,10 +293,6 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
class HTTP
|
||||
include SharedTestRoutes.url_helpers
|
||||
end
|
||||
|
||||
class TestCase
|
||||
include ActionDispatch::TestProcess
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ActionMethodsHTTPController < ActionController::HTTP
|
||||
def one; end
|
||||
def two; end
|
||||
hide_action :two
|
||||
end
|
||||
|
||||
class ActionMethodsHTTPTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@controller = ActionMethodsHTTPController.new
|
||||
end
|
||||
|
||||
def test_action_methods
|
||||
assert_equal Set.new(%w(one)),
|
||||
@controller.class.action_methods,
|
||||
"#{@controller.controller_path} should not be empty!"
|
||||
end
|
||||
end
|
|
@ -1,55 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ConditionalGetHTTPController < ActionController::HTTP
|
||||
before_filter :handle_last_modified_and_etags, :only => :two
|
||||
|
||||
def one
|
||||
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123])
|
||||
render :text => "Hi!"
|
||||
end
|
||||
end
|
||||
|
||||
def two
|
||||
render :text => "Hi!"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_last_modified_and_etags
|
||||
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
|
||||
end
|
||||
end
|
||||
|
||||
class ConditionalGetHTTPTest < ActionController::TestCase
|
||||
tests ConditionalGetHTTPController
|
||||
|
||||
def setup
|
||||
@last_modified = Time.now.utc.beginning_of_day.httpdate
|
||||
end
|
||||
|
||||
def test_request_with_bang_gets_last_modified
|
||||
get :two
|
||||
assert_equal @last_modified, @response.headers['Last-Modified']
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_request_with_bang_obeys_last_modified
|
||||
@request.if_modified_since = @last_modified
|
||||
get :two
|
||||
assert_response :not_modified
|
||||
end
|
||||
|
||||
def test_last_modified_works_with_less_than_too
|
||||
@request.if_modified_since = 5.years.ago.httpdate
|
||||
get :two
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_request_not_modified
|
||||
@request.if_modified_since = @last_modified
|
||||
get :one
|
||||
assert_equal 304, @response.status.to_i
|
||||
assert_blank @response.body
|
||||
assert_equal @last_modified, @response.headers['Last-Modified']
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
module TestHTTPFileUtils
|
||||
def file_name() File.basename(__FILE__) end
|
||||
def file_path() File.expand_path(__FILE__) end
|
||||
def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
|
||||
end
|
||||
|
||||
class DataStreamingHTTPController < ActionController::HTTP
|
||||
include TestHTTPFileUtils
|
||||
|
||||
def one; end
|
||||
def two
|
||||
send_data(file_data, {})
|
||||
end
|
||||
end
|
||||
|
||||
class DataStreamingHTTPTest < ActionController::TestCase
|
||||
include TestHTTPFileUtils
|
||||
tests DataStreamingHTTPController
|
||||
|
||||
def test_data
|
||||
response = process('two')
|
||||
assert_kind_of String, response.body
|
||||
assert_equal file_data, response.body
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ForceSSLHTTPController < ActionController::HTTP
|
||||
force_ssl
|
||||
|
||||
def one; end
|
||||
def two
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
class ForceSSLHTTPTest < ActionController::TestCase
|
||||
tests ForceSSLHTTPController
|
||||
|
||||
def test_banana_redirects_to_https
|
||||
get :two
|
||||
assert_response 301
|
||||
assert_equal "https://test.host/force_sslhttp/two", redirect_to_url
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class RedirectToHTTPController < ActionController::HTTP
|
||||
def one
|
||||
redirect_to :action => "two"
|
||||
end
|
||||
|
||||
def two; end
|
||||
end
|
||||
|
||||
class RedirectToHTTPTest < ActionController::TestCase
|
||||
tests RedirectToHTTPController
|
||||
|
||||
def test_redirect_to
|
||||
get :one
|
||||
assert_response :redirect
|
||||
assert_equal "http://test.host/redirect_to_http/two", redirect_to_url
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class Model
|
||||
def to_json(options = {})
|
||||
{ :a => 'b' }.to_json(options)
|
||||
end
|
||||
|
||||
def to_xml(options = {})
|
||||
{ :a => 'b' }.to_xml(options)
|
||||
end
|
||||
end
|
||||
|
||||
class RenderersHTTPController < ActionController::HTTP
|
||||
def one
|
||||
render :json => Model.new
|
||||
end
|
||||
|
||||
def two
|
||||
render :xml => Model.new
|
||||
end
|
||||
end
|
||||
|
||||
class RenderersHTTPTest < ActionController::TestCase
|
||||
tests RenderersHTTPController
|
||||
|
||||
def test_render_json
|
||||
get :one
|
||||
assert_response :success
|
||||
assert_equal({ :a => 'b' }.to_json, @response.body)
|
||||
end
|
||||
|
||||
def test_render_xml
|
||||
get :two
|
||||
assert_response :success
|
||||
assert_equal({ :a => 'b' }.to_xml, @response.body)
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class UrlForHTTPController < ActionController::HTTP
|
||||
def one; end
|
||||
def two; end
|
||||
end
|
||||
|
||||
class UrlForHTTPTest < ActionController::TestCase
|
||||
tests UrlForHTTPController
|
||||
|
||||
def setup
|
||||
super
|
||||
@request.host = 'www.example.com'
|
||||
end
|
||||
|
||||
def test_url_for
|
||||
get :one
|
||||
assert_equal "http://www.example.com/url_for_http/one", @controller.url_for
|
||||
end
|
||||
end
|
|
@ -1,271 +0,0 @@
|
|||
h2. Using Rails for API-only Apps
|
||||
|
||||
In this guide you will learn:
|
||||
|
||||
* What Rails provides for API-only applications
|
||||
* How to configure Rails to start without any browser features
|
||||
* How to decide which middlewares you will want to include
|
||||
* How to decide which modules to use in your controller
|
||||
|
||||
endprologue.
|
||||
|
||||
h3. What is an API app?
|
||||
|
||||
Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application.
|
||||
For example, GitHub provides "an API":http://developer.github.com that you can use from your own custom clients.
|
||||
|
||||
With the advent of client-side frameworks, more developers are using Rails to build a backend that is shared between their web application and other native applications.
|
||||
|
||||
For example, Twitter uses its "public API":https://dev.twitter.com in its web application, which is built as a static site that consumes JSON resources.
|
||||
|
||||
Instead of using Rails to generate dynamic HTML that will communicate with the server through forms and links, many developers are treating their web application as just another client, delivered as static HTML, CSS and JavaScript, and consuming a simple JSON API
|
||||
|
||||
This guide covers building a Rails application that serves JSON resources to an API client *or* client-side framework.
|
||||
|
||||
h3. Why use Rails for JSON APIs?
|
||||
|
||||
The first question a lot of people have when thinking about building a JSON API using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I just use something like Sinatra?"
|
||||
|
||||
For very simple APIs, this may be true. However, even in very HTML-heavy applications, most of an application's logic is actually outside of the view layer.
|
||||
|
||||
The reason most people use Rails is that it provides a set of defaults that allows us to get up and running quickly without having to make a lot of trivial decisions.
|
||||
|
||||
Let's take a look at some of the things that Rails provides out of the box that are still applicable to API applications.
|
||||
|
||||
Handled at the middleware layer:
|
||||
|
||||
* Reloading: Rails applications support transparent reloading. This works even if your application gets big and restarting the server for every request becomes non-viable.
|
||||
* Development Mode: Rails application come with smart defaults for development, making development pleasant without compromising production-time performance.
|
||||
* Test Mode: Ditto test mode.
|
||||
* Logging: Rails applications log every request, with a level of verbosity appropriate for the current mode. Rails logs in development include information about the request environment, database queries, and basic performance information.
|
||||
* Security: Rails detects and thwarts "IP spoofing attacks":http://en.wikipedia.org/wiki/IP_address_spoofing and handles cryptographic signatures in a "timing attack":http://en.wikipedia.org/wiki/Timing_attack aware way. Don't know what an IP spoofing attack or a timing attack is? Exactly.
|
||||
* Parameter Parsing: Want to specify your parameters as JSON instead of as a URL-encoded String? No problem. Rails will decode the JSON for you and make it available in +params+. Want to use nested URL-encoded params? That works too.
|
||||
* Conditional GETs: Rails handles conditional +GET+, (+ETag+ and +Last-Modified+), processing request headers and returning the correct response headers and status code. All you need to do is use the "stale?":http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F check in your controller, and Rails will handle all of the HTTP details for you.
|
||||
* Caching: If you use +dirty?+ with public cache control, Rails will automatically cache your responses. You can easily configure the cache store.
|
||||
* HEAD requests: Rails will transparently convert +HEAD+ requests into +GET+ requests, and return just the headers on the way out. This makes +HEAD+ work reliably in all Rails APIs.
|
||||
|
||||
While you could obviously build these up in terms of existing Rack middlewares, I think this list demonstrates that the default Rails middleware stack provides a lot of value, even if you're "just generating JSON".
|
||||
|
||||
Handled at the ActionPack layer:
|
||||
|
||||
* Resourceful Routing: If you're building a RESTful JSON API, you want to be using the Rails router. Clean and conventional mapping from HTTP to controllers means not having to spend time thinking about how to model your API in terms of HTTP.
|
||||
* URL Generation: The flip side of routing is URL generation. A good API based on HTTP includes URLs (see "the GitHub gist API":http://developer.github.com/v3/gists/ for an example).
|
||||
* Header and Redirection Responses: +head :no_content+ and +redirect_to user_url(current_user)+ come in handy. Sure, you could manually add the response headers, but why?
|
||||
* Caching: Rails provides page, action and fragment caching. Fragment caching is especially helpful when building up a nested JSON object.
|
||||
* Basic, Digest and Token Authentication: Rails comes with out-of-the-box support for three kinds of HTTP authentication.
|
||||
* Instrumentation: Rails 3.0 added an instrumentation API that will trigger registered handlers for a variety of events, such as action processing, sending a file or data, redirection, and database queries. The payload of each event comes with relevant information (for the action processing event, the payload includes the controller, action, params, request format, request method and the request's full path).
|
||||
* Generators: This may be passé for advanced Rails users, but it can be nice to generate a resource and get your model, controller, test stubs, and routes created for you in a single command.
|
||||
* Plugins: Many third-party libraries come with support for Rails that reduces or eliminates the cost of setting up and gluing together the library and the web framework. This includes things like overriding default generators, adding rake tasks, and honoring Rails choices (like the logger and cache backend).
|
||||
|
||||
Of course, the Rails boot process also glues together all registered components. For example, the Rails boot process is what uses your +config/database.yml+ file when configuring ActiveRecord.
|
||||
|
||||
**The short version is**: you may not have thought about which parts of Rails are still applicable even if you remove the view layer, but the answer turns out to be "most of it".
|
||||
|
||||
h3. The Basic Configuration
|
||||
|
||||
If you're building a Rails application that will be an API server first and foremost, you can start with a more limited subset of Rails and add in features as needed.
|
||||
|
||||
You can generate a new bare Rails app:
|
||||
|
||||
<shell>
|
||||
$ rails new my_api --http
|
||||
</shell>
|
||||
|
||||
This will do three main things for you:
|
||||
|
||||
* Configure your application to start with a more limited set of middleware than normal. Specifically, it will not include any middleware primarily useful for browser applications (like cookie support) by default.
|
||||
* Make +ApplicationController+ inherit from +ActionController::HTTP+ instead of +ActionController::Base+. As with middleware, this will leave out any +ActionController+ modules that provide functionality primarily used by browser applications.
|
||||
* Configure the generators to skip generating views, helpers and assets when you generate a new resource.
|
||||
|
||||
If you want to take an existing app and make it an API app, follow the following steps.
|
||||
|
||||
In +config/application.rb+ add the following lines at the top of the +Application+ class:
|
||||
|
||||
<ruby>
|
||||
config.middleware.http_only!
|
||||
config.generators.http_only!
|
||||
</ruby>
|
||||
|
||||
Change +app/controllers/application_controller.rb+:
|
||||
|
||||
<ruby>
|
||||
# instead of
|
||||
class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
# do
|
||||
class ApplicationController < ActionController::HTTP
|
||||
end
|
||||
</ruby>
|
||||
|
||||
h3. Choosing Middlewares
|
||||
|
||||
An API application comes with the following middlewares by default.
|
||||
|
||||
* +Rack::Cache+: Caches responses with public +Cache-Control+ headers using HTTP caching semantics. See below for more information.
|
||||
* +Rack::Sendfile+: Uses a front-end server's file serving support from your Rails application.
|
||||
* +Rack::Lock+: If your application is not marked as threadsafe (+config.threadsafe!+), this middleware will add a mutex around your requests.
|
||||
* +ActionDispatch::RequestId+:
|
||||
* +Rails::Rack::Logger+:
|
||||
* +Rack::Runtime+: Adds a header to the response listing the total runtime of the request.
|
||||
* +ActionDispatch::ShowExceptions+: Rescue exceptions and re-dispatch them to an exception handling application
|
||||
* +ActionDispatch::DebugExceptions+: Log exceptions
|
||||
* +ActionDispatch::RemoteIp+: Protect against IP spoofing attacks
|
||||
* +ActionDispatch::Reloader+: In development mode, support code reloading.
|
||||
* +ActionDispatch::ParamsParser+: Parse XML, YAML and JSON parameters when the request's +Content-Type+ is one of those.
|
||||
* +ActionDispatch::Head+: Dispatch +HEAD+ requests as +GET+ requests, and return only the status code and headers.
|
||||
* +Rack::ConditionalGet+: Supports the +stale?+ feature in Rails controllers.
|
||||
* +Rack::ETag+: Automatically set an +ETag+ on all string responses. This means that if the same response is returned from a controller for the same URL, the server will return a +304 Not Modified+, even if no additional caching steps are taken. This is primarily a client-side optimization; it reduces bandwidth costs but not server processing time.
|
||||
|
||||
Other plugins, including +ActiveRecord+, may add additional middlewares. In general, these middlewares are agnostic to the type of app you are building, and make sense in an API-only Rails application.
|
||||
|
||||
You can get a list of all middlewares in your application via:
|
||||
|
||||
<shell>
|
||||
$ rake middleware
|
||||
</shell>
|
||||
|
||||
h4. Using Rack::Cache
|
||||
|
||||
When used with Rails, +Rack::Cache+ uses the Rails cache store for its entity and meta stores. This means that if you use memcache, for your Rails app, for instance, the built-in HTTP cache will use memcache.
|
||||
|
||||
To make use of +Rack::Cache+, you will want to use +stale?+ in your controller. Here's an example of +stale?+ in use.
|
||||
|
||||
<ruby>
|
||||
def show
|
||||
@post = Post.find(params[:id])
|
||||
|
||||
if stale?(:last_modified => @post.updated_at)
|
||||
render json: @post
|
||||
end
|
||||
end
|
||||
</ruby>
|
||||
|
||||
The call to +stale?+ will compare the +If-Modified-Since+ header in the request with +@post.updated_at+. If the header is newer than the last modified, this action will return a +304 Not Modified+ response. Otherwise, it will render the response and include a +Last-Modified+ header with the response.
|
||||
|
||||
Normally, this mechanism is used on a per-client basis. +Rack::Cache+ allows us to share this caching mechanism across clients. We can enable cross-client caching in the call to +stale?+
|
||||
|
||||
<ruby>
|
||||
def show
|
||||
@post = Post.find(params[:id])
|
||||
|
||||
if stale?(:last_modified => @post.updated_at, :public => true)
|
||||
render json: @post
|
||||
end
|
||||
end
|
||||
</ruby>
|
||||
|
||||
This means that +Rack::Cache+ will store off +Last-Modified+ value for a URL in the Rails cache, and add an +If-Modified-Since+ header to any subsequent inbound requests for the same URL.
|
||||
|
||||
Think of it as page caching using HTTP semantics.
|
||||
|
||||
NOTE: The +Rack::Cache+ middleware is always outside of the +Rack::Lock+ mutex, even in single-threaded apps.
|
||||
|
||||
h4. Using Rack::Sendfile
|
||||
|
||||
When you use the +send_file+ method in a Rails controller, it sets the +X-Sendfile+ header. +Rack::Sendfile+ is responsible for actually sending the file.
|
||||
|
||||
If your front-end server supports accelerated file sending, +Rack::Sendfile+ will offload the actual file sending work to the front-end server.
|
||||
|
||||
You can configure the name of the header that your front-end server uses for this purposes using +config.action_dispatch.x_sendfile_header+ in the appropriate environment config file.
|
||||
|
||||
You can learn more about how to use +Rack::Sendfile+ with popular front-ends in "the Rack::Sendfile documentation":http://rubydoc.info/github/rack/rack/master/Rack/Sendfile
|
||||
|
||||
The values for popular servers once they are configured to support accelerated file sending:
|
||||
|
||||
<ruby>
|
||||
# Apache and lighttpd
|
||||
config.action_dispatch.x_sendfile_header = "X-Sendfile"
|
||||
|
||||
# nginx
|
||||
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"
|
||||
</ruby>
|
||||
|
||||
Make sure to configure your server to support these options following the instructions in the +Rack::Sendfile+ documentation.
|
||||
|
||||
NOTE: The +Rack::Sendfile+ middleware is always outside of the +Rack::Lock+ mutex, even in single-threaded apps.
|
||||
|
||||
h4. Using ActionDispatch::ParamsParser
|
||||
|
||||
+ActionDispatch::ParamsParser+ will take parameters from the client in JSON and make them available in your controller as +params+.
|
||||
|
||||
To use this, your client will need to make a request with JSON-encoded parameters and specify the +Content-Type+ as +application/json+.
|
||||
|
||||
Here's an example in jQuery:
|
||||
|
||||
<plain>
|
||||
jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/people'
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
|
||||
|
||||
success: function(json) { }
|
||||
});
|
||||
</plain>
|
||||
|
||||
+ActionDispatch::ParamsParser+ will see the +Content-Type+ and your params will be +{ :person => { :firstName => "Yehuda", :lastName => "Katz" } }+.
|
||||
|
||||
h4. Other Middlewares
|
||||
|
||||
Rails ships with a number of other middlewares that you might want to use in an API app, especially if one of your API clients is the browser:
|
||||
|
||||
* +Rack::MethodOverride+: Allows the use of the +_method+ hack to route POST requests to other verbs.
|
||||
* +ActionDispatch::Cookies+: Supports the +cookie+ method in +ActionController+, including support for signed and encrypted cookies.
|
||||
* +ActionDispatch::Flash+: Supports the +flash+ mechanism in +ActionController+.
|
||||
* +ActionDispatch::BestStandards+: Tells Internet Explorer to use the most standards-compliant available renderer. In production mode, if ChromeFrame is available, use ChromeFrame.
|
||||
* Session Management: If a +config.session_store+ is supplied, this middleware makes the session available as the +session+ method in +ActionController+.
|
||||
|
||||
Any of these middlewares can be adding via:
|
||||
|
||||
<ruby>
|
||||
config.middleware.use Rack::MethodOverride
|
||||
</ruby>
|
||||
|
||||
h4. Removing Middlewares
|
||||
|
||||
If you don't want to use a middleware that is included by default in the API-only middleware set, you can remove it using +config.middleware.delete+:
|
||||
|
||||
<ruby>
|
||||
config.middleware.delete ::Rack::Sendfile
|
||||
</ruby>
|
||||
|
||||
Keep in mind that removing these features may remove support for certain features in +ActionController+.
|
||||
|
||||
h3. Choosing Controller Modules
|
||||
|
||||
An API application (using +ActionController::HTTP+) comes with the following controller modules by default:
|
||||
|
||||
* +ActionController::UrlFor+: Makes +url_for+ and friends available
|
||||
* +ActionController::Redirecting+: Support for +redirect_to+
|
||||
* +ActionController::Rendering+: Basic support for rendering
|
||||
* +ActionController::Renderers::All+: Support for +render :json+ and friends
|
||||
* +ActionController::ConditionalGet+: Support for +stale?+
|
||||
* +ActionController::ForceSSL+: Support for +force_ssl+
|
||||
* +ActionController::RackDelegation+: Support for the +request+ and +response+ methods returning +ActionDispatch::Request+ and +ActionDispatch::Response+ objects.
|
||||
* +ActionController::DataStreaming+: Support for +send_file+ and +send_data+
|
||||
* +AbstractController::Callbacks+: Support for +before_filter+ and friends
|
||||
* +ActionController::Instrumentation+: Support for the instrumentation hooks defined by +ActionController+ (see "the source":https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/instrumentation.rb for more).
|
||||
* +ActionController::Rescue+: Support for +rescue_from+.
|
||||
|
||||
Other plugins may add additional modules. You can get a list of all modules included into +ActionController::HTTP+ in the rails console:
|
||||
|
||||
<shell>
|
||||
$ irb
|
||||
>> ActionController::HTTP.ancestors - ActionController::Metal.ancestors
|
||||
</shell>
|
||||
|
||||
h4. Adding Other Modules
|
||||
|
||||
All ActionController modules know about their dependent modules, so you can feel free to include any modules into your controllers, and all dependencies will be included and set up as well.
|
||||
|
||||
Some common modules you might want to add:
|
||||
|
||||
* +AbstractController::Translation+: Support for the +l+ and +t+ localization and translation methods. These delegate to +I18n.translate+ and +I18n.localize+.
|
||||
* +ActionController::HTTPAuthentication::Basic+ (or +Digest+ or +Token): Support for basic, digest or token HTTP authentication.
|
||||
* +AbstractController::Layouts+: Support for layouts when rendering.
|
||||
* +ActionController::MimeResponds+: Support for content negotiation (+respond_to+, +respond_with+).
|
||||
* +ActionController::Cookies+: Support for +cookies+, which includes support for signed and encrypted cookies. This requires the cookie middleware.
|
||||
|
||||
The best place to add a module is in your +ApplicationController+. You can also add modules to individual controllers.
|
|
@ -6,20 +6,6 @@ require 'rails/rack'
|
|||
|
||||
module Rails
|
||||
module Configuration
|
||||
module HttpOnly #:nodoc:
|
||||
def initialize
|
||||
@http_only = false
|
||||
end
|
||||
|
||||
def http_only!
|
||||
@http_only = true
|
||||
end
|
||||
|
||||
def http_only?
|
||||
@http_only
|
||||
end
|
||||
end
|
||||
|
||||
# MiddlewareStackProxy is a proxy for the Rails middleware stack that allows
|
||||
# you to configure middlewares in your application. It works basically as a
|
||||
# command recorder, saving each command to be applied after initialization
|
||||
|
@ -58,10 +44,7 @@ module Rails
|
|||
# Cookies, Session and Flash, BestStandardsSupport, and MethodOverride. You
|
||||
# can always add any of them later manually if you want.
|
||||
class MiddlewareStackProxy
|
||||
include HttpOnly
|
||||
|
||||
def initialize
|
||||
super
|
||||
@operations = []
|
||||
end
|
||||
|
||||
|
@ -99,10 +82,7 @@ module Rails
|
|||
attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging
|
||||
attr_reader :hidden_namespaces
|
||||
|
||||
include HttpOnly
|
||||
|
||||
def initialize
|
||||
super
|
||||
@aliases = Hash.new { |h,k| h[k] = {} }
|
||||
@options = Hash.new { |h,k| h[k] = {} }
|
||||
@fallbacks = {}
|
||||
|
|
|
@ -46,7 +46,6 @@ module Rails
|
|||
:assets => true,
|
||||
:force_plural => false,
|
||||
:helper => true,
|
||||
:http => false,
|
||||
:integration_tool => nil,
|
||||
:javascripts => true,
|
||||
:javascript_engine => :js,
|
||||
|
@ -62,7 +61,6 @@ module Rails
|
|||
}
|
||||
|
||||
def self.configure!(config) #:nodoc:
|
||||
http_only! if config.http_only?
|
||||
no_color! unless config.colorize_logging
|
||||
aliases.deep_merge! config.aliases
|
||||
options.deep_merge! config.options
|
||||
|
@ -72,7 +70,7 @@ module Rails
|
|||
hide_namespaces(*config.hidden_namespaces)
|
||||
end
|
||||
|
||||
def self.templates_path
|
||||
def self.templates_path #:nodoc:
|
||||
@templates_path ||= []
|
||||
end
|
||||
|
||||
|
@ -106,25 +104,6 @@ module Rails
|
|||
Thor::Base.shell = Thor::Shell::Basic
|
||||
end
|
||||
|
||||
# Configure generators for http only applications. It basically hides
|
||||
# everything that is usually browser related, such as assets and session
|
||||
# migration generators, and completely disable views, helpers and assets
|
||||
# so generators such as scaffold won't create them.
|
||||
def self.http_only!
|
||||
hide_namespaces "assets", "css", "js", "session_migration"
|
||||
|
||||
options[:rails].merge!(
|
||||
:assets => false,
|
||||
:helper => false,
|
||||
:http => true,
|
||||
:javascripts => false,
|
||||
:javascript_engine => nil,
|
||||
:stylesheets => false,
|
||||
:stylesheet_engine => nil,
|
||||
:template_engine => nil
|
||||
)
|
||||
end
|
||||
|
||||
# Track all generators subclasses.
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
|
|
|
@ -144,9 +144,6 @@ module Rails
|
|||
class AppGenerator < AppBase
|
||||
add_shared_options_for "application"
|
||||
|
||||
class_option :http, :type => :boolean, :default => false,
|
||||
:desc => "Preconfigure smaller stack for HTTP only apps"
|
||||
|
||||
# Add bin/rails options
|
||||
class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
|
||||
:desc => "Show Rails version number and quit"
|
||||
|
@ -159,10 +156,6 @@ module Rails
|
|||
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
|
||||
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
|
||||
end
|
||||
|
||||
# Force sprockets to be skipped when generating http only app.
|
||||
# Can't modify options hash as it's frozen by default.
|
||||
self.options = options.merge(:skip_sprockets => true).freeze if options.http?
|
||||
end
|
||||
|
||||
public_task :create_root
|
||||
|
@ -177,7 +170,6 @@ module Rails
|
|||
|
||||
def create_app_files
|
||||
build(:app)
|
||||
remove_file("app/views") if options.http?
|
||||
end
|
||||
|
||||
def create_config_files
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class ApplicationController < ActionController::<%= options.http? ? "HTTP" : "Base" %>
|
||||
class ApplicationController < ActionController::Base
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :reset_session instead.
|
||||
<%= comment_if :http %>protect_from_forgery :with => :exception
|
||||
protect_from_forgery :with => :exception
|
||||
end
|
|
@ -66,15 +66,6 @@ module <%= app_const_base %>
|
|||
|
||||
# Version of your assets, change this if you want to expire all your assets.
|
||||
config.assets.version = '1.0'
|
||||
<% end -%>
|
||||
<% if options.http? -%>
|
||||
|
||||
# Only loads a smaller set of middleware suitable for HTTP only apps.
|
||||
# Middleware like session, flash, cookies can be added back manually.
|
||||
config.middleware.http_only!
|
||||
|
||||
# Skip views, helpers and assets when generating a new resource.
|
||||
config.generators.http_only!
|
||||
<% end -%>
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,6 @@ module Rails
|
|||
return if options[:actions].present?
|
||||
route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ")
|
||||
route_config << "resources :#{file_name.pluralize}"
|
||||
route_config << ", except: :edit" if options.http?
|
||||
route_config << " end" * regular_class_path.size
|
||||
route route_config
|
||||
end
|
||||
|
|
|
@ -14,8 +14,7 @@ module Rails
|
|||
:desc => "Generate controller with HTTP actions only"
|
||||
|
||||
def create_controller_files
|
||||
template_file = options.http? ? "http_controller.rb" : "controller.rb"
|
||||
template template_file, File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
|
||||
template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
|
||||
end
|
||||
|
||||
hook_for :template_engine, :test_framework, :as => :scaffold
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<% module_namespacing do -%>
|
||||
class <%= controller_class_name %>Controller < ApplicationController
|
||||
# GET <%= route_url %>
|
||||
# GET <%= route_url %>.json
|
||||
def index
|
||||
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
|
||||
|
||||
render json: @<%= plural_table_name %>
|
||||
end
|
||||
|
||||
# GET <%= route_url %>/1
|
||||
# GET <%= route_url %>/1.json
|
||||
def show
|
||||
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
||||
|
||||
render json: @<%= singular_table_name %>
|
||||
end
|
||||
|
||||
# GET <%= route_url %>/new
|
||||
# GET <%= route_url %>/new.json
|
||||
def new
|
||||
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
|
||||
|
||||
render json: @<%= singular_table_name %>
|
||||
end
|
||||
|
||||
# POST <%= route_url %>
|
||||
# POST <%= route_url %>.json
|
||||
def create
|
||||
@<%= singular_table_name %> = <%= orm_class.build(class_name, "params[:#{singular_table_name}]") %>
|
||||
|
||||
if @<%= orm_instance.save %>
|
||||
render json: @<%= singular_table_name %>, status: :created, location: @<%= singular_table_name %>
|
||||
else
|
||||
render json: @<%= orm_instance.errors %>, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT <%= route_url %>/1
|
||||
# PATCH/PUT <%= route_url %>/1.json
|
||||
def update
|
||||
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
||||
|
||||
if @<%= orm_instance.update_attributes("params[:#{singular_table_name}]") %>
|
||||
head :no_content
|
||||
else
|
||||
render json: @<%= orm_instance.errors %>, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE <%= route_url %>/1
|
||||
# DELETE <%= route_url %>/1.json
|
||||
def destroy
|
||||
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
||||
@<%= orm_instance.destroy %>
|
||||
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
<% end -%>
|
|
@ -14,10 +14,8 @@ module TestUnit
|
|||
:desc => "Generate functional test with HTTP actions only"
|
||||
|
||||
def create_test_files
|
||||
template_file = options.http? ? "http_functional_test.rb" : "functional_test.rb"
|
||||
|
||||
template template_file,
|
||||
File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb")
|
||||
template "functional_test.rb",
|
||||
File.join("test/functional", controller_class_path, "#{controller_file_name}_controller_test.rb")
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
require 'test_helper'
|
||||
|
||||
<% module_namespacing do -%>
|
||||
class <%= controller_class_name %>ControllerTest < ActionController::TestCase
|
||||
setup do
|
||||
@<%= singular_table_name %> = <%= table_name %>(:one)
|
||||
@request.accept = "application/json"
|
||||
end
|
||||
|
||||
test "should get index" do
|
||||
get :index
|
||||
assert_response :success
|
||||
assert_not_nil assigns(:<%= table_name %>)
|
||||
end
|
||||
|
||||
test "should get new" do
|
||||
get :new
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should create <%= singular_table_name %>" do
|
||||
assert_difference('<%= class_name %>.count') do
|
||||
post :create, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
|
||||
end
|
||||
|
||||
assert_response 201
|
||||
assert_not_nil assigns(:<%= singular_table_name %>)
|
||||
end
|
||||
|
||||
test "should show <%= singular_table_name %>" do
|
||||
get :show, id: @<%= singular_table_name %>
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should update <%= singular_table_name %>" do
|
||||
put :update, id: @<%= singular_table_name %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
|
||||
assert_response 204
|
||||
assert_not_nil assigns(:<%= singular_table_name %>)
|
||||
end
|
||||
|
||||
test "should destroy <%= singular_table_name %>" do
|
||||
assert_difference('<%= class_name %>.count', -1) do
|
||||
delete :destroy, id: @<%= singular_table_name %>
|
||||
end
|
||||
|
||||
assert_response 204
|
||||
assert_not_nil assigns(:<%= singular_table_name %>)
|
||||
end
|
||||
end
|
||||
<% end -%>
|
|
@ -125,33 +125,5 @@ module ApplicationTests
|
|||
assert_equal expected, c.generators.options
|
||||
end
|
||||
end
|
||||
|
||||
test "http only disables options from generators" do
|
||||
add_to_config <<-RUBY
|
||||
config.generators.http_only!
|
||||
RUBY
|
||||
|
||||
# Initialize the application
|
||||
require "#{app_path}/config/environment"
|
||||
Rails.application.load_generators
|
||||
|
||||
assert !Rails::Generators.options[:rails][:template_engine],
|
||||
"http only should have disabled generator options"
|
||||
end
|
||||
|
||||
test "http only allow overriding generators options" do
|
||||
add_to_config <<-RUBY
|
||||
config.generators.helper = true
|
||||
config.generators.http_only!
|
||||
config.generators.template_engine = :my_template
|
||||
RUBY
|
||||
|
||||
# Initialize the application
|
||||
require "#{app_path}/config/environment"
|
||||
Rails.application.load_generators
|
||||
|
||||
assert_equal :my_template, Rails::Generators.options[:rails][:template_engine]
|
||||
assert_equal true, Rails::Generators.options[:rails][:helper]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -130,33 +130,6 @@ module ApplicationTests
|
|||
assert_equal "false", last_response.body
|
||||
end
|
||||
|
||||
test "action_controller http initializes successfully" do
|
||||
app_file "app/controllers/application_controller.rb", <<-RUBY
|
||||
class ApplicationController < ActionController::HTTP
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/controllers/omg_controller.rb", <<-RUBY
|
||||
class OmgController < ApplicationController
|
||||
def show
|
||||
render :json => { :omg => 'omg' }
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
AppTemplate::Application.routes.draw do
|
||||
match "/:controller(/:action)"
|
||||
end
|
||||
RUBY
|
||||
|
||||
require 'rack/test'
|
||||
extend Rack::Test::Methods
|
||||
|
||||
get '/omg/show'
|
||||
assert_equal '{"omg":"omg"}', last_response.body
|
||||
end
|
||||
|
||||
# AD
|
||||
test "action_dispatch extensions are applied to ActionDispatch" do
|
||||
add_to_config "config.action_dispatch.tld_length = 2"
|
||||
|
|
|
@ -52,36 +52,6 @@ module ApplicationTests
|
|||
], middleware
|
||||
end
|
||||
|
||||
test "http only middleware stack" do
|
||||
add_to_config "config.middleware.http_only!"
|
||||
add_to_config "config.force_ssl = true"
|
||||
add_to_config "config.action_dispatch.x_sendfile_header = 'X-Sendfile'"
|
||||
|
||||
boot!
|
||||
|
||||
assert_equal [
|
||||
"Rack::SSL",
|
||||
"Rack::Sendfile",
|
||||
"ActionDispatch::Static",
|
||||
"Rack::Lock",
|
||||
"ActiveSupport::Cache::Strategy::LocalCache",
|
||||
"Rack::Runtime",
|
||||
"ActionDispatch::RequestId",
|
||||
"Rails::Rack::Logger",
|
||||
"ActionDispatch::ShowExceptions",
|
||||
"ActionDispatch::DebugExceptions",
|
||||
"ActionDispatch::RemoteIp",
|
||||
"ActionDispatch::Reloader",
|
||||
"ActionDispatch::Callbacks",
|
||||
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
|
||||
"ActiveRecord::QueryCache",
|
||||
"ActionDispatch::ParamsParser",
|
||||
"ActionDispatch::Head",
|
||||
"Rack::ConditionalGet",
|
||||
"Rack::ETag"
|
||||
], middleware
|
||||
end
|
||||
|
||||
test "Rack::Sendfile is not included by default" do
|
||||
boot!
|
||||
|
||||
|
|
|
@ -132,26 +132,6 @@ module ApplicationTests
|
|||
assert_no_match(/Errors running/, output)
|
||||
end
|
||||
|
||||
def test_http_scaffold_tests_pass_by_default
|
||||
add_to_config <<-RUBY
|
||||
config.middleware.http_only!
|
||||
config.generators.http_only!
|
||||
RUBY
|
||||
|
||||
app_file "app/controllers/application_controller.rb", <<-RUBY
|
||||
class ApplicationController < ActionController::HTTP
|
||||
end
|
||||
RUBY
|
||||
|
||||
output = Dir.chdir(app_path) do
|
||||
`rails generate scaffold user username:string password:string;
|
||||
bundle exec rake db:migrate db:test:clone test`
|
||||
end
|
||||
|
||||
assert_match(/6 tests, 12 assertions, 0 failures, 0 errors/, output)
|
||||
assert_no_match(/Errors running/, output)
|
||||
end
|
||||
|
||||
def test_rake_dump_structure_should_respect_db_structure_env_variable
|
||||
Dir.chdir(app_path) do
|
||||
# ensure we have a schema_migrations table to dump
|
||||
|
|
|
@ -361,39 +361,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
|
|||
assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/
|
||||
end
|
||||
|
||||
def test_http_generates_config_middleware_and_generator_http_setup
|
||||
run_generator [destination_root, "--http"]
|
||||
assert_file "config/application.rb", /config\.middleware\.http_only!/,
|
||||
/config\.generators\.http_only!/
|
||||
end
|
||||
|
||||
def test_http_generates_application_controller_with_action_controller_http
|
||||
run_generator [destination_root, "--http"]
|
||||
assert_file "app/controllers/application_controller.rb",
|
||||
/class ApplicationController < ActionController::HTTP/
|
||||
end
|
||||
|
||||
def test_http_generates_application_controller_with_protect_from_forgery_commented_out_setup
|
||||
run_generator [destination_root, "--http"]
|
||||
assert_file "app/controllers/application_controller.rb", /^ # protect_from_forgery/
|
||||
end
|
||||
|
||||
def test_http_does_not_generate_app_views_dir
|
||||
run_generator [destination_root, "--http"]
|
||||
assert_no_directory "app/views"
|
||||
end
|
||||
|
||||
def test_http_skip_sprockets_entries_in_gemfile_and_application
|
||||
run_generator [destination_root, "--http"]
|
||||
assert_file "Gemfile" do |content|
|
||||
assert_no_match(/group :assets/, content)
|
||||
end
|
||||
assert_file "config/application.rb" do |content|
|
||||
assert_match(/^# require "sprockets/, content)
|
||||
assert_no_match(/config\.assets/, content)
|
||||
end
|
||||
end
|
||||
|
||||
def test_pretend_option
|
||||
output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
|
||||
assert_no_match(/run bundle install/, output)
|
||||
|
|
|
@ -86,10 +86,4 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
|
|||
assert_no_match(/resources :accounts$/, route)
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_does_not_generate_edit_route
|
||||
run_generator ["Account", "--http"]
|
||||
|
||||
assert_file "config/routes.rb", /resources :accounts, except: :edit$/
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,41 +142,4 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
|
|||
assert_match(/\{ render action: "new" \}/, content)
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_generates_controller_without_respond_to_block_and_html_format
|
||||
run_generator ["User", "--http"]
|
||||
|
||||
assert_file "app/controllers/users_controller.rb" do |content|
|
||||
assert_match(/render json: @user/, content)
|
||||
assert_no_match(/respond_to/, content)
|
||||
assert_no_match(/format\.html/, content)
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_generates_functional_tests_with_json_format_and_http_status_assertions
|
||||
run_generator ["User", "--http"]
|
||||
|
||||
assert_file "test/functional/users_controller_test.rb" do |content|
|
||||
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
|
||||
assert_match(/@request\.accept = "application\/json"/, content)
|
||||
assert_match(/test "should get index"/, content)
|
||||
|
||||
assert_match(/assert_response 201/, content)
|
||||
assert_no_match(/assert_redirected_to/, content)
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_does_not_generate_edit_action
|
||||
run_generator ["User", "--http"]
|
||||
|
||||
assert_file "app/controllers/users_controller.rb" do |content|
|
||||
assert_match(/def index/, content)
|
||||
assert_no_match(/def edit/, content)
|
||||
end
|
||||
|
||||
assert_file "test/functional/users_controller_test.rb" do |content|
|
||||
assert_match(/test "should get index"/, content)
|
||||
assert_no_match(/test "should get edit"/, content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -213,55 +213,4 @@ class GeneratorsTest < Rails::Generators::TestCase
|
|||
Rails::Generators.hide_namespace("special:namespace")
|
||||
assert Rails::Generators.hidden_namespaces.include?("special:namespace")
|
||||
end
|
||||
|
||||
def test_http_only_hides_generators
|
||||
generators = %w(assets js css session_migration)
|
||||
|
||||
generators.each do |generator|
|
||||
assert !Rails::Generators.hidden_namespaces.include?(generator)
|
||||
end
|
||||
|
||||
with_http_only! do
|
||||
generators.each do |generator|
|
||||
assert Rails::Generators.hidden_namespaces.include?(generator),
|
||||
"http only should hide #{generator} generator"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_enables_http_option
|
||||
options = Rails::Generators.options[:rails]
|
||||
|
||||
assert !options[:http], "http option should be disabled by default"
|
||||
|
||||
with_http_only! do
|
||||
assert options[:http], "http only should enable generator http option"
|
||||
end
|
||||
end
|
||||
|
||||
def test_http_only_disables_template_and_helper_and_assets_options
|
||||
options = Rails::Generators.options[:rails]
|
||||
disable_options = [:assets, :helper, :javascripts, :javascript_engine,
|
||||
:stylesheets, :stylesheet_engine, :template_engine]
|
||||
|
||||
disable_options.each do |disable_option|
|
||||
assert options[disable_option], "without http only should have generator option #{disable_option} enabled"
|
||||
end
|
||||
|
||||
with_http_only! do
|
||||
disable_options.each do |disable_option|
|
||||
assert !options[disable_option], "http only should have generator option #{disable_option} disabled"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_http_only!
|
||||
Rails::Generators.http_only!
|
||||
yield
|
||||
ensure
|
||||
Rails::Generators.instance_variable_set(:@hidden_namespaces, nil)
|
||||
Rails::Generators.instance_variable_set(:@options, nil)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue