From 396d599e24cbfa15c62b20fab8cc02cdba046ce3 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 6 Nov 2008 01:10:30 +0530 Subject: Update guides from docrails --- railties/Rakefile | 21 +- railties/doc/guides/html/2_2_release_notes.html | 2 +- .../doc/guides/html/actioncontroller_basics.html | 299 +++++++++++++++------ .../html/activerecord_validations_callbacks.html | 267 ++++++++++++++++++ railties/doc/guides/html/authors.html | 13 + .../guides/html/debugging_rails_applications.html | 192 ++++++++++--- railties/doc/guides/html/finders.html | 45 ++-- railties/doc/guides/html/form_helpers.html | 76 +++++- .../guides/html/getting_started_with_rails.html | 114 ++++---- railties/doc/guides/html/index.html | 20 +- railties/doc/guides/html/migrations.html | 2 +- .../guides/html/testing_rails_applications.html | 67 ++--- railties/doc/guides/source/2_2_release_notes.txt | 2 +- .../source/actioncontroller_basics/changelog.txt | 5 + .../source/actioncontroller_basics/cookies.txt | 2 +- .../guides/source/actioncontroller_basics/csrf.txt | 32 +++ .../source/actioncontroller_basics/filters.txt | 18 +- .../source/actioncontroller_basics/http_auth.txt | 2 +- .../source/actioncontroller_basics/index.txt | 14 +- .../actioncontroller_basics/introduction.txt | 6 +- .../source/actioncontroller_basics/methods.txt | 10 +- .../parameter_filtering.txt | 4 +- .../source/actioncontroller_basics/params.txt | 43 ++- .../request_response_objects.txt | 30 ++- .../source/actioncontroller_basics/rescue.txt | 8 +- .../source/actioncontroller_basics/session.txt | 22 +- .../source/actioncontroller_basics/streaming.txt | 14 +- .../actioncontroller_basics/verification.txt | 6 +- .../doc/guides/source/active_record_basics.txt | 2 +- .../source/activerecord_validations_callbacks.txt | 25 ++ railties/doc/guides/source/authors.txt | 14 + railties/doc/guides/source/configuring.txt | 225 ++++++++++++++++ .../guides/source/debugging_rails_applications.txt | 110 +++++++- railties/doc/guides/source/finders.txt | 5 - railties/doc/guides/source/form_helpers.txt | 85 +++++- .../guides/source/getting_started_with_rails.txt | 23 +- railties/doc/guides/source/index.txt | 8 +- .../guides/source/migrations/rakeing_around.txt | 2 +- .../guides/source/testing_rails_applications.txt | 9 +- 39 files changed, 1476 insertions(+), 368 deletions(-) create mode 100644 railties/doc/guides/html/activerecord_validations_callbacks.html create mode 100644 railties/doc/guides/source/actioncontroller_basics/changelog.txt create mode 100644 railties/doc/guides/source/actioncontroller_basics/csrf.txt create mode 100644 railties/doc/guides/source/activerecord_validations_callbacks.txt create mode 100644 railties/doc/guides/source/configuring.txt diff --git a/railties/Rakefile b/railties/Rakefile index 872ea83ec2..52357a09c5 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -283,26 +283,35 @@ task :guides do template = File.expand_path("doc/guides/source/templates/guides.html.erb") - ignore = ['icons', 'images', 'templates', 'stylesheets'] + ignore = ['..', 'icons', 'images', 'templates', 'stylesheets'] ignore << 'active_record_basics.txt' indexless = ['index.txt', 'authors.txt'] - Dir.entries(source)[2..-1].each do |entry| + # Traverse all entries in doc/guides/source/ + Dir.entries(source).each do |entry| next if ignore.include?(entry) if File.directory?(File.join(source, entry)) - input = File.join(source, entry, 'index.txt') - output = File.join(html, entry) + # If the current entry is a directory, then we will want to compile + # the 'index.txt' file inside this directory. + if entry == '.' + input = File.join(source, 'index.txt') + output = File.join(html, "index.html") + else + input = File.join(source, entry, 'index.txt') + output = File.join(html, "#{entry}.html") + end else + # If the current entry is a file, then we will want to compile this file. input = File.join(source, entry) - output = File.join(html, entry).sub(/\.txt$/, '') + output = File.join(html, entry).sub(/\.txt$/, '.html') end begin puts "GENERATING => #{output}" ENV['MANUALSONRAILS_TOC'] = 'no' if indexless.include?(entry) - Mizuho::Generator.new(input, output, template).start + Mizuho::Generator.new(input, :output => output, :template => template).start rescue Mizuho::GenerationError STDERR.puts "*** ERROR" exit 2 diff --git a/railties/doc/guides/html/2_2_release_notes.html b/railties/doc/guides/html/2_2_release_notes.html index c68f10ad5a..931786ef6c 100644 --- a/railties/doc/guides/html/2_2_release_notes.html +++ b/railties/doc/guides/html/2_2_release_notes.html @@ -407,7 +407,7 @@ More information :

All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers.

-

If you want to these generate guides locally, inside your application:

+

If you want to generate these guides locally, inside your application:

-
class ClientsController < ActionController::Base
+
class ClientsController < ApplicationController
 
   # Actions are public methods
   def new
@@ -321,7 +374,7 @@ private
 end
 

Private methods in a controller are also used as filters, which will be covered later in this guide.

-

As an example, if the user goes to /clients/new in your application to add a new client, a ClientsController instance will be created and the new method will be run. Note that the empty method from the example above could work just fine because Rails will by default render the new.html.erb view unless the action says otherwise. The new method could make available to the view a @client instance variable by creating a new Client:

+

As an example, if the user goes to /clients/new in your application to add a new client, Rails will create a ClientsController instance will be created and run the new method. Note that the empty method from the example above could work just fine because Rails will by default render the new.html.erb view unless the action says otherwise. The new method could make available to the view a @client instance variable by creating a new Client:

end

The Layouts & rendering guide explains this in more detail.

+

ApplicationController inherits from ActionController::Base, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the API documentation or in the source itself.

3. Parameters

-

You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from a HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the params hash in your controller:

+

You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from a HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the params hash in your controller:

class ClientsController < ActionController::Base
 
-  # This action uses query string parameters because it gets run by a HTTP GET request,
-  # but this does not make any difference to the way in which the parameters are accessed.
-  # The URL for this action would look like this in order to list activated clients: /clients?status=activated
+  # This action uses query string parameters because it gets run by a HTTP
+  # GET request, but this does not make any difference to the way in which
+  # the parameters are accessed. The URL for this action would look like this
+  # in order to list activated clients: /clients?status=activated
   def index
     if params[:status] = "activated"
       @clients = Client.activated
@@ -370,11 +425,11 @@ http://www.gnu.org/software/src-highlite -->
 
 end
 
-

3.1. Hash and array parameters

+

3.1. Hash and Array Parameters

The params hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append "[]" to the key name:

-
GET /clients?ids[]=1&ids[2]&ids[]=3
+
GET /clients?ids[]=1&ids[]=2&ids[]=3

The value of params[:ids] will now be ["1", "2", "3"]. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.

To send a hash you include the key name inside the brackets:

@@ -388,8 +443,32 @@ http://www.gnu.org/software/src-highlite --> </form>

The value of params[:client] when this form is submitted will be {:name ⇒ "Acme", :phone ⇒ "12345", :address ⇒ {:postcode ⇒ "12345", :city ⇒ "Carrot City"}}. Note the nested hash in params[:client][:address].

-

3.2. Routing parameters

-

The params hash will always contain the :controller and :action keys, but you should use the methods controller_name and action_name instead to access these values. Any other parameters defined by the routing, such as :id will also be available.

+

3.2. Routing Parameters

+

The params hash will always contain the :controller and :action keys, but you should use the methods controller_name and action_name instead to access these values. Any other parameters defined by the routing, such as :id will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the :status parameter in a "pretty" URL:

+
+
+
# ...
+map.connect "/clients/:status", :controller => "clients", :action => "index", :foo => "bar"
+# ...
+
+

In this case, when a user opens the URL /clients/active, params[:status] will be set to "active". When this route is used, params[:foo] will also be set to "bar" just like it was passed in the query string in the same way params[:action] will contain "index".

+

3.3. default_url_options

+

You can set global default parameters that will be used when generating URLs with default_url_options. To do this, define a method with that name in your controller:

+
+
+
class ApplicationController < ActionController::Base
+
+  #The options parameter is the hash passed in to url_for
+  def default_url_options(options)
+    {:locale => I18n.locale}
+  end
+
+end
+
+

These options will be used as a starting-point when generating, so it's possible they'll be overridden by url_for. Because this method is defined in the controller, you can define it on ApplicationController so it would be used for all URL generation, or you could define it on only one controller for all URLs generated there.

4. Session

@@ -416,8 +495,9 @@ ActiveRecordStore - Stores the data in a database using Active Record.

-

All session stores store the session id in a cookie - there is no other way of passing it to the server. Most stores also use this key to locate the session data on the server.

-

The default and recommended store, the Cookie Store, does not store session data on the server, but in the cookie itself. The data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents. It can only store about 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data is discouraged no matter which session store your application uses. Expecially discouraged is storing complex objects (anything other than basic Ruby objects, the primary example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. The Cookie Store has the added advantage that it does not require any setting up beforehand - Rails will generate a "secret key" which will be used to sign the cookie when you create the application.

+

All session stores store either the session ID or the entire session in a cookie - Rails does not allow the session ID to be passed in any other way. Most stores also use this key to locate the session data on the server.

+

The default and recommended store, the Cookie Store, does not store session data on the server, but in the cookie itself. The data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it. It can only store about 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the primary example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. The Cookie Store has the added advantage that it does not require any setting up beforehand - Rails will generate a "secret key" which will be used to sign the cookie when you create the application.

+

Read more about session storage in the Security Guide.

If you need a different session storage mechanism, you can change it in the config/environment.rb file:

# Set to one of [:active_record_store, :drb_store, :mem_cache_store, :cookie_store]
 config.action_controller.session_store = :active_record_store
 
-

4.1. Disabling the session

-

Sometimes you don't need a session, and you can turn it off to avoid the unnecessary overhead. To do this, use the session class method in your controller:

+

4.1. Disabling the Session

+

Sometimes you don't need a session. In this case, you can turn it off to avoid the unnecessary overhead. To do this, use the session class method in your controller:

session :on end
-

Or even a single action:

+

Or even for specified actions:

session :on, :only => [:create, :update] end
-

4.2. Accessing the session

+

4.2. Accessing the Session

In your controller you can access the session through the session instance method.

@@ -481,7 +561,7 @@ http://www.gnu.org/software/src-highlite --> private # Finds the User with the ID stored in the session with the key :current_user_id - # This is a common way to do user login in a Rails application; logging in sets the + # This is a common way to handle user login in a Rails application; logging in sets the# session value and logging out removes it.def current_user @_current_user||= session[:current_user_id]&& User.find(session[:current_user_id]) @@ -525,9 +605,9 @@ http://www.gnu.org/software/src-highlite --> end -

To reset the entire session, use reset_session.

+

To reset the entire session, use reset_session.

4.3. The flash

-

The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can set a message which will be displayed to the user on the next request:

+

The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request:

end
-

4.3.1. flash.now

+

4.3.1. flash.now

By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the create action fails to save a resource and you render the new template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use flash.now in the same way you use the normal flash:

end
-

Note that while for session values, you set the key to nil, to delete a cookie value, you use cookies.delete(:key).

+

Note that while for session values, you set the key to nil, to delete a cookie value, you should use cookies.delete(:key).

6. Filters

-

Filters are methods that are run before, after or "around" a controller action. For example, one filter might check to see if the logged in user has the right credentials to access that particular controller or action. Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application. A common, simple filter is one which requires that a user is logged in for an action to be run. Let's define the filter method first:

+

Filters are methods that are run before, after or "around" a controller action. For example, one filter might check to see if the logged in user has the right credentials to access that particular controller or action. Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application. A common, simple filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way:

end
-

In this example, the filter is added to ApplicationController and thus all controllers in the application. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this, so to prevent this filter from running you can use skip_before_filter :

+

In this example, the filter is added to ApplicationController and thus all controllers in the application. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with skip_before_filter :

end
-

Now, the LoginsController's "new" and "create" actions will work as before without requiring the user to be logged in. The :only option is used to only skip this filter for these actions, and there is also an :except option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.

-

6.1. After filters and around filters

+

Now, the LoginsController's "new" and "create" actions will work as before without requiring the user to be logged in. The :only option is used to only skip this filter for these actions, and there is also an :except option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.

+

6.1. After Filters and Around Filters

In addition to the before filters, you can run filters after an action has run or both before and after. The after filter is similar to the before filter, but because the action has already been run it has access to the response data that's about to be sent to the client. Obviously, after filters can not stop the action from running. Around filters are responsible for running the action, but they can choose not to, which is the around filter's way of stopping it.

end

Note that the filter in this case uses send because the logged_in? method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.

-

The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex than can not be implemented in a readable and reusable way using the two other methods. As an example, we will rewrite the login filter again to use a class:

+

The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex than can not be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:

end
-

Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets it passed as an argument. The filter class has a class method filter which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same filter method, which will get run in the same way. The method must yield to execute the action. Alternatively, it can have both a before and an after method that are run before and after the action.

+

Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method filter which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same filter method, which will get run in the same way. The method must yield to execute the action. Alternatively, it can have both a before and an after method that are run before and after the action.

The Rails API documentation has more information on using filters.

7. Verification

-

Verifications make sure certain criterias are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the params, session or flash hashes or that a certain HTTP method was used or that the request was made using XMLHTTPRequest (Ajax). The default action taken when these criterias are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. It is described in the API codumentation as "essentially a special kind of before_filter".

-

Let's see how we can use verification to make sure the user supplies a username and a password in order to log in:

+

Verifications make sure certain criteria are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the params, session or flash hashes or that a certain HTTP method was used or that the request was made using XMLHTTPRequest (Ajax). The default action taken when these criteria are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. It is described in the API documentation as "essentially a special kind of before_filter".

+

Here's an example of using verification to make sure the user supplies a username and a password in order to log in:

verify :params => [:username, :password], :render => {:action => "new"}, :add_flash => {:error => "Username and password required to log in"}, - :only => :create #Only run this verification for the "create" action + :only => :create # Only run this verification for the "create" action end
-

8. The request and response objects

+

8. Request Forgery Protection

-

In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The request method contains an instance of AbstractRequest and the response method contains the response object representing what is going to be sent back to the client.

-

8.1. The request

-

The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the API documentation.

+

Cross-site request forgery is a type of attack in which a site tricks a user into making requests on another site, possibly adding, modifying or deleting data on that site without the user's knowledge or permission. The first step to avoid this is to make sure all "destructive" actions (create, update and destroy) can only be accessed with non-GET requests. If you're following RESTful conventions you're already doing this. However, a malicious site can still send a non-GET request to your site quite easily, and that's where the request forgery protection comes in. As the name says, it protects from forged requests. The way this is done is to add a non-guessable token which is only known to your server to each request. This way, if a request comes in without the proper token, it will be denied access.

+

If you generate a form like this:

+
+
+
<% form_for @user do |f| -%>
+  <%= f.text_field :username %>
+  <%= f.text_field :password -%>
+<% end -%>
+
+

You will see how the token gets added as a hidden field:

+
+
+
<form action="/users/1" method="post">
+<div><!-- ... --><input type="hidden" value="67250ab105eb5ad10851c00a5621854a23af5489" name="authenticity_token"/></div>
+<!-- Fields -->
+</form>
+
+

Rails adds this token to every form that's generated using the form helpers, so most of the time you don't have to worry about it. If you're writing a form manually or need to add the token for another reason, it's available through the method form_authenticity_token:

+
+
Example: Add a JavaScript variable containing the token for use with Ajax
+
+
<%= javascript_tag "MyApp.authenticity_token = '#{form_authenticity_token}'" %>
+
+

The Security Guide has more about this and a lot of other security-related issues that you should be aware of when developing a web application.

+
+

9. The request and response Objects

+
+

In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The request method contains an instance of AbstractRequest and the response method returns a response object representing what is going to be sent back to the client.

+

9.1. The request Object

+

The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the API documentation. Among the properties that you can access on this object:

  • @@ -812,7 +925,7 @@ host - The hostname used for this request.

  • -domain - The hostname without the first part (usually "www"). +domain - The hostname without the first segment (usually "www").

  • @@ -861,11 +974,10 @@ url - The entire URL used for the request.

-

8.1.1. path_parameters, query_parameters and request_parameters

-

TODO: Does this belong here?

-

Rails collects all of the parameters sent along with the request in the params hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The query_parameters hash contains parameters that were sent as part of the query string while the request_parameters hash contains parameters sent as part of the post body. The path_parameters hash contains parameters that were recognised by the routing as being part of the path leading to this particular controller and action.

-

8.2. The response

-

The response objects is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values.

+

9.1.1. path_parameters, query_parameters and request_parameters

+

Rails collects all of the parameters sent along with the request in the params hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The query_parameters hash contains parameters that were sent as part of the query string while the request_parameters hash contains parameters sent as part of the post body. The path_parameters hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action.

+

9.2. The response Object

+

The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values.

  • @@ -892,11 +1004,25 @@ content_type - The content type of the response. charset - The character set being used for the response. Default is "utf8".

  • +
  • +

    +headers - Headers used for the response. +

    +
+

9.2.1. Setting Custom Headers

+

If you want to set custom headers for a response then response.headers is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them - like "Content-Type" - automatically. If you want to add or change a header, just assign it to headers with the name and value:

+
+
+
response.headers["Content-Type"] = "application/pdf"
+
-

9. HTTP Basic Authentication

+

10. HTTP Basic Authentication

-

Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, we will create an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, authenticate_or_request_with_http_basic.

+

Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, authenticate_or_request_with_http_basic.

- +
Warning Be careful when using (or just don't use) "outside" data (params, cookies, etc) to locate the file on disk, as this is a security risk as someone could gain access to files they are not meant to have access to.Be careful when using (or just don't use) "outside" data (params, cookies, etc) to locate the file on disk, as this is a security risk that might allow someone to gain access to files they are not meant to see.
@@ -986,8 +1112,8 @@ http://www.gnu.org/software/src-highlite --> It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack.
-

10.2. RESTful downloads

-

While send_data works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Let's try to rewrite the example so that the PDF download is a part of the show action:

+

11.2. RESTful Downloads

+

While send_data works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Here's how you can rewrite the example so that the PDF download is a part of the show action, without any streaming:

end
-

In order for this example to work, we have to add the PDF MIME type to Rails. This can be done by adding the following line to the file config/initializers/mime_types.rb:

+

In order for this example to work, you have to add the PDF MIME type to Rails. This can be done by adding the following line to the file config/initializers/mime_types.rb:

GET /clients/1.pdf
-

11. Parameter filtering

+

12. Parameter Filtering

-

Rails keeps a log file for each environment (development, test and production) in the "log" folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. The filter_parameter_logging method can be used to filter out sensitive information from the log. It works by replacing certain keys in the params hash with "[FILTERED]" as they are written to the log. As an example, let's see how to filter all parameters with keys that include "password":

+

Rails keeps a log file for each environment (development, test and production) in the "log" folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. The filter_parameter_logging method can be used to filter out sensitive information from the log. It works by replacing certain values in the params hash with "[FILTERED]" as they are written to the log. As an example, let's see how to filter all parameters with keys that include "password":

The method works recursively through all levels of the params hash and takes an optional second parameter which is used as the replacement string if present. It can also take a block which receives each key in return and replaces those for which the block returns true.

-

12. Rescue

+

13. Rescue

Most likely your application is going to contain bugs or otherwise throw an exception that needs to be handled. For example, if the user follows a link to a resource that no longer exists in the database, Active Record will throw the ActiveRecord::RecordNotFound exception. Rails' default exception handling displays a 500 Server Error message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application:

-

12.1. The default 500 and 404 templates

+

13.1. The Default 500 and 404 Templates

By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the public folder, in 404.html and 500.html respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML.

-

12.2. rescue_from

-

If you want to do something a bit more elaborate when catching errors, you can use rescue_from, which handles exceptions of a certain type (or multiple types) in an entire controller and its subclasses. When an exception occurs which is caught by a rescue_from directive, the exception object is passed to the handler. The handler can be a method or a Proc object passed to the :with option. You can also use a block directly instead of an explicit Proc object.

-

Let's see how we can use rescue_from to intercept all ActiveRecord::RecordNotFound errors and do something with them.

+

13.2. rescue_from

+

If you want to do something a bit more elaborate when catching errors, you can use rescue_from, which handles exceptions of a certain type (or multiple types) in an entire controller and its subclasses. When an exception occurs which is caught by a rescue_from directive, the exception object is passed to the handler. The handler can be a method or a Proc object passed to the :with option. You can also use a block directly instead of an explicit Proc object.

+

Here's how you can use rescue_from to intercept all ActiveRecord::RecordNotFound errors and do something with them.

+ + + + + + + + +
+ + + +
+

Active Record Validations and Callbacks

+
+
+

This guide teaches you how to work with the lifecycle of your Active Record objects. More precisely, you will learn how to validate the state of your objects before they go into the database and also how to teach them to perform custom operations at certain points of their lifecycles.

+

After reading this guide and trying out the presented concepts, we hope that you'll be able to:

+
    +
  • +

    +Correctly use all the built-in Active Record validation helpers +

    +
  • +
  • +

    +Create your own custom validation methods +

    +
  • +
  • +

    +Work with the error messages generated by the validation proccess +

    +
  • +
  • +

    +Register callback methods that will execute custom operations during your objects lifecycle, for example before/after they are saved. +

    +
  • +
  • +

    +Create special classes that encapsulate common behaviour for your callbacks +

    +
  • +
  • +

    +Create Observers - classes with callback methods specific for each of your models, keeping the callback code outside your models' declarations. +

    +
  • +
+
+
+

1. Active Record Validations

+
+
+

2. Credits

+
+
+

3. Changelog

+ + +
+
+ + diff --git a/railties/doc/guides/html/authors.html b/railties/doc/guides/html/authors.html index 973cf7cd2e..a54135b14d 100644 --- a/railties/doc/guides/html/authors.html +++ b/railties/doc/guides/html/authors.html @@ -218,6 +218,19 @@ work with Rails. His near-daily links and other blogging can be found at Eventioz. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops.

+
+
+
+
diff --git a/railties/doc/guides/html/debugging_rails_applications.html b/railties/doc/guides/html/debugging_rails_applications.html index bf1e442d59..95f5b39e4c 100644 --- a/railties/doc/guides/html/debugging_rails_applications.html +++ b/railties/doc/guides/html/debugging_rails_applications.html @@ -208,6 +208,8 @@ ul#navMain {
  • inspect
  • +
  • Debugging Javascript
  • +
  • @@ -253,6 +255,19 @@ ul#navMain {
  • + Debugging Memory Leaks + +
  • +
  • + Plugins for Debugging +
  • +
  • References
  • @@ -325,10 +340,7 @@ http://www.gnu.org/software/src-highlite -->

    You'll see something like this:

    -
    +
    --- !ruby/object:Post
     attributes:
       updated_at: 2008-09-05 22:55:47
    @@ -340,8 +352,8 @@ attributes:
     attributes_cache: {}
     
     
    -Title: Rails debugging guide
    -
    +Title: Rails debugging guide +

    1.2. to_yaml

    Displaying an instance variable, or any other object or method, in yaml format can be achieved this way:

    @@ -358,10 +370,7 @@ http://www.gnu.org/software/src-highlite -->

    The to_yaml method converts the method to YAML format leaving it more readable, and then the simple_format helper is used to render each line as in the console. This is how debug method does its magic.

    As a result of this, you will have something like this in your view:

    -
    +
    --- !ruby/object:Post
     attributes:
     updated_at: 2008-09-05 22:55:47
    @@ -372,8 +381,8 @@ id: "1"
     created_at: 2008-09-05 22:55:47
     attributes_cache: {}
     
    -Title: Rails debugging guide
    -
    +Title: Rails debugging guide +

    1.3. inspect

    Another useful method for displaying object values is inspect, especially when working with arrays or hashes. This will print the object value as a string. For example:

    @@ -389,14 +398,37 @@ http://www.gnu.org/software/src-highlite -->

    Will be rendered as follows:

    +
    +
    [1, 2, 3, 4, 5]
    +
    +Title: Rails debugging guide
    +
    +

    1.4. Debugging Javascript

    +

    Rails has built-in support to debug RJS, to active it, set ActionView::Base.debug_rjs to true, this will specify whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it).

    +

    To enable it, add the following in the Rails::Initializer do |config| block inside environment.rb:

    +
    -
    [1, 2, 3, 4, 5]
    -
    -Title: Rails debugging guide
    +
    config.action_view[:debug_rjs] = true
    +
    +

    Or, at any time, setting ActionView::Base.debug_rjs to true:

    +
    +
    +
    ActionView::Base.debug_rjs = true
     
    +
    + + + +
    +Tip +For more information on debugging javascript refer to Firebug, the popular debugger for Firefox.
    +

    2. The Logger

    @@ -488,11 +520,8 @@ http://www.gnu.org/software/src-highlite -->

    Here's an example of the log generated by this method:

    -
    -
    Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
    +
    +
    Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
       Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
     vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
       Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails",
    @@ -506,8 +535,8 @@ Post should be valid: true
      'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54')
     The post was saved and now is the user is going to be redirected...
     Redirected to #<Post:0x20af760>
    -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
    -
    +Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts] +

    Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia.

    3. Debugging with ruby-debug

    @@ -540,12 +569,9 @@ http://www.gnu.org/software/src-highlite -->

    If you see the message in the console or logs:

    -
    -
    ***** Debugger requested, but was not available: Start server with --debugger to enable *****
    -
    +
    +
    ***** Debugger requested, but was not available: Start server with --debugger to enable *****
    +

    Make sure you have started your web server with the option —debugger:

    @posts = Post.find(:all)
     (rdb:7)
    -

    Now it's time to play and dig into your application. A good place to start is by asking the debugger for help… so type: help (You didn't see that coming, right?)

    +

    Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help… so type: help (You didn't see that coming, right?)

    (rdb:7) help
    @@ -969,16 +995,104 @@ No breakpoints.

    Here's a good start for an .rdebugrc:

    +
    +
    set autolist
    +set forcestep
    +set listsize 25
    +
    +
    +

    4. Debugging Memory Leaks

    +
    +

    A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level.

    +

    In this section, you will learn how to find and fix such leaks by using Bleak House and Valgrind debugging tools.

    +

    4.1. BleakHouse

    +

    BleakHouse is a library for finding memory leaks.

    +

    If a Ruby object does not go out of scope, the Ruby Garbage Collector won't sweep it since it is referenced somewhere. Leaks like this can grow slowly and your application will consume more and more memory, gradually affecting the overall system performance. This tool will help you find leaks on the Ruby heap.

    +

    To install it run:

    +
    +
    +
    sudo gem install bleak_house
    +
    +

    Then setup you application for profiling. Then add the following at the bottom of config/environment.rb:

    +
    -
    set autolist
    -set forcestep
    -set listsize 25
    +
    require 'bleak_house' if ENV['BLEAK_HOUSE']
     
    +

    Start a server instance with BleakHouse integration:

    +
    +
    +
    RAILS_ENV=production BLEAK_HOUSE=1 ruby-bleak-house ./script/server
    +
    +

    Make sure to run a couple hundred requests to get better data samples, then press CTRL-C. The server will stop and Bleak House will produce a dumpfile in /tmp:

    +
    +
    +
    ** BleakHouse: working...
    +** BleakHouse: complete
    +** Bleakhouse: run 'bleak /tmp/bleak.5979.0.dump' to analyze.
    +
    +

    To analyze it, just run the listed command. The top 20 leakiest lines will be listed:

    +
    +
    +
      191691 total objects
    +  Final heap size 191691 filled, 220961 free
    +  Displaying top 20 most common line/class pairs
    +  89513 __null__:__null__:__node__
    +  41438 __null__:__null__:String
    +  2348 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:Array
    +  1508 /opt/local//lib/ruby/gems/1.8/specifications/gettext-1.90.0.gemspec:14:String
    +  1021 /opt/local//lib/ruby/gems/1.8/specifications/heel-0.2.0.gemspec:14:String
    +   951 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:111:String
    +   935 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:String
    +   834 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:146:Array
    +  ...
    +
    +

    This way you can find where your application is leaking memory and fix it.

    +

    If BleakHouse doesn't report any heap growth but you still have memory growth, you might have a broken C extension, or real leak in the interpreter. In that case, try using Valgrind to investigate further.

    +

    4.2. Valgrind

    +

    Valgrind is a Linux-only application for detecting C-based memory leaks and race conditions.

    +

    There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, a C extension in the interpreter calls malloc() but is doesn't properly call free(), this memory won't be available until the app terminates.

    +

    For further information on how to install Valgrind and use with Ruby, refer to Valgrind and Ruby by Evan Weaver.

    -

    4. References

    +

    5. Plugins for Debugging

    +
    +

    There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:

    +
      +
    • +

      +Footnotes: Every Rails page has footnotes that link give request information and link back to your source via TextMate. +

      +
    • +
    • +

      +Query Trace: Adds query origin tracing to your logs. +

      +
    • +
    • +

      +Query Stats: A Rails plugin to track database queries. +

      +
    • +
    • +

      +Query Reviewer: This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. +

      +
    • +
    • +

      +Exception Notifier: Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. +

      +
    • +
    • +

      +Exception Logger: Logs your Rails exceptions in the database and provides a funky web interface to manage them. +

      +
    • +
    +
    +

    6. References

    -

    5. Changelog

    +

    7. Changelog

    • +November 3, 2008: Accepted for publication. Added RJS, memory leaks and plugins chapters by Emilio Tagua +

      +
    • +
    • +

      October 19, 2008: Copy editing pass by Mike Gunderloy

    • diff --git a/railties/doc/guides/html/finders.html b/railties/doc/guides/html/finders.html index f8396bb517..18bc32306f 100644 --- a/railties/doc/guides/html/finders.html +++ b/railties/doc/guides/html/finders.html @@ -373,27 +373,21 @@ http://www.gnu.org/software/src-highlite -->
      SELECT * FROM +clients+ WHERE (+clients+.+id+ IN (1,2))
       
    -
    +
    >> Client.find(1,2)
     => [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2,
       created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">,
       #<Client id: 2, name: => "Michael", locked: false, orders_count: 3,
    -  created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">]
    -
    + created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">]
    +

    Note that if you pass in a list of numbers that the result will be returned as an array, not as a single Client object.

    If you wanted to find the first client you would simply type Client.first and that would find the first client created in your clients table:

    -
    +
    >> Client.first
     => #<Client id: 1, name: => "Ryan", locked: false, orders_count: 2,
    -  created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">
    -
    + created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50"> +

    If you were running script/server you might see the following output:

    Indicating the query that Rails has performed on your database.

    To find the last client you would simply type Client.find(:last) and that would find the last client created in your clients table:

    -
    +
    >> Client.find(:last)
     => #<Client id: 2, name: => "Michael", locked: false, orders_count: 3,
    -  created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">
    -
    + created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40"> +

    To find all the clients you would simply type Client.all and that would find all the clients in your clients table:

    -
    +
    >> Client.all
     => [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2,
       created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">,
       #<Client id: 2, name: => "Michael", locked: false, orders_count: 3,
    -  created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">]
    -
    + created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">] +

    As alternatives to calling Client.first, Client.last, and Client.all, you can use the class methods Client.first, Client.last, and Client.all instead. Client.first, Client.last and Client.all just call their longer counterparts: Client.find(:first), Client.find(:last) and Client.find(:all) respectively.

    Be aware that Client.first/Client.find(:first) and Client.last/Client.find(:last) will both return a single object, where as Client.all/Client.find(:all) will return an array of Client objects, just as passing in an array of ids to find will do also.

    @@ -510,12 +498,9 @@ http://www.gnu.org/software/src-highlite -->

    This could possibly cause your database server to raise an unexpected error, for example MySQL will throw back this error:

    -
    -
    Got a packet bigger than 'max_allowed_packet' bytes: _query_
    -
    +
    +
    Got a packet bigger than 'max_allowed_packet' bytes: _query_
    +

    Where query is the actual query used to get that error.

    In this example it would be better to use greater-than and less-than operators in SQL, like so:

    diff --git a/railties/doc/guides/html/form_helpers.html b/railties/doc/guides/html/form_helpers.html index 28c317411b..7ff4a13a6a 100644 --- a/railties/doc/guides/html/form_helpers.html +++ b/railties/doc/guides/html/form_helpers.html @@ -220,6 +220,16 @@ ul#navMain {
  • +
  • + Making select boxes with ease + +
  • @@ -227,7 +237,7 @@ ul#navMain {

    Rails form helpers

    -

    Forms in web applications are an essential interface for user input. They are also often considered the most complex elements of HTML. Rails deals away with these complexities by providing numerous view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.

    +

    Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.

    In this guide we will:

    • @@ -392,7 +402,7 @@ a submit element. Warning -Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an ugly expecting tASSOC syntax error. +Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an expecting tASSOC syntax error.

    1.3. Checkboxes, radio buttons and other controls

    @@ -431,7 +441,7 @@ output: Important -Always use labels for each checkbox and radio button. They associate text with a specific option, while also providing a larger clickable region. +Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region.

    Other form controls we might mention are the text area, password input and hidden input:

    @@ -458,7 +468,7 @@ output:

    1.4. How do forms with PUT or DELETE methods work?

    Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). Still, most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. How does this work, then?

    -

    Rails works around this issue by emulating other methods over POST with a hidden input named "method" that is set to reflect the _real method:

    +

    Rails works around this issue by emulating other methods over POST with a hidden input named "_method" that is set to reflect the wanted method:

    form_tag(search_path, :method => "put")
    @@ -562,6 +572,64 @@ form_for(@article)
    When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, :url and :method explicitly.
    +
    +

    3. Making select boxes with ease

    +
    +

    Select boxes in HTML require a significant amount of markup (one OPTION element for each option to choose from), therefore it makes the most sense for them to be dynamically generated from data stored in arrays or hashes.

    +

    Here is what our wanted markup might look like:

    +
    +
    +
    <select name="city_id" id="city_id">
    +  <option value="1">Lisabon</option>
    +  <option value="2">Madrid</option>
    +  ...
    +  <option value="12">Berlin</option>
    +</select>
    +
    +

    Here we have a list of cities where their names are presented to the user, but internally we want to handle just their IDs so we keep them in value attributes. Let's see how Rails can help out here.

    +

    3.1. The select tag and options

    +

    The most generic helper is select_tag, which — as the name implies — simply generates the SELECT tag that encapsulates the options:

    +
    +
    +
    <%= select_tag(:city_id, '<option value="1">Lisabon</option>...') %>
    +
    +

    This is a start, but it doesn't dynamically create our option tags. We had to pass them in as a string.

    +

    We can generate option tags with the options_for_select helper:

    +
    +
    +
    <%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %>
    +
    +output:
    +
    +<option value="1">Lisabon</option>
    +<option value="2">Madrid</option>
    +...
    +
    +

    For input data we used a nested array where each element has two elements: visible value (name) and internal value (ID).

    +

    Now you can combine select_tag and options_for_select to achieve the desired, complete markup:

    +
    +
    +
    <%= select_tag(:city_id, options_for_select(...)) %>
    +
    +

    Sometimes, depending on our application's needs, we also wish a specific option to be pre-selected. The options_for_select helper supports this with an optional second argument:

    +
    +
    +
    <%= options_for_select(cities_array, 2) %>
    +
    +output:
    +
    +<option value="1">Lisabon</option>
    +<option value="2" selected="selected">Madrid</option>
    +...
    +
    +

    So whenever Rails sees that the internal value of an option being generated matches this value, it will add the selected attribute to that option.

    +

    3.2. Select boxes for dealing with models

    +

    Until now we've covered how to make generic select boxes, but in most cases our form controls will be tied to a specific database model. So, to continue from our previous examples, let's assume that we have a "Person" model with a city_id attribute.

    +
    +
    +
    ...
    +
    +

    diff --git a/railties/doc/guides/html/getting_started_with_rails.html b/railties/doc/guides/html/getting_started_with_rails.html index 797ae2fb3a..1b2eac0ce5 100644 --- a/railties/doc/guides/html/getting_started_with_rails.html +++ b/railties/doc/guides/html/getting_started_with_rails.html @@ -1457,51 +1457,57 @@ http://www.gnu.org/software/src-highlite -->

    At this point, it’s worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use partials to clean up duplication in views and filters to help with duplication in controllers.

    7.1. Using Partials to Eliminate View Duplication

    As you saw earlier, the scaffold-generated views for the new and edit actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template:

    -

    new.html.erb: -[source, ruby]

    +

    new.html.erb:

    -
    -
    <h1>New post</h1>
    +
    +
    <h1>New post</h1>
     
    -<%= render :partial => "form" %>
    +<%= render :partial => "form" %>
     
    -<%= link_to 'Back', posts_path %>
    -
    -

    edit.html.erb: -[source, ruby]

    +<%= link_to 'Back', posts_path %> +
    +

    edit.html.erb:

    -
    -
    <h1>Editing post</h1>
    +
    +
    <h1>Editing post</h1>
     
    -<%= render :partial => "form" %>
    +<%= render :partial => "form" %>
     
    -<%= link_to 'Show', @post %> |
    -<%= link_to 'Back', posts_path %>
    -
    -

    _form.html.erb: -[source, ruby]

    +<%= link_to 'Show', @post %> | +<%= link_to 'Back', posts_path %> +
    +

    _form.html.erb:

    -
    -
    <% form_for(@post) do |f| %>
    -  <%= f.error_messages %>
    -
    -  <p>
    -    <%= f.label :name %><br />
    -    <%= f.text_field :name %>
    -  </p>
    -  <p>
    -    <%= f.label :title, "title" %><br />
    -    <%= f.text_field :title %>
    -  </p>
    -  <p>
    -    <%= f.label :content %><br />
    -    <%= f.text_area :content %>
    -  </p>
    -  <p>
    -    <%= f.submit "Save" %>
    -  </p>
    -<% end %>
    -
    +
    +
    <% form_for(@post) do |f| %>
    +  <%= f.error_messages %>
    +
    +  <p>
    +    <%= f.label :name %><br />
    +    <%= f.text_field :name %>
    +  </p>
    +  <p>
    +    <%= f.label :title, "title" %><br />
    +    <%= f.text_field :title %>
    +  </p>
    +  <p>
    +    <%= f.label :content %><br />
    +    <%= f.text_area :content %>
    +  </p>
    +  <p>
    +    <%= f.submit "Save" %>
    +  </p>
    +<% end %>
    +

    Now, when Rails renders the new or edit view, it will insert the _form partial at the indicated point. Note the naming convention for partials: if you refer to a partial named form inside of a view, the corresponding file is _form.html.erb, with a leading underscore.

    For more information on partials, refer to the Layouts and Rending in Rails guide.

    7.2. Using Filters to Eliminate Controller Duplication

    @@ -1721,32 +1727,32 @@ http://www.gnu.org/software/src-highlite -->
  • -+app/helpers/comments_helper.rb - A view helper file +app/helpers/comments_helper.rb - A view helper file

  • -+app/views/comments/index.html.erb - The view for the index action +app/views/comments/index.html.erb - The view for the index action

  • -+app/views/comments/show.html.erb - The view for the show action +app/views/comments/show.html.erb - The view for the show action

  • -+app/views/comments/new.html.erb - The view for the new action +app/views/comments/new.html.erb - The view for the new action

  • -+app/views/comments/edit.html.erb - The view for the edit action +app/views/comments/edit.html.erb - The view for the edit action

  • -+test/functional/comments_controller_test.rb - The functional tests for the controller +test/functional/comments_controller_test.rb - The functional tests for the controller

  • @@ -1984,7 +1990,7 @@ http://www.gnu.org/software/src-highlite -->
    +

    Rails also comes with built-in help that you can generate using the rake command-line utility:

    +

    10. Changelog

    @@ -2010,6 +2029,11 @@ The Rails wiki
    • +November 3, 2008: Formatting patch from Dave Rothlisberger +

      +
    • +
    • +

      November 1, 2008: First approved version by Mike Gunderloy

    • diff --git a/railties/doc/guides/html/index.html b/railties/doc/guides/html/index.html index 306257678b..991b10c7e8 100644 --- a/railties/doc/guides/html/index.html +++ b/railties/doc/guides/html/index.html @@ -250,7 +250,7 @@ ul#navMain {
    @@ -276,14 +276,6 @@ understand how to use routing in your own Rails applications, start here.

    @@ -318,20 +310,12 @@ Enjoy.

    -

    will run the down method fron the last 3 migrations.

    +

    will run the down method from the last 3 migrations.

    The db:migrate:redo task is a shortcut for doing a rollback and then migrating back up again. As with the db:rollback task you can use the STEP parameter if you need to go more than one version back, for example

    diff --git a/railties/doc/guides/html/testing_rails_applications.html b/railties/doc/guides/html/testing_rails_applications.html index 73634ef328..666d1dff85 100644 --- a/railties/doc/guides/html/testing_rails_applications.html +++ b/railties/doc/guides/html/testing_rails_applications.html @@ -233,7 +233,7 @@ ul#navMain {
  • What to include in your Functional Tests
  • -
  • Available Request Types for Functional Tests===
  • +
  • Available Request Types for Functional Tests
  • The 4 Hashes of the Apocalypse
  • @@ -398,15 +398,12 @@ steve:

    Fixtures can also be described using the all-too-familiar comma-separated value (CSV) file format. These files, just like YAML fixtures, are placed in the test/fixtures directory, but these end with the .csv file extension (as in celebrity_holiday_figures.csv).

    A CSV fixture looks like this:

    -
    +
    id, username, password, stretchable, comments
     1, sclaus, ihatekids, false, I like to say ""Ho! Ho! Ho!""
     2, ebunny, ihateeggs, true, Hoppity hop y'all
    -3, tfairy, ilovecavities, true, "Pull your teeth, I will"
    -
    +3, tfairy, ilovecavities, true, "Pull your teeth, I will" +

    The first line is the header. It is a comma-separated list of fields. The rest of the file is the payload: 1 record per line. A few notes about this format:

    • @@ -535,17 +532,14 @@ email(david.

      In Rails, unit tests are what you write to test your models.

    When you create a model using script/generate, among other things it creates a test stub in the test/unit folder, as well as a fixture for the model:

    -
    +
    $ script/generate model Post
     ...
     create  app/models/post.rb
     create  test/unit/post_test.rb
     create  test/fixtures/posts.yml
    -...
    -
    +... +

    The default test stub in test/unit/post_test.rb looks like this:

    +
    $ ruby unit/post_test.rb -n test_truth
     
     Loaded suite unit/post_test
    @@ -648,8 +639,8 @@ Started
     .
     Finished in 0.023513 seconds.
     
    -1 tests, 1 assertions, 0 failures, 0 errors
    -
    +1 tests, 1 assertions, 0 failures, 0 errors +

    The . (dot) above indicates a passing test. When a test fails you see an F; when a test throws an error you see an E in its place. The last line of the output is the summary.

    To see how a test failure is reported, you can add a failing test to the post_test.rb test case:

    @@ -664,10 +655,7 @@ http://www.gnu.org/software/src-highlite -->

    If you haven't added any data to the test fixture for posts, this test will fail. You can see this by running it:

    -
    +
    $ ruby unit/post_test.rb
     Loaded suite unit/post_test
     Started
    @@ -681,8 +669,8 @@ test_should_have_atleast_one_post(PostTest)
          /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']:
     <nil> expected to not be nil.
     
    -2 tests, 2 assertions, 1 failures, 0 errors
    -
    +2 tests, 2 assertions, 1 failures, 0 errors +

    In the output, F denotes a failure. You can see the corresponding trace shown under 1) along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable every assertion provides an optional message parameter, as shown here:

    Running this test shows the friendlier assertion message:

    -
    +
    $ ruby unit/post_test.rb
     Loaded suite unit/post_test
     Started
    @@ -714,8 +699,8 @@ test_should_have_atleast_one_post(PostTest)
     Should not be nil as Posts table should have atleast one post.
     <nil> expected to not be nil.
     
    -2 tests, 2 assertions, 1 failures, 0 errors
    -
    +2 tests, 2 assertions, 1 failures, 0 errors +

    To see how an error gets reported, here's a test containing an error:

    Now you can see even more output in the console from running the tests:

    -
    +
    $ ruby unit/post_test.rb
     Loaded suite unit/post_test
     Started
    @@ -756,8 +738,8 @@ NameError: undefined local variable or method `some_undefined_variable' for #<
         /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
         /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run'
     
    -3 tests, 2 assertions, 1 failures, 1 errors
    -
    +3 tests, 2 assertions, 1 failures, 1 errors +

    Notice the E in the output. It denotes a test with error.

    @@ -1160,7 +1142,7 @@ http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
     
    -

    4.2. Available Request Types for Functional Tests===

    +

    4.2. Available Request Types for Functional Tests

    If you're familiar with the HTTP protocol, you'll know that get is a type of request. There are 5 request types supported in Rails functional tests:

    • @@ -1638,16 +1620,13 @@ http://www.gnu.org/software/src-highlite -->

      In this test, @expected is an instance of TMail::Mail that you can use in your tests. It is defined in ActionMailer::TestCase. The test above uses @expected to construct an email, which it then asserts with email created by the custom mailer. The invite fixture is the body of the email and is used as the sample content to assert against. The helper read_fixture is used to read in the content from this file.

      Here's the content of the invite fixture:

      -
      +
      Hi friend@example.com,
       
       You have been invited.
       
      -Cheers!
      -
      +Cheers! +

    This is the right time to understand a little more about writing tests for your mailers. The line ActionMailer::Base.delivery_method = :test in config/environments/test.rb sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (ActionMailer::Base.deliveries).

    However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be.

    6.3. Functional Testing

    diff --git a/railties/doc/guides/source/2_2_release_notes.txt b/railties/doc/guides/source/2_2_release_notes.txt index 2e9e9b8aa1..715648b95e 100644 --- a/railties/doc/guides/source/2_2_release_notes.txt +++ b/railties/doc/guides/source/2_2_release_notes.txt @@ -44,7 +44,7 @@ The internal documentation of Rails, in the form of code comments, has been impr All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers. -If you want to these generate guides locally, inside your application: +If you want to generate these guides locally, inside your application: [source, ruby] ------------------------------------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/changelog.txt b/railties/doc/guides/source/actioncontroller_basics/changelog.txt new file mode 100644 index 0000000000..4ee16af19e --- /dev/null +++ b/railties/doc/guides/source/actioncontroller_basics/changelog.txt @@ -0,0 +1,5 @@ +== Changelog == + +http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/17[Lighthouse ticket] + +* November 4, 2008: First release version by Tore Darrell diff --git a/railties/doc/guides/source/actioncontroller_basics/cookies.txt b/railties/doc/guides/source/actioncontroller_basics/cookies.txt index d451f3f7a6..88b99de3ee 100644 --- a/railties/doc/guides/source/actioncontroller_basics/cookies.txt +++ b/railties/doc/guides/source/actioncontroller_basics/cookies.txt @@ -31,4 +31,4 @@ class CommentsController < ApplicationController end ----------------------------------------- -Note that while for session values, you set the key to `nil`, to delete a cookie value, you use `cookies.delete(:key)`. +Note that while for session values, you set the key to `nil`, to delete a cookie value, you should use `cookies.delete(:key)`. diff --git a/railties/doc/guides/source/actioncontroller_basics/csrf.txt b/railties/doc/guides/source/actioncontroller_basics/csrf.txt new file mode 100644 index 0000000000..87e3d39c88 --- /dev/null +++ b/railties/doc/guides/source/actioncontroller_basics/csrf.txt @@ -0,0 +1,32 @@ +== Request Forgery Protection == + +Cross-site request forgery is a type of attack in which a site tricks a user into making requests on another site, possibly adding, modifying or deleting data on that site without the user's knowledge or permission. The first step to avoid this is to make sure all "destructive" actions (create, update and destroy) can only be accessed with non-GET requests. If you're following RESTful conventions you're already doing this. However, a malicious site can still send a non-GET request to your site quite easily, and that's where the request forgery protection comes in. As the name says, it protects from forged requests. The way this is done is to add a non-guessable token which is only known to your server to each request. This way, if a request comes in without the proper token, it will be denied access. + +If you generate a form like this: + +[source, ruby] +----------------------------------------- +<% form_for @user do |f| -%> + <%= f.text_field :username %> + <%= f.text_field :password -%> +<% end -%> +----------------------------------------- + +You will see how the token gets added as a hidden field: + +[source, html] +----------------------------------------- + +
    + + +----------------------------------------- + +Rails adds this token to every form that's generated using the link:../form_helpers.html[form helpers], so most of the time you don't have to worry about it. If you're writing a form manually or need to add the token for another reason, it's available through the method `form_authenticity_token`: + +.Add a JavaScript variable containing the token for use with Ajax +----------------------------------------- +<%= javascript_tag "MyApp.authenticity_token = '#{form_authenticity_token}'" %> +----------------------------------------- + +The link:../security.html[Security Guide] has more about this and a lot of other security-related issues that you should be aware of when developing a web application. diff --git a/railties/doc/guides/source/actioncontroller_basics/filters.txt b/railties/doc/guides/source/actioncontroller_basics/filters.txt index a6f688d144..df67977efd 100644 --- a/railties/doc/guides/source/actioncontroller_basics/filters.txt +++ b/railties/doc/guides/source/actioncontroller_basics/filters.txt @@ -1,6 +1,6 @@ == Filters == -Filters are methods that are run before, after or "around" a controller action. For example, one filter might check to see if the logged in user has the right credentials to access that particular controller or action. Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application. A common, simple filter is one which requires that a user is logged in for an action to be run. Let's define the filter method first: +Filters are methods that are run before, after or "around" a controller action. For example, one filter might check to see if the logged in user has the right credentials to access that particular controller or action. Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application. A common, simple filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way: [source, ruby] --------------------------------- @@ -27,7 +27,7 @@ private end --------------------------------- -The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a before filter (a filter which is run before the action) renders or redirects, the action will not run. If there are additional filters scheduled to run after the rendering/redirecting filter, they are also cancelled. To use this filter in a controller, use the link:http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html#M000704[before_filter] method: +The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a before filter (a filter which is run before the action) renders or redirects, the action will not run. If there are additional filters scheduled to run after the rendering or redirecting filter, they are also cancelled. To use this filter in a controller, use the `before_filter` method: [source, ruby] --------------------------------- @@ -38,7 +38,7 @@ class ApplicationController < ActionController::Base end --------------------------------- -In this example, the filter is added to ApplicationController and thus all controllers in the application. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this, so to prevent this filter from running you can use link:http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html#M000711[skip_before_filter] : +In this example, the filter is added to ApplicationController and thus all controllers in the application. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with `skip_before_filter` : [source, ruby] --------------------------------- @@ -49,9 +49,9 @@ class LoginsController < Application end --------------------------------- -Now, the LoginsController's "new" and "create" actions will work as before without requiring the user to be logged in. The `:only` option is used to only skip this filter for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. +Now, the +LoginsController+'s "new" and "create" actions will work as before without requiring the user to be logged in. The `:only` option is used to only skip this filter for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. -=== After filters and around filters === +=== After Filters and Around Filters === In addition to the before filters, you can run filters after an action has run or both before and after. The after filter is similar to the before filter, but because the action has already been run it has access to the response data that's about to be sent to the client. Obviously, after filters can not stop the action from running. Around filters are responsible for running the action, but they can choose not to, which is the around filter's way of stopping it. @@ -75,11 +75,11 @@ private end --------------------------------- -=== Other ways to use filters === +=== Other Ways to Use Filters === While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing. -The first is to use a block directly with the *_filter methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritte to use a block: +The first is to use a block directly with the *_filter methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block: [source, ruby] --------------------------------- @@ -92,7 +92,7 @@ end Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful. -The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex than can not be implemented in a readable and reusable way using the two other methods. As an example, we will rewrite the login filter again to use a class: +The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex than can not be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class: [source, ruby] --------------------------------- @@ -114,6 +114,6 @@ class LoginFilter end --------------------------------- -Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets it passed as an argument. The filter class has a class method `filter` which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same `filter` method, which will get run in the same way. The method must `yield` to execute the action. Alternatively, it can have both a `before` and an `after` method that are run before and after the action. +Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method `filter` which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same `filter` method, which will get run in the same way. The method must `yield` to execute the action. Alternatively, it can have both a `before` and an `after` method that are run before and after the action. The Rails API documentation has link:http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html[more information on using filters]. diff --git a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt index 7df0e635bf..954b8a525e 100644 --- a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt +++ b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt @@ -1,6 +1,6 @@ == HTTP Basic Authentication == -Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, we will create an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, link:http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#M000610[authenticate_or_request_with_http_basic]. +Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, `authenticate_or_request_with_http_basic`. [source, ruby] ------------------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/index.txt b/railties/doc/guides/source/actioncontroller_basics/index.txt index 0b884e590b..6865ace97b 100644 --- a/railties/doc/guides/source/actioncontroller_basics/index.txt +++ b/railties/doc/guides/source/actioncontroller_basics/index.txt @@ -1,7 +1,15 @@ Action Controller basics ======================= -In this guide you will learn how controllers work and how they fit into the request cycle in your application. You will learn how to make use of the many tools provided by Action Controller to work with the session, cookies and filters and how to use the built-in HTTP authentication and data streaming facilities. In the end, we will take a look at some tools that will be useful once your controllers are ready and working, like how to filter sensitive parameters from the log and how to rescue and deal with exceptions that may be raised during the request. +In this guide you will learn how controllers work and how they fit into the request cycle in your application. After reading this guide, you will be able to: + +* Follow the flow of a request through a controller +* Understand why and how to store data in the session or cookies +* Work with filters to execute code during request processing +* Use Action Controller's built-in HTTP authentication +* Stream data directly to the user's browser +* Filter sensitive parameters so they do not appear in the application's log +* Deal with exceptions that may be raised during request processing include::introduction.txt[] @@ -17,6 +25,8 @@ include::filters.txt[] include::verification.txt[] +include::csrf.txt[] + include::request_response_objects.txt[] include::http_auth.txt[] @@ -26,3 +36,5 @@ include::streaming.txt[] include::parameter_filtering.txt[] include::rescue.txt[] + +include::changelog.txt[] diff --git a/railties/doc/guides/source/actioncontroller_basics/introduction.txt b/railties/doc/guides/source/actioncontroller_basics/introduction.txt index e4b0953b95..6ea217dbb9 100644 --- a/railties/doc/guides/source/actioncontroller_basics/introduction.txt +++ b/railties/doc/guides/source/actioncontroller_basics/introduction.txt @@ -1,7 +1,9 @@ -== What does a controller do? == +== What Does a Controller do? == Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straight-forward as possible. For most conventional RESTful applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. -A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display it to the user, and it saves or updates data from the user to the model. +A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model. + +NOTE: For more details on the routing process, see link:../routing_outside_in.html[Rails Routing from the Outside In]. diff --git a/railties/doc/guides/source/actioncontroller_basics/methods.txt b/railties/doc/guides/source/actioncontroller_basics/methods.txt index 370b492e41..c6ae54a540 100644 --- a/railties/doc/guides/source/actioncontroller_basics/methods.txt +++ b/railties/doc/guides/source/actioncontroller_basics/methods.txt @@ -1,10 +1,10 @@ -== Methods and actions == +== Methods and Actions == -A controller is a Ruby class which inherits from ActionController::Base and has methods just like any other class. Usually these methods correspond to actions in MVC, but they can just as well be helpful methods which can be called by actions. When your application receives a request, the routing will determine which controller and action to run. Then an instance of that controller will be created and the method corresponding to the action (the method with the same name as the action) gets run. +A controller is a Ruby class which inherits from ApplicationController and has methods just like any other class. Usually these methods correspond to actions in MVC, but they can just as well be helpful methods which can be called by actions. When your application receives a request, the routing will determine which controller and action to run. Then Rails creates an instance of that controller and runs the method corresponding to the action (the method with the same name as the action). [source, ruby] ---------------------------------------------- -class ClientsController < ActionController::Base +class ClientsController < ApplicationController # Actions are public methods def new @@ -25,7 +25,7 @@ end Private methods in a controller are also used as filters, which will be covered later in this guide. -As an example, if the user goes to `/clients/new` in your application to add a new client, a ClientsController instance will be created and the `new` method will be run. Note that the empty method from the example above could work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new Client: +As an example, if the user goes to `/clients/new` in your application to add a new client, Rails will create a ClientsController instance will be created and run the `new` method. Note that the empty method from the example above could work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new Client: [source, ruby] ---------------------------------------------- @@ -35,3 +35,5 @@ end ---------------------------------------------- The link:../layouts_and_rendering.html[Layouts & rendering guide] explains this in more detail. + +ApplicationController inherits from ActionController::Base, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the API documentation or in the source itself. diff --git a/railties/doc/guides/source/actioncontroller_basics/parameter_filtering.txt b/railties/doc/guides/source/actioncontroller_basics/parameter_filtering.txt index c4577d4f6d..e29f631038 100644 --- a/railties/doc/guides/source/actioncontroller_basics/parameter_filtering.txt +++ b/railties/doc/guides/source/actioncontroller_basics/parameter_filtering.txt @@ -1,6 +1,6 @@ -== Parameter filtering == +== Parameter Filtering == -Rails keeps a log file for each environment (development, test and production) in the "log" folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. The link:http://api.rubyonrails.org/classes/ActionController/Base.html#M000837[filter_parameter_logging] method can be used to filter out sensitive information from the log. It works by replacing certain keys in the `params` hash with "[FILTERED]" as they are written to the log. As an example, let's see how to filter all parameters with keys that include "password": +Rails keeps a log file for each environment (development, test and production) in the "log" folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. The `filter_parameter_logging` method can be used to filter out sensitive information from the log. It works by replacing certain values in the `params` hash with "[FILTERED]" as they are written to the log. As an example, let's see how to filter all parameters with keys that include "password": [source, ruby] ------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/params.txt b/railties/doc/guides/source/actioncontroller_basics/params.txt index 7f494d7c9b..fb380519fd 100644 --- a/railties/doc/guides/source/actioncontroller_basics/params.txt +++ b/railties/doc/guides/source/actioncontroller_basics/params.txt @@ -1,14 +1,15 @@ == Parameters == -You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from a HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the `params` hash in your controller: +You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from a HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the `params` hash in your controller: [source, ruby] ------------------------------------- class ClientsController < ActionController::Base - # This action uses query string parameters because it gets run by a HTTP GET request, - # but this does not make any difference to the way in which the parameters are accessed. - # The URL for this action would look like this in order to list activated clients: /clients?status=activated + # This action uses query string parameters because it gets run by a HTTP + # GET request, but this does not make any difference to the way in which + # the parameters are accessed. The URL for this action would look like this + # in order to list activated clients: /clients?status=activated def index if params[:status] = "activated" @clients = Client.activated @@ -34,12 +35,12 @@ class ClientsController < ActionController::Base end ------------------------------------- -=== Hash and array parameters === +=== Hash and Array Parameters === The params hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append "[]" to the key name: ------------------------------------- -GET /clients?ids[]=1&ids[2]&ids[]=3 +GET /clients?ids[]=1&ids[]=2&ids[]=3 ------------------------------------- The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type. @@ -57,6 +58,32 @@ To send a hash you include the key name inside the brackets: The value of `params[:client]` when this form is submitted will be `{:name => "Acme", :phone => "12345", :address => {:postcode => "12345", :city => "Carrot City"}}`. Note the nested hash in `params[:client][:address]`. -=== Routing parameters === +=== Routing Parameters === -The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. +The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL: + +[source, ruby] +------------------------------------ +# ... +map.connect "/clients/:status", :controller => "clients", :action => "index", :foo => "bar" +# ... +------------------------------------ + +In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string in the same way `params[:action]` will contain "index". + +=== `default_url_options` === + +You can set global default parameters that will be used when generating URLs with `default_url_options`. To do this, define a method with that name in your controller: + +------------------------------------ +class ApplicationController < ActionController::Base + + #The options parameter is the hash passed in to url_for + def default_url_options(options) + {:locale => I18n.locale} + end + +end +------------------------------------ + +These options will be used as a starting-point when generating, so it's possible they'll be overridden by url_for. Because this method is defined in the controller, you can define it on ApplicationController so it would be used for all URL generation, or you could define it on only one controller for all URLs generated there. diff --git a/railties/doc/guides/source/actioncontroller_basics/request_response_objects.txt b/railties/doc/guides/source/actioncontroller_basics/request_response_objects.txt index 493bd4cb43..250f84bd72 100644 --- a/railties/doc/guides/source/actioncontroller_basics/request_response_objects.txt +++ b/railties/doc/guides/source/actioncontroller_basics/request_response_objects.txt @@ -1,13 +1,13 @@ -== The request and response objects == +== The +request+ and +response+ Objects == -In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of link:http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html[AbstractRequest] and the `response` method contains the link:http://github.com/rails/rails/tree/master/actionpack/lib/action_controller/response.rb[response object] representing what is going to be sent back to the client. +In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of AbstractRequest and the `response` method returns a +response+ object representing what is going to be sent back to the client. -=== The request === +=== The +request+ Object === -The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the link:http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html[API documentation]. +The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the link:http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html[API documentation]. Among the properties that you can access on this object: * host - The hostname used for this request. - * domain - The hostname without the first part (usually "www"). + * domain - The hostname without the first segment (usually "www"). * format - The content type requested by the client. * method - The HTTP method used for the request. * get?, post?, put?, delete?, head? - Returns true if the HTTP method is get/post/put/delete/head. @@ -18,18 +18,26 @@ The request object contains a lot of useful information about the request coming * remote_ip - The IP address of the client. * url - The entire URL used for the request. -==== path_parameters, query_parameters and request_parameters ==== +==== +path_parameters+, +query_parameters+ and +request_parameters+ ==== -TODO: Does this belong here? +Rails collects all of the parameters sent along with the request in the `params` hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The `query_parameters` hash contains parameters that were sent as part of the query string while the `request_parameters` hash contains parameters sent as part of the post body. The `path_parameters` hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action. -Rails collects all of the parameters sent along with the request in the `params` hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The `query_parameters` hash contains parameters that were sent as part of the query string while the `request_parameters` hash contains parameters sent as part of the post body. The `path_parameters` hash contains parameters that were recognised by the routing as being part of the path leading to this particular controller and action. +=== The +response+ Object === -=== The response === - -The response objects is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. +The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. * body - This is the string of data being sent back to the client. This is most often HTML. * status - The HTTP status code for the response, like 200 for a successful request or 404 for file not found. * location - The URL the client is being redirected to, if any. * content_type - The content type of the response. * charset - The character set being used for the response. Default is "utf8". + * headers - Headers used for the response. + +==== Setting Custom Headers ==== + +If you want to set custom headers for a response then `response.headers` is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them - like "Content-Type" - automatically. If you want to add or change a header, just assign it to `headers` with the name and value: + +[source, ruby] +------------------------------------- +response.headers["Content-Type"] = "application/pdf" +------------------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/rescue.txt b/railties/doc/guides/source/actioncontroller_basics/rescue.txt index ec03006764..3353df617c 100644 --- a/railties/doc/guides/source/actioncontroller_basics/rescue.txt +++ b/railties/doc/guides/source/actioncontroller_basics/rescue.txt @@ -2,15 +2,15 @@ Most likely your application is going to contain bugs or otherwise throw an exception that needs to be handled. For example, if the user follows a link to a resource that no longer exists in the database, Active Record will throw the ActiveRecord::RecordNotFound exception. Rails' default exception handling displays a 500 Server Error message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application: -=== The default 500 and 404 templates === +=== The Default 500 and 404 Templates === By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML. === `rescue_from` === -If you want to do something a bit more elaborate when catching errors, you can use link::http://api.rubyonrails.org/classes/ActionController/Rescue/ClassMethods.html#M000620[rescue_from], which handles exceptions of a certain type (or multiple types) in an entire controller and its subclasses. When an exception occurs which is caught by a rescue_from directive, the exception object is passed to the handler. The handler can be a method or a Proc object passed to the `:with` option. You can also use a block directly instead of an explicit Proc object. +If you want to do something a bit more elaborate when catching errors, you can use `rescue_from`, which handles exceptions of a certain type (or multiple types) in an entire controller and its subclasses. When an exception occurs which is caught by a +rescue_from+ directive, the exception object is passed to the handler. The handler can be a method or a Proc object passed to the `:with` option. You can also use a block directly instead of an explicit Proc object. -Let's see how we can use rescue_from to intercept all ActiveRecord::RecordNotFound errors and do something with them. +Here's how you can use +rescue_from+ to intercept all ActiveRecord::RecordNotFound errors and do something with them. [source, ruby] ----------------------------------- @@ -27,7 +27,7 @@ private end ----------------------------------- -Of course, this example is anything but elaborate and doesn't improve the default exception handling at all, but once you can catch all those exceptions you're free to do whatever you want with them. For example, you could create custom exception classes that will be thrown when a user doesn't have access to a certain section of your application: +Of course, this example is anything but elaborate and doesn't improve on the default exception handling at all, but once you can catch all those exceptions you're free to do whatever you want with them. For example, you could create custom exception classes that will be thrown when a user doesn't have access to a certain section of your application: [source, ruby] ----------------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/session.txt b/railties/doc/guides/source/actioncontroller_basics/session.txt index 467cffbf85..3b69ec82ef 100644 --- a/railties/doc/guides/source/actioncontroller_basics/session.txt +++ b/railties/doc/guides/source/actioncontroller_basics/session.txt @@ -7,9 +7,11 @@ Your application has a session for each user in which you can store small amount * MemCacheStore - Stores the data in MemCache. * ActiveRecordStore - Stores the data in a database using Active Record. -All session stores store the session id in a cookie - there is no other way of passing it to the server. Most stores also use this key to locate the session data on the server. +All session stores store either the session ID or the entire session in a cookie - Rails does not allow the session ID to be passed in any other way. Most stores also use this key to locate the session data on the server. -The default and recommended store, the Cookie Store, does not store session data on the server, but in the cookie itself. The data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents. It can only store about 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data is discouraged no matter which session store your application uses. Expecially discouraged is storing complex objects (anything other than basic Ruby objects, the primary example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. The Cookie Store has the added advantage that it does not require any setting up beforehand - Rails will generate a "secret key" which will be used to sign the cookie when you create the application. +The default and recommended store, the Cookie Store, does not store session data on the server, but in the cookie itself. The data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it. It can only store about 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the primary example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. The Cookie Store has the added advantage that it does not require any setting up beforehand - Rails will generate a "secret key" which will be used to sign the cookie when you create the application. + +Read more about session storage in the link:../security.html[Security Guide]. If you need a different session storage mechanism, you can change it in the `config/environment.rb` file: @@ -19,9 +21,9 @@ If you need a different session storage mechanism, you can change it in the `con config.action_controller.session_store = :active_record_store ------------------------------------------ -=== Disabling the session === +=== Disabling the Session === -Sometimes you don't need a session, and you can turn it off to avoid the unnecessary overhead. To do this, use the link:http://api.rubyonrails.org/classes/ActionController/SessionManagement/ClassMethods.html#M000649[session] class method in your controller: +Sometimes you don't need a session. In this case, you can turn it off to avoid the unnecessary overhead. To do this, use the `session` class method in your controller: [source, ruby] ------------------------------------------ @@ -41,7 +43,7 @@ class LoginsController < ActionController::Base end ------------------------------------------ -Or even a single action: +Or even for specified actions: [source, ruby] ------------------------------------------ @@ -50,7 +52,7 @@ class ProductsController < ActionController::Base end ------------------------------------------ -=== Accessing the session === +=== Accessing the Session === In your controller you can access the session through the `session` instance method. @@ -65,7 +67,7 @@ class ApplicationController < ActionController::Base private # Finds the User with the ID stored in the session with the key :current_user_id - # This is a common way to do user login in a Rails application; logging in sets the + # This is a common way to handle user login in a Rails application; logging in sets the # session value and logging out removes it. def current_user @_current_user ||= session[:current_user_id] && User.find(session[:current_user_id]) @@ -108,11 +110,11 @@ class LoginsController < ApplicationController end ------------------------------------------ -To reset the entire session, use link:http://api.rubyonrails.org/classes/ActionController/Base.html#M000855[reset_session]. +To reset the entire session, use `reset_session`. === The flash === -The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can set a message which will be displayed to the user on the next request: +The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request: [source, ruby] ------------------------------------------ @@ -163,7 +165,7 @@ class MainController < ApplicationController end ------------------------------------------ -==== flash.now ==== +==== +flash.now+ ==== By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the `create` action fails to save a resource and you render the `new` template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use `flash.now` in the same way you use the normal `flash`: diff --git a/railties/doc/guides/source/actioncontroller_basics/streaming.txt b/railties/doc/guides/source/actioncontroller_basics/streaming.txt index 41d56935b9..f42480ba25 100644 --- a/railties/doc/guides/source/actioncontroller_basics/streaming.txt +++ b/railties/doc/guides/source/actioncontroller_basics/streaming.txt @@ -1,6 +1,6 @@ -== Streaming and file downloads == +== Streaming and File Downloads == -Sometimes you may want to send a file to the user instead of rendering an HTML page. All controllers in Rails have the link:http://api.rubyonrails.org/classes/ActionController/Streaming.html#M000624[send_data] and the link:http://api.rubyonrails.org/classes/ActionController/Streaming.html#M000623[send_file] methods, that will both stream data to the client. `send_file` is a convenience method which lets you provide the name of a file on the disk and it will stream the contents of that file for you. +Sometimes you may want to send a file to the user instead of rendering an HTML page. All controllers in Rails have the `send_data` and the `send_file` methods, that will both stream data to the client. `send_file` is a convenience method which lets you provide the name of a file on the disk and it will stream the contents of that file for you. To stream data to the client, use `send_data`: @@ -31,7 +31,7 @@ end The `download_pdf` action in the example above will call a private method which actually generates the file (a PDF document) and returns it as a string. This string will then be streamed to the client as a file download and a filename will be suggested to the user. Sometimes when streaming files to the user, you may not want them to download the file. Take images, for example, which can be embedded into HTML pages. To tell the browser a file is not meant to be downloaded, you can set the `:disposition` option to "inline". The opposite and default value for this option is "attachment". -=== Sending files === +=== Sending Files === If you want to send a file that already exists on disk, use the `send_file` method. This is usually not recommended, but can be useful if you want to perform some authentication before letting the user download the file. @@ -50,13 +50,13 @@ end This will read and stream the file 4Kb at the time, avoiding loading the entire file into memory at once. You can turn off streaming with the `stream` option or adjust the block size with the `buffer_size` option. -WARNING: Be careful when using (or just don't use) "outside" data (params, cookies, etc) to locate the file on disk, as this is a security risk as someone could gain access to files they are not meant to have access to. +WARNING: Be careful when using (or just don't use) "outside" data (params, cookies, etc) to locate the file on disk, as this is a security risk that might allow someone to gain access to files they are not meant to see. TIP: It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. -=== RESTful downloads === +=== RESTful Downloads === -While `send_data` works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Let's try to rewrite the example so that the PDF download is a part of the `show` action: +While `send_data` works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Here's how you can rewrite the example so that the PDF download is a part of the `show` action, without any streaming: [source, ruby] ---------------------------- @@ -75,7 +75,7 @@ class ClientsController < ApplicationController end ---------------------------- -In order for this example to work, we have to add the PDF MIME type to Rails. This can be done by adding the following line to the file `config/initializers/mime_types.rb`: +In order for this example to work, you have to add the PDF MIME type to Rails. This can be done by adding the following line to the file `config/initializers/mime_types.rb`: [source, ruby] ---------------------------- diff --git a/railties/doc/guides/source/actioncontroller_basics/verification.txt b/railties/doc/guides/source/actioncontroller_basics/verification.txt index 39046eee85..5d8ee6117e 100644 --- a/railties/doc/guides/source/actioncontroller_basics/verification.txt +++ b/railties/doc/guides/source/actioncontroller_basics/verification.txt @@ -1,8 +1,8 @@ == Verification == -Verifications make sure certain criterias are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the `params`, `session` or `flash` hashes or that a certain HTTP method was used or that the request was made using XMLHTTPRequest (Ajax). The default action taken when these criterias are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. It is described in the link:http://api.rubyonrails.org/classes/ActionController/Verification/ClassMethods.html[API codumentation] as "essentially a special kind of before_filter". +Verifications make sure certain criteria are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the `params`, `session` or `flash` hashes or that a certain HTTP method was used or that the request was made using XMLHTTPRequest (Ajax). The default action taken when these criteria are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. It is described in the link:http://api.rubyonrails.org/classes/ActionController/Verification/ClassMethods.html[API documentation] as "essentially a special kind of before_filter". -Let's see how we can use verification to make sure the user supplies a username and a password in order to log in: +Here's an example of using verification to make sure the user supplies a username and a password in order to log in: [source, ruby] --------------------------------------- @@ -34,7 +34,7 @@ class LoginsController < ApplicationController verify :params => [:username, :password], :render => {:action => "new"}, :add_flash => {:error => "Username and password required to log in"}, - :only => :create #Only run this verification for the "create" action + :only => :create # Only run this verification for the "create" action end --------------------------------------- diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 15fc544f25..892adb2d43 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -1,7 +1,7 @@ Active Record Basics ==================== -Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of AcitveRecord. +Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of ActiveRecord. After reading this guide readers should have a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer. diff --git a/railties/doc/guides/source/activerecord_validations_callbacks.txt b/railties/doc/guides/source/activerecord_validations_callbacks.txt new file mode 100644 index 0000000000..cd698d0c1e --- /dev/null +++ b/railties/doc/guides/source/activerecord_validations_callbacks.txt @@ -0,0 +1,25 @@ +Active Record Validations and Callbacks +======================================= + +This guide teaches you how to work with the lifecycle of your Active Record objects. More precisely, you will learn how to validate the state of your objects before they go into the database and also how to teach them to perform custom operations at certain points of their lifecycles. + +After reading this guide and trying out the presented concepts, we hope that you'll be able to: + +* Correctly use all the built-in Active Record validation helpers +* Create your own custom validation methods +* Work with the error messages generated by the validation proccess +* Register callback methods that will execute custom operations during your objects lifecycle, for example before/after they are saved. +* Create special classes that encapsulate common behaviour for your callbacks +* Create Observers - classes with callback methods specific for each of your models, keeping the callback code outside your models' declarations. + +== Active Record Validations + + + +== Credits + + + +== Changelog + +http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks diff --git a/railties/doc/guides/source/authors.txt b/railties/doc/guides/source/authors.txt index 8d0970e4f6..94dfc4db08 100644 --- a/railties/doc/guides/source/authors.txt +++ b/railties/doc/guides/source/authors.txt @@ -23,3 +23,17 @@ Cofounder of http://www.eventioz.com[Eventioz]. He has been using Rails since 20 Can be found at gmail, twitter, freenode, everywhere as miloops. *********************************************************** +.Heiko Webers +[[hawe]] +*********************************************************** +Heiko Webers is the founder of http://www.bauland42.de[bauland42], a German web application security consulting and development +company focused on Ruby on Rails. He blogs at http://www.rorsecurity.info. After 10 years of desktop application development, +Heiko has rarely looked back. +*********************************************************** + +.Tore Darell +[[toretore]] +*********************************************************** +Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails +and unobtrusive JavaScript. His home on the internet is his blog http://tore.darell.no/[Sneaky Abstractions]. +*********************************************************** diff --git a/railties/doc/guides/source/configuring.txt b/railties/doc/guides/source/configuring.txt new file mode 100644 index 0000000000..07b630c59d --- /dev/null +++ b/railties/doc/guides/source/configuring.txt @@ -0,0 +1,225 @@ +Configuring Rails Applications +============================== + +This guide covers the configuration and initialization features available to Rails applications. By referring to this guide, you will be able to: + +* Adjust the behavior of your Rails applications +* Add additional code to be run at application start time + +== Locations for Initialization Code + +preinitializers +environment.rb first +env-specific files +initializers (load_application_initializers) +after-initializer + +== Using a Preinitializer + +== Configuring Rails Components + +=== Configuring Active Record + +=== Configuring Action Controller + +=== Configuring Action View + +=== Configuring Action Mailer + +=== Configuring Active Resource + +=== Configuring Active Support + +== Using Initializers + organization, controlling load order + +== Using an After-Initializer + +== Changelog == + +http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/28[Lighthouse ticket] + +* November 5, 2008: Rough outline by link:../authors.html#mgunderloy[Mike Gunderloy] + + +actionmailer/lib/action_mailer/base.rb +257: cattr_accessor :logger +267: cattr_accessor :smtp_settings +273: cattr_accessor :sendmail_settings +276: cattr_accessor :raise_delivery_errors +282: cattr_accessor :perform_deliveries +285: cattr_accessor :deliveries +288: cattr_accessor :default_charset +291: cattr_accessor :default_content_type +294: cattr_accessor :default_mime_version +297: cattr_accessor :default_implicit_parts_order +299: cattr_reader :protected_instance_variables + +actionmailer/Rakefile +36: rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + +actionpack/lib/action_controller/base.rb +263: cattr_reader :protected_instance_variables +273: cattr_accessor :asset_host +279: cattr_accessor :consider_all_requests_local +285: cattr_accessor :allow_concurrency +317: cattr_accessor :param_parsers +321: cattr_accessor :default_charset +325: cattr_accessor :logger +329: cattr_accessor :resource_action_separator +333: cattr_accessor :resources_path_names +337: cattr_accessor :request_forgery_protection_token +341: cattr_accessor :optimise_named_routes +351: cattr_accessor :use_accept_header +361: cattr_accessor :relative_url_root + +actionpack/lib/action_controller/caching/pages.rb +55: cattr_accessor :page_cache_directory +58: cattr_accessor :page_cache_extension + +actionpack/lib/action_controller/caching.rb +37: cattr_reader :cache_store +48: cattr_accessor :perform_caching + +actionpack/lib/action_controller/dispatcher.rb +98: cattr_accessor :error_file_path + +actionpack/lib/action_controller/mime_type.rb +24: cattr_reader :html_types, :unverifiable_types + +actionpack/lib/action_controller/rescue.rb +36: base.cattr_accessor :rescue_responses +40: base.cattr_accessor :rescue_templates + +actionpack/lib/action_controller/session/active_record_store.rb +60: cattr_accessor :data_column_name +170: cattr_accessor :connection +173: cattr_accessor :table_name +177: cattr_accessor :session_id_column +181: cattr_accessor :data_column +282: cattr_accessor :session_class + +actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +44: cattr_accessor :included_tags, :instance_writer => false + +actionpack/lib/action_view/base.rb +189: cattr_accessor :debug_rjs +193: cattr_accessor :warn_cache_misses + +actionpack/lib/action_view/helpers/active_record_helper.rb +7: cattr_accessor :field_error_proc + +actionpack/lib/action_view/helpers/form_helper.rb +805: cattr_accessor :default_form_builder + +actionpack/lib/action_view/template_handlers/erb.rb +47: cattr_accessor :erb_trim_mode + +actionpack/test/active_record_unit.rb +5: cattr_accessor :able_to_connect +6: cattr_accessor :connected + +actionpack/test/controller/filters_test.rb +286: cattr_accessor :execution_log + +actionpack/test/template/form_options_helper_test.rb +3:TZInfo::Timezone.cattr_reader :loaded_zones + +activemodel/lib/active_model/errors.rb +28: cattr_accessor :default_error_messages + +activemodel/Rakefile +19: rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + +activerecord/lib/active_record/attribute_methods.rb +9: base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false +11: base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false + +activerecord/lib/active_record/base.rb +394: cattr_accessor :logger, :instance_writer => false +443: cattr_accessor :configurations, :instance_writer => false +450: cattr_accessor :primary_key_prefix_type, :instance_writer => false +456: cattr_accessor :table_name_prefix, :instance_writer => false +461: cattr_accessor :table_name_suffix, :instance_writer => false +467: cattr_accessor :pluralize_table_names, :instance_writer => false +473: cattr_accessor :colorize_logging, :instance_writer => false +478: cattr_accessor :default_timezone, :instance_writer => false +487: cattr_accessor :schema_format , :instance_writer => false +491: cattr_accessor :timestamped_migrations , :instance_writer => false + +activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +11: cattr_accessor :connection_handler, :instance_writer => false + +activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +166: cattr_accessor :emulate_booleans + +activerecord/lib/active_record/fixtures.rb +498: cattr_accessor :all_loaded_fixtures + +activerecord/lib/active_record/locking/optimistic.rb +38: base.cattr_accessor :lock_optimistically, :instance_writer => false + +activerecord/lib/active_record/migration.rb +259: cattr_accessor :verbose + +activerecord/lib/active_record/schema_dumper.rb +13: cattr_accessor :ignore_tables + +activerecord/lib/active_record/serializers/json_serializer.rb +4: base.cattr_accessor :include_root_in_json, :instance_writer => false + +activerecord/Rakefile +142: rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + +activerecord/test/cases/lifecycle_test.rb +61: cattr_reader :last_inherited + +activerecord/test/cases/mixin_test.rb +9: cattr_accessor :forced_now_time + +activeresource/lib/active_resource/base.rb +206: cattr_accessor :logger + +activeresource/Rakefile +43: rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + +activesupport/lib/active_support/buffered_logger.rb +17: cattr_accessor :silencer + +activesupport/lib/active_support/cache.rb +81: cattr_accessor :logger + +activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +5:# cattr_accessor :hair_colors +10: def cattr_reader(*syms) +29: def cattr_writer(*syms) +50: def cattr_accessor(*syms) +51: cattr_reader(*syms) +52: cattr_writer(*syms) + +activesupport/lib/active_support/core_ext/logger.rb +34: cattr_accessor :silencer + +activesupport/test/core_ext/class/attribute_accessor_test.rb +6: cattr_accessor :foo +7: cattr_accessor :bar, :instance_writer => false + +activesupport/test/core_ext/module/synchronization_test.rb +6: @target.cattr_accessor :mutex, :instance_writer => false + +railties/doc/guides/html/creating_plugins.html +786: cattr_accessor :yaffle_text_field,:yaffle_date_field +860: cattr_accessor :yaffle_text_field,:yaffle_date_field + +railties/lib/rails_generator/base.rb +93: cattr_accessor :logger + +railties/Rakefile +265: rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object' + +railties/test/rails_info_controller_test.rb +12: cattr_accessor :local_request + +Rakefile +32: rdoc.options << '-A cattr_accessor=object' + diff --git a/railties/doc/guides/source/debugging_rails_applications.txt b/railties/doc/guides/source/debugging_rails_applications.txt index 24eb0c0431..b45473fc14 100644 --- a/railties/doc/guides/source/debugging_rails_applications.txt +++ b/railties/doc/guides/source/debugging_rails_applications.txt @@ -31,7 +31,6 @@ The `debug` helper will return a
    -tag that renders the object using the YAM
     
     You'll see something like this:
     
    -[source, log]
     ----------------------------------------------------------------------------
     --- !ruby/object:Post
     attributes:
    @@ -64,7 +63,6 @@ The `to_yaml` method converts the method to YAML format leaving it more readable
     
     As a result of this, you will have something like this in your view:
     
    -[source, log]
     ----------------------------------------------------------------------------
     --- !ruby/object:Post
     attributes:
    @@ -94,13 +92,33 @@ Another useful method for displaying object values is `inspect`, especially when
     
     Will be rendered as follows:
     
    -[source, log]
     ----------------------------------------------------------------------------
     [1, 2, 3, 4, 5]
     
     Title: Rails debugging guide
     ----------------------------------------------------------------------------
     
    +=== Debugging Javascript
    +
    +Rails has built-in support to debug RJS, to active it, set `ActionView::Base.debug_rjs` to _true_, this will specify whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it).
    +
    +To enable it, add the following in the `Rails::Initializer do |config|` block inside +environment.rb+:
    +
    +[source, ruby]
    +----------------------------------------------------------------------------
    +config.action_view[:debug_rjs] = true
    +----------------------------------------------------------------------------
    +
    +Or, at any time, setting `ActionView::Base.debug_rjs` to _true_:
    +
    +[source, ruby]
    +----------------------------------------------------------------------------
    +ActionView::Base.debug_rjs = true
    +----------------------------------------------------------------------------
    +
    +[TIP]
    +For more information on debugging javascript refer to link:http://getfirebug.com/[Firebug], the popular debugger for Firefox.
    +
     == The Logger
     
     It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment.
    @@ -183,7 +201,6 @@ end
     
     Here's an example of the log generated by this method:
     
    -[source, log]
     ----------------------------------------------------------------------------
     Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
       Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
    @@ -237,7 +254,6 @@ end
     
     If you see the message in the console or logs:
     
    -[source, log]
     ----------------------------------------------------------------------------
     ***** Debugger requested, but was not available: Start server with --debugger to enable *****
     ----------------------------------------------------------------------------
    @@ -271,7 +287,7 @@ For example:
     (rdb:7)
     ----------------------------------------------------------------------------
     
    -Now it's time to play and dig into your application. A good place to start is by asking the debugger for help... so type: `help` (You didn't see that coming, right?)
    +Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help... so type: `help` (You didn't see that coming, right?)
     
     ----------------------------------------------------------------------------
     (rdb:7) help
    @@ -610,13 +626,91 @@ You can include any number of these configuration lines inside a `.rdebugrc` fil
     
     Here's a good start for an `.rdebugrc`:
     
    -[source, log]
     ----------------------------------------------------------------------------
     set autolist
     set forcestep
     set listsize 25
     ----------------------------------------------------------------------------
     
    +== Debugging Memory Leaks
    +
    +A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level.
    +
    +In this section, you will learn how to find and fix such leaks by using Bleak House and Valgrind debugging tools.
    +
    +=== BleakHouse
    +
    +link:http://github.com/fauna/bleak_house/tree/master[BleakHouse] is a library for finding memory leaks.
    +
    +If a Ruby object does not go out of scope, the Ruby Garbage Collector won't sweep it since it is referenced somewhere. Leaks like this can grow slowly and your application will consume more and more memory, gradually affecting the overall system performance. This tool will help you find leaks on the Ruby heap.
    +
    +To install it run:
    +
    +----------------------------------------------------------------------------
    +sudo gem install bleak_house
    +----------------------------------------------------------------------------
    +
    +Then setup you application for profiling. Then add the following at the bottom of config/environment.rb:
    +
    +[source, ruby]
    +----------------------------------------------------------------------------
    +require 'bleak_house' if ENV['BLEAK_HOUSE']
    +----------------------------------------------------------------------------
    +
    +Start a server instance with BleakHouse integration:
    +
    +----------------------------------------------------------------------------
    +RAILS_ENV=production BLEAK_HOUSE=1 ruby-bleak-house ./script/server
    +----------------------------------------------------------------------------
    +
    +Make sure to run a couple hundred requests to get better data samples, then press `CTRL-C`. The server will stop and Bleak House will produce a dumpfile in `/tmp`:
    +
    +----------------------------------------------------------------------------
    +** BleakHouse: working...
    +** BleakHouse: complete
    +** Bleakhouse: run 'bleak /tmp/bleak.5979.0.dump' to analyze.
    +----------------------------------------------------------------------------
    + 
    +To analyze it, just run the listed command. The top 20 leakiest lines will be listed: 
    +
    +----------------------------------------------------------------------------
    +  191691 total objects
    +  Final heap size 191691 filled, 220961 free
    +  Displaying top 20 most common line/class pairs
    +  89513 __null__:__null__:__node__
    +  41438 __null__:__null__:String
    +  2348 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:Array
    +  1508 /opt/local//lib/ruby/gems/1.8/specifications/gettext-1.90.0.gemspec:14:String
    +  1021 /opt/local//lib/ruby/gems/1.8/specifications/heel-0.2.0.gemspec:14:String
    +   951 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:111:String
    +   935 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:String
    +   834 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:146:Array
    +  ...
    +----------------------------------------------------------------------------
    +
    +This way you can find where your application is leaking memory and fix it.
    +
    +If link:http://github.com/fauna/bleak_house/tree/master[BleakHouse] doesn't report any heap growth but you still have memory growth, you might have a broken C extension, or real leak in the interpreter. In that case, try using Valgrind to investigate further.
    +
    +=== Valgrind
    +
    +link:http://valgrind.org/[Valgrind] is a Linux-only application for detecting C-based memory leaks and race conditions.
    +
    +There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, a C extension in the interpreter calls `malloc()` but is doesn't properly call `free()`, this memory won't be available until the app terminates.
    +
    +For further information on how to install Valgrind and use with Ruby, refer to link:http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/[Valgrind and Ruby] by Evan Weaver.
    +
    +== Plugins for Debugging
    +
    +There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:
    +
    +* link:http://github.com/drnic/rails-footnotes/tree/master[Footnotes]: Every Rails page has footnotes that link give request information and link back to your source via TextMate.
    +* link:http://github.com/ntalbott/query_trace/tree/master[Query Trace]: Adds query origin tracing to your logs.
    +* link:http://github.com/dan-manges/query_stats/tree/master[Query Stats]: A Rails plugin to track database queries. 
    +* link:http://code.google.com/p/query-reviewer/[Query Reviewer]: This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
    +* link:http://github.com/rails/exception_notification/tree/master[Exception Notifier]: Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
    +* link:http://github.com/defunkt/exception_logger/tree/master[Exception Logger]: Logs your Rails exceptions in the database and provides a funky web interface to manage them.
    +
     == References
     
     * link:http://www.datanoise.com/ruby-debug[ruby-debug Homepage]
    @@ -628,10 +722,12 @@ set listsize 25
     * link:http://bashdb.sourceforge.net/ruby-debug.html[Debugging with ruby-debug]
     * link:http://cheat.errtheblog.com/s/rdebug/[ruby-debug cheat sheet]
     * link:http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging[Ruby on Rails Wiki: How to Configure Logging]
    +* link:http://blog.evanweaver.com/files/doc/fauna/bleak_house/files/README.html[Bleak House Documentation]
     
     == Changelog ==
     
     http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/5[Lighthouse ticket]
     
    +* November 3, 2008: Accepted for publication. Added RJS, memory leaks and plugins chapters by link:../authors.html#miloops[Emilio Tagua]
     * October 19, 2008: Copy editing pass by link:../authors.html#mgunderloy[Mike Gunderloy]
     * September 16, 2008: initial version by link:../authors.html#miloops[Emilio Tagua]
    diff --git a/railties/doc/guides/source/finders.txt b/railties/doc/guides/source/finders.txt
    index 945b527e1d..24d078f9e4 100644
    --- a/railties/doc/guides/source/finders.txt
    +++ b/railties/doc/guides/source/finders.txt
    @@ -68,7 +68,6 @@ If you wanted to find clients with id 1 or 2, you call +Client.find([1,2])+ or +
     SELECT * FROM +clients+ WHERE (+clients+.+id+ IN (1,2)) 
     -------------------------------------------------------
     
    -[source,txt]
     -------------------------------------------------------
     >> Client.find(1,2)
     => [# "Ryan", locked: false, orders_count: 2, 
    @@ -81,7 +80,6 @@ Note that if you pass in a list of numbers that the result will be returned as a
     
     If you wanted to find the first client you would simply type +Client.first+ and that would find the first client created in your clients table:
     
    -[source,txt]
     -------------------------------------------------------
     >> Client.first
     => # "Ryan", locked: false, orders_count: 2, 
    @@ -99,7 +97,6 @@ Indicating the query that Rails has performed on your database.
     
     To find the last client you would simply type +Client.find(:last)+ and that would find the last client created in your clients table:
     
    -[source,txt]
     -------------------------------------------------------
     >> Client.find(:last)
     => # "Michael", locked: false, orders_count: 3, 
    @@ -113,7 +110,6 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
     
     To find all the clients you would simply type +Client.all+ and that would find all the clients in your clients table:
     
    -[source,txt]
     -------------------------------------------------------
     >> Client.all
     => [# "Ryan", locked: false, orders_count: 2, 
    @@ -192,7 +188,6 @@ SELECT * FROM +users+ WHERE (created_at IN
     
     This could possibly cause your database server to raise an unexpected error, for example MySQL will throw back this error:
     
    -[source, txt]
     -------------------------------------------------------
     Got a packet bigger than 'max_allowed_packet' bytes: _query_
     -------------------------------------------------------
    diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt
    index 7b0aeb0ed9..88ca74a557 100644
    --- a/railties/doc/guides/source/form_helpers.txt
    +++ b/railties/doc/guides/source/form_helpers.txt
    @@ -2,7 +2,7 @@ Rails form helpers
     ==================
     Mislav Marohnić 
     
    -Forms in web applications are an essential interface for user input. They are also often considered the most complex elements of HTML. Rails deals away with these complexities by providing numerous view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
    +Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
     
     In this guide we will:
     
    @@ -112,7 +112,7 @@ form_tag({:controller => "people", :action => "search"}, :method => "get")
     
     This is a common pitfall when using form helpers, since many of them accept multiple hashes. So in future, if a helper produces unexpected output, make sure that you have delimited the hash parameters properly.
     
    -WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an ugly `expecting tASSOC` syntax error.
    +WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an `expecting tASSOC` syntax error.
     
     Checkboxes, radio buttons and other controls
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -149,7 +149,7 @@ output:
       
     ----------------------------------------------------------------------------
     
    -IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option, while also providing a larger clickable region.
    +IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region.
     
     Other form controls we might mention are the text area, password input and hidden input:
     
    @@ -174,7 +174,7 @@ How do forms with PUT or DELETE methods work?
     
     Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). Still, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. How does this work, then?
     
    -Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"` that is set to reflect the _real_ method:
    +Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"` that is set to reflect the wanted method:
     
     ----------------------------------------------------------------------------
     form_tag(search_path, :method => "put")
    @@ -267,4 +267,79 @@ form_for(@article)
     
     Notice how the short-style `form_for` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`.
     
    -WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, `:url` and `:method` explicitly.
    \ No newline at end of file
    +WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, `:url` and `:method` explicitly.
    +
    +
    +Making select boxes with ease
    +-----------------------------
    +
    +Select boxes in HTML require a significant amount of markup (one `OPTION` element for each option to choose from), therefore it makes the most sense for them to be dynamically generated from data stored in arrays or hashes.
    +
    +Here is what our wanted markup might look like:
    +
    +----------------------------------------------------------------------------
    +
    +----------------------------------------------------------------------------
    +
    +Here we have a list of cities where their names are presented to the user, but internally we want to handle just their IDs so we keep them in value attributes. Let's see how Rails can help out here.
    +
    +The select tag and options
    +~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates the options:
    +
    +----------------------------------------------------------------------------
    +<%= select_tag(:city_id, '...') %>
    +----------------------------------------------------------------------------
    +
    +This is a start, but it doesn't dynamically create our option tags. We had to pass them in as a string.
    +
    +We can generate option tags with the `options_for_select` helper:
    +
    +----------------------------------------------------------------------------
    +<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %>
    +
    +output:
    +
    +
    +
    +...
    +----------------------------------------------------------------------------
    +
    +For input data we used a nested array where each element has two elements: visible value (name) and internal value (ID).
    +
    +Now you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup:
    +
    +----------------------------------------------------------------------------
    +<%= select_tag(:city_id, options_for_select(...)) %>
    +----------------------------------------------------------------------------
    +
    +Sometimes, depending on our application's needs, we also wish a specific option to be pre-selected. The `options_for_select` helper supports this with an optional second argument:
    +
    +----------------------------------------------------------------------------
    +<%= options_for_select(cities_array, 2) %>
    +
    +output:
    +
    +
    +
    +...
    +----------------------------------------------------------------------------
    +
    +So whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option.
    +
    +Select boxes for dealing with models
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Until now we've covered how to make generic select boxes, but in most cases our form controls will be tied to a specific database model. So, to continue from our previous examples, let's assume that we have a "Person" model with a `city_id` attribute.
    +
    +----------------------------------------------------------------------------
    +...
    +----------------------------------------------------------------------------
    +
    +...
    \ No newline at end of file
    diff --git a/railties/doc/guides/source/getting_started_with_rails.txt b/railties/doc/guides/source/getting_started_with_rails.txt
    index c5bbc9e814..f924d0793a 100644
    --- a/railties/doc/guides/source/getting_started_with_rails.txt
    +++ b/railties/doc/guides/source/getting_started_with_rails.txt
    @@ -751,6 +751,7 @@ At this point, it’s worth looking at some of the tools that Rails provides to
     As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a +partial+ template. This requires editing the new and edit views, and adding a new template:
     
     +new.html.erb+:
    +
     [source, ruby]
     -------------------------------------------------------
     

    New post

    @@ -761,6 +762,7 @@ As you saw earlier, the scaffold-generated views for the +new+ and +edit+ action ------------------------------------------------------- +edit.html.erb+: + [source, ruby] -------------------------------------------------------

    Editing post

    @@ -772,6 +774,7 @@ As you saw earlier, the scaffold-generated views for the +new+ and +edit+ action ------------------------------------------------------- +_form.html.erb+: + [source, ruby] ------------------------------------------------------- <% form_for(@post) do |f| %> @@ -979,12 +982,12 @@ $ script/generate controller Comments index show new edit This creates seven files: * +app/controllers/comments_controller.rb+ - The controller -* +app/helpers/comments_helper.rb - A view helper file -* +app/views/comments/index.html.erb - The view for the index action -* +app/views/comments/show.html.erb - The view for the show action -* +app/views/comments/new.html.erb - The view for the new action -* +app/views/comments/edit.html.erb - The view for the edit action -* +test/functional/comments_controller_test.rb - The functional tests for the controller +* +app/helpers/comments_helper.rb+ - A view helper file +* +app/views/comments/index.html.erb+ - The view for the index action +* +app/views/comments/show.html.erb+ - The view for the show action +* +app/views/comments/new.html.erb+ - The view for the new action +* +app/views/comments/edit.html.erb+ - The view for the edit action +* +test/functional/comments_controller_test.rb+ - The functional tests for the controller The controller will be generated with empty methods for each action that you specified in the call to +script/generate controller+: @@ -1216,15 +1219,21 @@ Note that each post has its own individual comments collection, accessible as +@ Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources: -* The [http://manuals.rubyonrails.org/]Ruby On Rails guides +* The link:http://manuals.rubyonrails.org/[Ruby On Rails guides] * The link:http://groups.google.com/group/rubyonrails-talk[Ruby on Rails mailing list] * The #rubyonrails channel on irc.freenode.net * The link:http://wiki.rubyonrails.org/rails[Rails wiki] +Rails also comes with built-in help that you can generate using the rake command-line utility: + +* Running +rake doc:guides+ will put a full copy of the Rails Guides in the +/doc/guides+ folder of your application. Open +/doc/guides/index.html+ in your web browser to explore the Guides. +* Running +rake doc:rails+ will put a full copy of the API documentation for Rails in the +/doc/api+ folder of your application. Open +/doc/api/index.html+ in your web browser to explore the API documentation. + == Changelog == http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2[Lighthouse ticket] +* November 3, 2008: Formatting patch from Dave Rothlisberger * November 1, 2008: First approved version by link:../authors.html#mgunderloy[Mike Gunderloy] * October 16, 2008: Revised based on feedback from Pratik Naik by link:../authors.html#mgunderloy[Mike Gunderloy] (not yet approved for publication) * October 13, 2008: First complete draft by link:../authors.html#mgunderloy[Mike Gunderloy] (not yet approved for publication) diff --git a/railties/doc/guides/source/index.txt b/railties/doc/guides/source/index.txt index 05d7deee6a..8828e1d313 100644 --- a/railties/doc/guides/source/index.txt +++ b/railties/doc/guides/source/index.txt @@ -42,7 +42,7 @@ This guide covers the find method defined in ActiveRecord::Base, as well as name .link:layouts_and_rendering.html[Layouts and Rendering in Rails] *********************************************************** This guide covers the basic layout features of Action Controller and Action View, -including rendering and redirecting, using +content_for_ blocks, and working +including rendering and redirecting, using +content_for+ blocks, and working with partials. *********************************************************** @@ -65,8 +65,6 @@ understand how to use routing in your own Rails applications, start here. .link:actioncontroller_basics.html[Basics of Action Controller] *********************************************************** -CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/17[Lighthouse Ticket] - This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics. *********************************************************** @@ -92,14 +90,12 @@ Enjoy. .link:security.html[Securing Rails Applications] *********************************************************** -This manual describes common security problems in web applications and how to +This guide describes common security problems in web applications and how to avoid them with Rails. *********************************************************** .link:debugging_rails_applications.html[Debugging Rails Applications] *********************************************************** -CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/5[Lighthouse Ticket] - This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code. diff --git a/railties/doc/guides/source/migrations/rakeing_around.txt b/railties/doc/guides/source/migrations/rakeing_around.txt index 1fcca0cf24..6d8c43d7a3 100644 --- a/railties/doc/guides/source/migrations/rakeing_around.txt +++ b/railties/doc/guides/source/migrations/rakeing_around.txt @@ -25,7 +25,7 @@ This will run the `down` method from the latest migration. If you need to undo s rake db:rollback STEP=3 ------------------ -will run the `down` method fron the last 3 migrations. +will run the `down` method from the last 3 migrations. The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating back up again. As with the `db:rollback` task you can use the `STEP` parameter if you need to go more than one version back, for example diff --git a/railties/doc/guides/source/testing_rails_applications.txt b/railties/doc/guides/source/testing_rails_applications.txt index dc7635eff9..31b6fc2cfa 100644 --- a/railties/doc/guides/source/testing_rails_applications.txt +++ b/railties/doc/guides/source/testing_rails_applications.txt @@ -89,7 +89,6 @@ Fixtures can also be described using the all-too-familiar comma-separated value A CSV fixture looks like this: -[source, log] -------------------------------------------------------------- id, username, password, stretchable, comments 1, sclaus, ihatekids, false, I like to say ""Ho! Ho! Ho!"" @@ -184,7 +183,6 @@ In Rails, unit tests are what you write to test your models. When you create a model using +script/generate+, among other things it creates a test stub in the +test/unit+ folder, as well as a fixture for the model: -[source, log] ------------------------------------------------------- $ script/generate model Post ... @@ -266,7 +264,6 @@ This will run all the test methods from the test case. You can also run a particular test method from the test case by using the +-n+ switch with the +test method name+. -[source, log] ------------------------------------------------------- $ ruby unit/post_test.rb -n test_truth @@ -292,7 +289,6 @@ end If you haven't added any data to the test fixture for posts, this test will fail. You can see this by running it: -[source, log] ------------------------------------------------------- $ ruby unit/post_test.rb Loaded suite unit/post_test @@ -322,7 +318,6 @@ end Running this test shows the friendlier assertion message: -[source, log] ------------------------------------------------------- $ ruby unit/post_test.rb Loaded suite unit/post_test @@ -354,7 +349,6 @@ end Now you can see even more output in the console from running the tests: -[source, log] ------------------------------------------------------- $ ruby unit/post_test.rb Loaded suite unit/post_test @@ -519,7 +513,7 @@ Another example: Calling the +:view+ action, passing an +id+ of 12 as the +param get(:view, {'id' => '12'}, nil, {'message' => 'booya!'}) -------------------------------------------------- -=== Available Request Types for Functional Tests=== +=== Available Request Types for Functional Tests === If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 5 request types supported in Rails functional tests: @@ -817,7 +811,6 @@ In this test, +@expected+ is an instance of +TMail::Mail+ that you can use in yo Here's the content of the +invite+ fixture: -[source, log] ------------------------------------------------- Hi friend@example.com, -- cgit v1.2.3 From 0832bc63f4047d0ad0171c411460db23450213aa Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 6 Nov 2008 01:29:09 +0530 Subject: Make sure ActiveRecord::Base.connected? doesn't raise an exception for defined connections --- .../active_record/connection_adapters/abstract/connection_pool.rb | 3 ++- activerecord/test/cases/pooled_connections_test.rb | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3016c329bd..54a17e20a9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -324,7 +324,8 @@ module ActiveRecord # Returns true if a connection that's accessible to this class has # already been opened. def connected?(klass) - conn = retrieve_connection_pool(klass) ? conn.connected? : false + conn = retrieve_connection_pool(klass) + conn ? conn.connected? : false end # Remove the connection for this class. This will close the active diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 3e8c617a89..36b45868b9 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -74,6 +74,11 @@ class PooledConnectionsTest < ActiveRecord::TestCase conn_pool.checkin(conn) end + def test_not_connected_defined_connection_reutnrs_false + ActiveRecord::Base.establish_connection(@connection) + assert ! ActiveRecord::Base.connected? + end + def test_undefined_connection_returns_false old_handler = ActiveRecord::Base.connection_handler ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new -- cgit v1.2.3 From 55707da1a14814ad84f138ee98e5b5fb1a745afc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 09:59:11 +0100 Subject: Dont bother logging the parameters hash if there are no parameters --- actionpack/lib/action_controller/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3a7f6c0f3c..43f6c1be44 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1241,7 +1241,7 @@ module ActionController #:nodoc: parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup parameters = parameters.except!(:controller, :action, :format, :_method) - logger.info " Parameters: #{parameters.inspect}" + logger.info " Parameters: #{parameters.inspect}" unless parameters.empty? end def default_render #:nodoc: -- cgit v1.2.3 From 8adb79b9b5983cda8dbdd4ef401661fbd51d8844 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 10:00:15 +0100 Subject: Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 for the MysqlAdapter as they only clutter up the log and offer no value [DHH] --- activerecord/CHANGELOG | 2 ++ .../lib/active_record/connection_adapters/mysql_adapter.rb | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4ca062b535..154d9f71c4 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 as they only clutter up the log and offer no value [DHH] + * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] * Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth] diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1e452ae88a..edf54026ff 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -305,8 +305,12 @@ module ActiveRecord rows end - def execute(sql, name = nil) #:nodoc: - log(sql, name) { @connection.query(sql) } + def execute(sql, name = nil, skip_logging = false) #:nodoc: + if skip_logging + @connection.query(sql) + else + log(sql, name) { @connection.query(sql) } + end rescue ActiveRecord::StatementInvalid => exception if exception.message.split(":").first =~ /Packets out of order/ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." @@ -437,7 +441,7 @@ module ActiveRecord def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } + execute(sql, name, true).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } columns end @@ -555,7 +559,7 @@ module ActiveRecord # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 - execute("SET SQL_AUTO_IS_NULL=0") + execute("SET SQL_AUTO_IS_NULL=0", "ID NULL OFF", true) end def select(sql, name = nil) -- cgit v1.2.3 From 077773257b682b7929e77ced3bbf46acf56a10c9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 10:00:15 +0100 Subject: Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 for the MysqlAdapter as they only clutter up the log and offer no value [DHH] --- activerecord/CHANGELOG | 2 ++ .../lib/active_record/connection_adapters/mysql_adapter.rb | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4ca062b535..154d9f71c4 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 as they only clutter up the log and offer no value [DHH] + * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] * Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth] diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1e452ae88a..edf54026ff 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -305,8 +305,12 @@ module ActiveRecord rows end - def execute(sql, name = nil) #:nodoc: - log(sql, name) { @connection.query(sql) } + def execute(sql, name = nil, skip_logging = false) #:nodoc: + if skip_logging + @connection.query(sql) + else + log(sql, name) { @connection.query(sql) } + end rescue ActiveRecord::StatementInvalid => exception if exception.message.split(":").first =~ /Packets out of order/ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." @@ -437,7 +441,7 @@ module ActiveRecord def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } + execute(sql, name, true).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } columns end @@ -555,7 +559,7 @@ module ActiveRecord # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 - execute("SET SQL_AUTO_IS_NULL=0") + execute("SET SQL_AUTO_IS_NULL=0", "ID NULL OFF", true) end def select(sql, name = nil) -- cgit v1.2.3 From a358d87e16fa876de29286b69474ab6aaee4a80b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 13:02:32 +0100 Subject: Fixed the sanitize helper to avoid double escaping already properly escaped entities [#683 state:committed] --- actionpack/CHANGELOG | 2 ++ .../lib/action_controller/vendor/html-scanner/html/sanitizer.rb | 2 +- actionpack/test/controller/html-scanner/sanitizer_test.rb | 4 ++++ activerecord/CHANGELOG | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 3ce6522535..e7d5031f1a 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 [antonmos/Ryan McGeary] + * Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov] * Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 [Bernardo Padua/Tor Erik] diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 12c8405101..ae20f9947c 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -160,7 +160,7 @@ module HTML if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value) node.attributes.delete(attr_name) else - node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(value) + node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value)) end end end diff --git a/actionpack/test/controller/html-scanner/sanitizer_test.rb b/actionpack/test/controller/html-scanner/sanitizer_test.rb index a9e8447e32..bae0f5c9fd 100644 --- a/actionpack/test/controller/html-scanner/sanitizer_test.rb +++ b/actionpack/test/controller/html-scanner/sanitizer_test.rb @@ -253,6 +253,10 @@ class SanitizerTest < Test::Unit::TestCase assert_sanitized "neverending...", "<![CDATA[<span>neverending...]]>" end + def test_should_not_mangle_urls_with_ampersand + assert_sanitized %{my link} + end + protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 154d9f71c4..290c0d785c 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,6 +1,6 @@ *2.2.1 [RC2 or 2.2 final]* -* Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 as they only clutter up the log and offer no value [DHH] +* Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 for the MysqlAdapter as they only clutter up the log and offer no value [DHH] * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] -- cgit v1.2.3 From 732c724df61bc8b780dc42817625b25a321908e4 Mon Sep 17 00:00:00 2001 From: Grant Hollingworth Date: Wed, 5 Nov 2008 22:54:37 -0500 Subject: Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it [#1336 state:committed] Signed-off-by: David Heinemeier Hansson --- actionmailer/CHANGELOG | 5 +++++ actionmailer/lib/action_mailer/base.rb | 6 ++++-- actionmailer/test/abstract_unit.rb | 8 ++++++-- actionmailer/test/mail_service_test.rb | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index d8636fd83d..4ae7b91327 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,3 +1,8 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth] + + *2.2.0 [RC1] (October 24th, 2008)* * Add layout functionality to mailers [Pratik] diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 043f56ba17..d63a608109 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -663,8 +663,10 @@ module ActionMailer #:nodoc: mail.ready_to_send sender = mail['return-path'] || mail.from - Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], - smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp| + smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port]) + smtp.enable_starttls_auto if smtp.respond_to?(:enable_starttls_auto) + smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password], + smtp_settings[:authentication]) do |smtp| smtp.sendmail(mail.encoded, sender, destinations) end end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 905f25c9f7..1617b88c8e 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -24,11 +24,15 @@ class MockSMTP def sendmail(mail, from, to) @@deliveries << [mail, from, to] end + + def start(*args) + yield self + end end class Net::SMTP - def self.start(*args) - yield MockSMTP.new + def self.new(*args) + MockSMTP.new end end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 7f9540c44b..f5cb372b2a 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -938,6 +938,20 @@ EOF mail = TestMailer.create_body_ivar(@recipient) assert_equal "body: foo\nbar: baz", mail.body end + + def test_starttls_is_enabled_if_supported + MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(true) + MockSMTP.any_instance.expects(:enable_starttls_auto) + ActionMailer::Base.delivery_method = :smtp + TestMailer.deliver_signed_up(@recipient) + end + + def test_starttls_is_disabled_if_not_supported + MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(false) + MockSMTP.any_instance.expects(:enable_starttls_auto).never + ActionMailer::Base.delivery_method = :smtp + TestMailer.deliver_signed_up(@recipient) + end end end # uses_mocha -- cgit v1.2.3 From af5b304a4002fe8ebeb8f31cd0a481dfa4a944db Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 6 Nov 2008 18:52:02 +0000 Subject: Fix stupid typo --- actionpack/lib/action_controller/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index a6d4abf029..c079895683 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -13,7 +13,7 @@ module ActionController ActiveSupport::Deprecation.warn( "ActionController::AbstractRequest.relative_url_root= has been renamed." + "You can now set it with config.action_controller.relative_url_root=", caller) - ActionController::base.relative_url_root=relative_url_root + ActionController::Base.relative_url_root=relative_url_root end HTTP_METHODS = %w(get head put post delete options) -- cgit v1.2.3 From 099f10679ec6d9ead9606cac2f843e854787db0c Mon Sep 17 00:00:00 2001 From: Aliaksey Kandratsenka Date: Sat, 1 Nov 2008 13:55:45 +0200 Subject: Don't eval recognize_optimized use __FILE__ and __LINE__ in the optimised recognition code. It produces meaningless line numbers. This also easily produces line numbers greater than recognition_optimization.rb have, which causes rcov to trash memory outside of it's coverage counting arrays. [#1319 state:committed] Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/routing/recognition_optimisation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb index 6c47ced6d1..3b98b16683 100644 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -148,7 +148,7 @@ module ActionController end nil end - }, __FILE__, __LINE__ + }, '(recognize_optimized)', 1 end def clear_recognize_optimized! -- cgit v1.2.3 From 4ccbc5dffb980edf35be899889f9e227dbd426c7 Mon Sep 17 00:00:00 2001 From: Luca Guidi Date: Mon, 3 Nov 2008 10:07:05 +0100 Subject: Increment the version of our modified memcache_client code to prevent users with the gem installed not seeing our changes. The changes will be submitted upstream. Signed-off-by: Michael Koziarski [#1239 state:committed] --- activesupport/lib/active_support/vendor.rb | 4 +- .../vendor/memcache-client-1.5.0/memcache.rb | 849 --------------------- .../vendor/memcache-client-1.5.1/memcache.rb | 849 +++++++++++++++++++++ activesupport/test/caching_test.rb | 6 + 4 files changed, 857 insertions(+), 851 deletions(-) delete mode 100644 activesupport/lib/active_support/vendor/memcache-client-1.5.0/memcache.rb create mode 100644 activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index 633eb2ef08..dc98936525 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -14,9 +14,9 @@ rescue Gem::LoadError end begin - gem 'memcache-client', '~> 1.5.0' + gem 'memcache-client', '~> 1.5.1' rescue Gem::LoadError - $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.0" + $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.1" end begin diff --git a/activesupport/lib/active_support/vendor/memcache-client-1.5.0/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.0/memcache.rb deleted file mode 100644 index 30113201a6..0000000000 --- a/activesupport/lib/active_support/vendor/memcache-client-1.5.0/memcache.rb +++ /dev/null @@ -1,849 +0,0 @@ -# All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel, -# The Robot Co-op. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the names of the authors nor the names of their contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -require 'socket' -require 'thread' -require 'timeout' -require 'rubygems' - -class String - - ## - # Uses the ITU-T polynomial in the CRC32 algorithm. - - def crc32_ITU_T - n = length - r = 0xFFFFFFFF - - n.times do |i| - r ^= self[i] - 8.times do - if (r & 1) != 0 then - r = (r>>1) ^ 0xEDB88320 - else - r >>= 1 - end - end - end - - r ^ 0xFFFFFFFF - end - -end - -## -# A Ruby client library for memcached. -# -# This is intended to provide access to basic memcached functionality. It -# does not attempt to be complete implementation of the entire API, but it is -# approaching a complete implementation. - -class MemCache - - ## - # The version of MemCache you are using. - - VERSION = '1.5.0' - - ## - # Default options for the cache object. - - DEFAULT_OPTIONS = { - :namespace => nil, - :readonly => false, - :multithread => false, - } - - ## - # Default memcached port. - - DEFAULT_PORT = 11211 - - ## - # Default memcached server weight. - - DEFAULT_WEIGHT = 1 - - ## - # The amount of time to wait for a response from a memcached server. If a - # response is not completed within this time, the connection to the server - # will be closed and an error will be raised. - - attr_accessor :request_timeout - - ## - # The namespace for this instance - - attr_reader :namespace - - ## - # The multithread setting for this instance - - attr_reader :multithread - - ## - # The servers this client talks to. Play at your own peril. - - attr_reader :servers - - ## - # Accepts a list of +servers+ and a list of +opts+. +servers+ may be - # omitted. See +servers=+ for acceptable server list arguments. - # - # Valid options for +opts+ are: - # - # [:namespace] Prepends this value to all keys added or retrieved. - # [:readonly] Raises an exception on cache writes when true. - # [:multithread] Wraps cache access in a Mutex for thread safety. - # - # Other options are ignored. - - def initialize(*args) - servers = [] - opts = {} - - case args.length - when 0 then # NOP - when 1 then - arg = args.shift - case arg - when Hash then opts = arg - when Array then servers = arg - when String then servers = [arg] - else raise ArgumentError, 'first argument must be Array, Hash or String' - end - when 2 then - servers, opts = args - else - raise ArgumentError, "wrong number of arguments (#{args.length} for 2)" - end - - opts = DEFAULT_OPTIONS.merge opts - @namespace = opts[:namespace] - @readonly = opts[:readonly] - @multithread = opts[:multithread] - @mutex = Mutex.new if @multithread - @buckets = [] - self.servers = servers - end - - ## - # Returns a string representation of the cache object. - - def inspect - "" % - [@servers.length, @buckets.length, @namespace, @readonly] - end - - ## - # Returns whether there is at least one active server for the object. - - def active? - not @servers.empty? - end - - ## - # Returns whether or not the cache object was created read only. - - def readonly? - @readonly - end - - ## - # Set the servers that the requests will be distributed between. Entries - # can be either strings of the form "hostname:port" or - # "hostname:port:weight" or MemCache::Server objects. - - def servers=(servers) - # Create the server objects. - @servers = servers.collect do |server| - case server - when String - host, port, weight = server.split ':', 3 - port ||= DEFAULT_PORT - weight ||= DEFAULT_WEIGHT - Server.new self, host, port, weight - when Server - if server.memcache.multithread != @multithread then - raise ArgumentError, "can't mix threaded and non-threaded servers" - end - server - else - raise TypeError, "cannot convert #{server.class} into MemCache::Server" - end - end - - # Create an array of server buckets for weight selection of servers. - @buckets = [] - @servers.each do |server| - server.weight.times { @buckets.push(server) } - end - end - - ## - # Decrements the value for +key+ by +amount+ and returns the new value. - # +key+ must already exist. If +key+ is not an integer, it is assumed to be - # 0. +key+ can not be decremented below 0. - - def decr(key, amount = 1) - server, cache_key = request_setup key - - if @multithread then - threadsafe_cache_decr server, cache_key, amount - else - cache_decr server, cache_key, amount - end - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Retrieves +key+ from memcache. If +raw+ is false, the value will be - # unmarshalled. - - def get(key, raw = false) - server, cache_key = request_setup key - - value = if @multithread then - threadsafe_cache_get server, cache_key - else - cache_get server, cache_key - end - - return nil if value.nil? - - value = Marshal.load value unless raw - - return value - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Retrieves multiple values from memcached in parallel, if possible. - # - # The memcached protocol supports the ability to retrieve multiple - # keys in a single request. Pass in an array of keys to this method - # and it will: - # - # 1. map the key to the appropriate memcached server - # 2. send a single request to each server that has one or more key values - # - # Returns a hash of values. - # - # cache["a"] = 1 - # cache["b"] = 2 - # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 } - - def get_multi(*keys) - raise MemCacheError, 'No active servers' unless active? - - keys.flatten! - key_count = keys.length - cache_keys = {} - server_keys = Hash.new { |h,k| h[k] = [] } - - # map keys to servers - keys.each do |key| - server, cache_key = request_setup key - cache_keys[cache_key] = key - server_keys[server] << cache_key - end - - results = {} - - server_keys.each do |server, keys_for_server| - keys_for_server = keys_for_server.join ' ' - values = if @multithread then - threadsafe_cache_get_multi server, keys_for_server - else - cache_get_multi server, keys_for_server - end - values.each do |key, value| - results[cache_keys[key]] = Marshal.load value - end - end - - return results - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Increments the value for +key+ by +amount+ and retruns the new value. - # +key+ must already exist. If +key+ is not an integer, it is assumed to be - # 0. - - def incr(key, amount = 1) - server, cache_key = request_setup key - - if @multithread then - threadsafe_cache_incr server, cache_key, amount - else - cache_incr server, cache_key, amount - end - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Add +key+ to the cache with value +value+ that expires in +expiry+ - # seconds. If +raw+ is true, +value+ will not be Marshalled. - # - # Warning: Readers should not call this method in the event of a cache miss; - # see MemCache#add. - - def set(key, value, expiry = 0, raw = false) - raise MemCacheError, "Update of readonly cache" if @readonly - server, cache_key = request_setup key - socket = server.socket - - value = Marshal.dump value unless raw - command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n" - - begin - @mutex.lock if @multithread - socket.write command - result = socket.gets - raise_on_error_response! result - result - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - ensure - @mutex.unlock if @multithread - end - end - - ## - # Add +key+ to the cache with value +value+ that expires in +expiry+ - # seconds, but only if +key+ does not already exist in the cache. - # If +raw+ is true, +value+ will not be Marshalled. - # - # Readers should call this method in the event of a cache miss, not - # MemCache#set or MemCache#[]=. - - def add(key, value, expiry = 0, raw = false) - raise MemCacheError, "Update of readonly cache" if @readonly - server, cache_key = request_setup key - socket = server.socket - - value = Marshal.dump value unless raw - command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n" - - begin - @mutex.lock if @multithread - socket.write command - result = socket.gets - raise_on_error_response! result - result - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - ensure - @mutex.unlock if @multithread - end - end - - ## - # Removes +key+ from the cache in +expiry+ seconds. - - def delete(key, expiry = 0) - @mutex.lock if @multithread - - raise MemCacheError, "No active servers" unless active? - cache_key = make_cache_key key - server = get_server_for_key cache_key - - sock = server.socket - raise MemCacheError, "No connection to server" if sock.nil? - - begin - sock.write "delete #{cache_key} #{expiry}\r\n" - result = sock.gets - raise_on_error_response! result - result - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - end - ensure - @mutex.unlock if @multithread - end - - ## - # Flush the cache from all memcache servers. - - def flush_all - raise MemCacheError, 'No active servers' unless active? - raise MemCacheError, "Update of readonly cache" if @readonly - begin - @mutex.lock if @multithread - @servers.each do |server| - begin - sock = server.socket - raise MemCacheError, "No connection to server" if sock.nil? - sock.write "flush_all\r\n" - result = sock.gets - raise_on_error_response! result - result - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - end - end - ensure - @mutex.unlock if @multithread - end - end - - ## - # Reset the connection to all memcache servers. This should be called if - # there is a problem with a cache lookup that might have left the connection - # in a corrupted state. - - def reset - @servers.each { |server| server.close } - end - - ## - # Returns statistics for each memcached server. An explanation of the - # statistics can be found in the memcached docs: - # - # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt - # - # Example: - # - # >> pp CACHE.stats - # {"localhost:11211"=> - # {"bytes"=>4718, - # "pid"=>20188, - # "connection_structures"=>4, - # "time"=>1162278121, - # "pointer_size"=>32, - # "limit_maxbytes"=>67108864, - # "cmd_get"=>14532, - # "version"=>"1.2.0", - # "bytes_written"=>432583, - # "cmd_set"=>32, - # "get_misses"=>0, - # "total_connections"=>19, - # "curr_connections"=>3, - # "curr_items"=>4, - # "uptime"=>1557, - # "get_hits"=>14532, - # "total_items"=>32, - # "rusage_system"=>0.313952, - # "rusage_user"=>0.119981, - # "bytes_read"=>190619}} - # => nil - - def stats - raise MemCacheError, "No active servers" unless active? - server_stats = {} - - @servers.each do |server| - sock = server.socket - raise MemCacheError, "No connection to server" if sock.nil? - - value = nil - begin - sock.write "stats\r\n" - stats = {} - while line = sock.gets do - raise_on_error_response! line - break if line == "END\r\n" - if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then - name, value = $1, $2 - stats[name] = case name - when 'version' - value - when 'rusage_user', 'rusage_system' then - seconds, microseconds = value.split(/:/, 2) - microseconds ||= 0 - Float(seconds) + (Float(microseconds) / 1_000_000) - else - if value =~ /\A\d+\Z/ then - value.to_i - else - value - end - end - end - end - server_stats["#{server.host}:#{server.port}"] = stats - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - end - end - - server_stats - end - - ## - # Shortcut to get a value from the cache. - - alias [] get - - ## - # Shortcut to save a value in the cache. This method does not set an - # expiration on the entry. Use set to specify an explicit expiry. - - def []=(key, value) - set key, value - end - - protected - - ## - # Create a key for the cache, incorporating the namespace qualifier if - # requested. - - def make_cache_key(key) - if namespace.nil? then - key - else - "#{@namespace}:#{key}" - end - end - - ## - # Pick a server to handle the request based on a hash of the key. - - def get_server_for_key(key) - raise ArgumentError, "illegal character in key #{key.inspect}" if - key =~ /\s/ - raise ArgumentError, "key too long #{key.inspect}" if key.length > 250 - raise MemCacheError, "No servers available" if @servers.empty? - return @servers.first if @servers.length == 1 - - hkey = hash_for key - - 20.times do |try| - server = @buckets[hkey % @buckets.nitems] - return server if server.alive? - hkey += hash_for "#{try}#{key}" - end - - raise MemCacheError, "No servers available" - end - - ## - # Returns an interoperable hash value for +key+. (I think, docs are - # sketchy for down servers). - - def hash_for(key) - (key.crc32_ITU_T >> 16) & 0x7fff - end - - ## - # Performs a raw decr for +cache_key+ from +server+. Returns nil if not - # found. - - def cache_decr(server, cache_key, amount) - socket = server.socket - socket.write "decr #{cache_key} #{amount}\r\n" - text = socket.gets - raise_on_error_response! text - return nil if text == "NOT_FOUND\r\n" - return text.to_i - end - - ## - # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache - # miss. - - def cache_get(server, cache_key) - socket = server.socket - socket.write "get #{cache_key}\r\n" - keyline = socket.gets # "VALUE \r\n" - - if keyline.nil? then - server.close - raise MemCacheError, "lost connection to #{server.host}:#{server.port}" - end - - raise_on_error_response! keyline - return nil if keyline == "END\r\n" - - unless keyline =~ /(\d+)\r/ then - server.close - raise MemCacheError, "unexpected response #{keyline.inspect}" - end - value = socket.read $1.to_i - socket.read 2 # "\r\n" - socket.gets # "END\r\n" - return value - end - - ## - # Fetches +cache_keys+ from +server+ using a multi-get. - - def cache_get_multi(server, cache_keys) - values = {} - socket = server.socket - socket.write "get #{cache_keys}\r\n" - - while keyline = socket.gets do - return values if keyline == "END\r\n" - raise_on_error_response! keyline - - unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then - server.close - raise MemCacheError, "unexpected response #{keyline.inspect}" - end - - key, data_length = $1, $3 - values[$1] = socket.read data_length.to_i - socket.read(2) # "\r\n" - end - - server.close - raise MemCacheError, "lost connection to #{server.host}:#{server.port}" - end - - ## - # Performs a raw incr for +cache_key+ from +server+. Returns nil if not - # found. - - def cache_incr(server, cache_key, amount) - socket = server.socket - socket.write "incr #{cache_key} #{amount}\r\n" - text = socket.gets - raise_on_error_response! text - return nil if text == "NOT_FOUND\r\n" - return text.to_i - end - - ## - # Handles +error+ from +server+. - - def handle_error(server, error) - server.close if server - new_error = MemCacheError.new error.message - new_error.set_backtrace error.backtrace - raise new_error - end - - ## - # Performs setup for making a request with +key+ from memcached. Returns - # the server to fetch the key from and the complete key to use. - - def request_setup(key) - raise MemCacheError, 'No active servers' unless active? - cache_key = make_cache_key key - server = get_server_for_key cache_key - raise MemCacheError, 'No connection to server' if server.socket.nil? - return server, cache_key - end - - def threadsafe_cache_decr(server, cache_key, amount) # :nodoc: - @mutex.lock - cache_decr server, cache_key, amount - ensure - @mutex.unlock - end - - def threadsafe_cache_get(server, cache_key) # :nodoc: - @mutex.lock - cache_get server, cache_key - ensure - @mutex.unlock - end - - def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc: - @mutex.lock - cache_get_multi socket, cache_keys - ensure - @mutex.unlock - end - - def threadsafe_cache_incr(server, cache_key, amount) # :nodoc: - @mutex.lock - cache_incr server, cache_key, amount - ensure - @mutex.unlock - end - - def raise_on_error_response!(response) - if response =~ /\A(?:CLIENT_|SERVER_)?ERROR (.*)/ - raise MemCacheError, $1.strip - end - end - - - ## - # This class represents a memcached server instance. - - class Server - - ## - # The amount of time to wait to establish a connection with a memcached - # server. If a connection cannot be established within this time limit, - # the server will be marked as down. - - CONNECT_TIMEOUT = 0.25 - - ## - # The amount of time to wait before attempting to re-establish a - # connection with a server that is marked dead. - - RETRY_DELAY = 30.0 - - ## - # The host the memcached server is running on. - - attr_reader :host - - ## - # The port the memcached server is listening on. - - attr_reader :port - - ## - # The weight given to the server. - - attr_reader :weight - - ## - # The time of next retry if the connection is dead. - - attr_reader :retry - - ## - # A text status string describing the state of the server. - - attr_reader :status - - ## - # Create a new MemCache::Server object for the memcached instance - # listening on the given host and port, weighted by the given weight. - - def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT) - raise ArgumentError, "No host specified" if host.nil? or host.empty? - raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero? - - @memcache = memcache - @host = host - @port = port.to_i - @weight = weight.to_i - - @multithread = @memcache.multithread - @mutex = Mutex.new - - @sock = nil - @retry = nil - @status = 'NOT CONNECTED' - end - - ## - # Return a string representation of the server object. - - def inspect - "" % [@host, @port, @weight, @status] - end - - ## - # Check whether the server connection is alive. This will cause the - # socket to attempt to connect if it isn't already connected and or if - # the server was previously marked as down and the retry time has - # been exceeded. - - def alive? - !!socket - end - - ## - # Try to connect to the memcached server targeted by this object. - # Returns the connected socket object on success or nil on failure. - - def socket - @mutex.lock if @multithread - return @sock if @sock and not @sock.closed? - - @sock = nil - - # If the host was dead, don't retry for a while. - return if @retry and @retry > Time.now - - # Attempt to connect if not already connected. - begin - @sock = timeout CONNECT_TIMEOUT do - TCPSocket.new @host, @port - end - if Socket.constants.include? 'TCP_NODELAY' then - @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 - end - @retry = nil - @status = 'CONNECTED' - rescue SocketError, SystemCallError, IOError, Timeout::Error => err - mark_dead err.message - end - - return @sock - ensure - @mutex.unlock if @multithread - end - - ## - # Close the connection to the memcached server targeted by this - # object. The server is not considered dead. - - def close - @mutex.lock if @multithread - @sock.close if @sock && !@sock.closed? - @sock = nil - @retry = nil - @status = "NOT CONNECTED" - ensure - @mutex.unlock if @multithread - end - - private - - ## - # Mark the server as dead and close its socket. - - def mark_dead(reason = "Unknown error") - @sock.close if @sock && !@sock.closed? - @sock = nil - @retry = Time.now + RETRY_DELAY - - @status = sprintf "DEAD: %s, will retry at %s", reason, @retry - end - end - - ## - # Base MemCache exception class. - - class MemCacheError < RuntimeError; end - -end - diff --git a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb new file mode 100644 index 0000000000..99c9af0398 --- /dev/null +++ b/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb @@ -0,0 +1,849 @@ +# All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel, +# The Robot Co-op. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the names of the authors nor the names of their contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +require 'socket' +require 'thread' +require 'timeout' +require 'rubygems' + +class String + + ## + # Uses the ITU-T polynomial in the CRC32 algorithm. + + def crc32_ITU_T + n = length + r = 0xFFFFFFFF + + n.times do |i| + r ^= self[i] + 8.times do + if (r & 1) != 0 then + r = (r>>1) ^ 0xEDB88320 + else + r >>= 1 + end + end + end + + r ^ 0xFFFFFFFF + end + +end + +## +# A Ruby client library for memcached. +# +# This is intended to provide access to basic memcached functionality. It +# does not attempt to be complete implementation of the entire API, but it is +# approaching a complete implementation. + +class MemCache + + ## + # The version of MemCache you are using. + + VERSION = '1.5.0' + + ## + # Default options for the cache object. + + DEFAULT_OPTIONS = { + :namespace => nil, + :readonly => false, + :multithread => false, + } + + ## + # Default memcached port. + + DEFAULT_PORT = 11211 + + ## + # Default memcached server weight. + + DEFAULT_WEIGHT = 1 + + ## + # The amount of time to wait for a response from a memcached server. If a + # response is not completed within this time, the connection to the server + # will be closed and an error will be raised. + + attr_accessor :request_timeout + + ## + # The namespace for this instance + + attr_reader :namespace + + ## + # The multithread setting for this instance + + attr_reader :multithread + + ## + # The servers this client talks to. Play at your own peril. + + attr_reader :servers + + ## + # Accepts a list of +servers+ and a list of +opts+. +servers+ may be + # omitted. See +servers=+ for acceptable server list arguments. + # + # Valid options for +opts+ are: + # + # [:namespace] Prepends this value to all keys added or retrieved. + # [:readonly] Raises an exception on cache writes when true. + # [:multithread] Wraps cache access in a Mutex for thread safety. + # + # Other options are ignored. + + def initialize(*args) + servers = [] + opts = {} + + case args.length + when 0 then # NOP + when 1 then + arg = args.shift + case arg + when Hash then opts = arg + when Array then servers = arg + when String then servers = [arg] + else raise ArgumentError, 'first argument must be Array, Hash or String' + end + when 2 then + servers, opts = args + else + raise ArgumentError, "wrong number of arguments (#{args.length} for 2)" + end + + opts = DEFAULT_OPTIONS.merge opts + @namespace = opts[:namespace] + @readonly = opts[:readonly] + @multithread = opts[:multithread] + @mutex = Mutex.new if @multithread + @buckets = [] + self.servers = servers + end + + ## + # Returns a string representation of the cache object. + + def inspect + "" % + [@servers.length, @buckets.length, @namespace, @readonly] + end + + ## + # Returns whether there is at least one active server for the object. + + def active? + not @servers.empty? + end + + ## + # Returns whether or not the cache object was created read only. + + def readonly? + @readonly + end + + ## + # Set the servers that the requests will be distributed between. Entries + # can be either strings of the form "hostname:port" or + # "hostname:port:weight" or MemCache::Server objects. + + def servers=(servers) + # Create the server objects. + @servers = servers.collect do |server| + case server + when String + host, port, weight = server.split ':', 3 + port ||= DEFAULT_PORT + weight ||= DEFAULT_WEIGHT + Server.new self, host, port, weight + when Server + if server.memcache.multithread != @multithread then + raise ArgumentError, "can't mix threaded and non-threaded servers" + end + server + else + raise TypeError, "cannot convert #{server.class} into MemCache::Server" + end + end + + # Create an array of server buckets for weight selection of servers. + @buckets = [] + @servers.each do |server| + server.weight.times { @buckets.push(server) } + end + end + + ## + # Decrements the value for +key+ by +amount+ and returns the new value. + # +key+ must already exist. If +key+ is not an integer, it is assumed to be + # 0. +key+ can not be decremented below 0. + + def decr(key, amount = 1) + server, cache_key = request_setup key + + if @multithread then + threadsafe_cache_decr server, cache_key, amount + else + cache_decr server, cache_key, amount + end + rescue TypeError, SocketError, SystemCallError, IOError => err + handle_error server, err + end + + ## + # Retrieves +key+ from memcache. If +raw+ is false, the value will be + # unmarshalled. + + def get(key, raw = false) + server, cache_key = request_setup key + + value = if @multithread then + threadsafe_cache_get server, cache_key + else + cache_get server, cache_key + end + + return nil if value.nil? + + value = Marshal.load value unless raw + + return value + rescue TypeError, SocketError, SystemCallError, IOError => err + handle_error server, err + end + + ## + # Retrieves multiple values from memcached in parallel, if possible. + # + # The memcached protocol supports the ability to retrieve multiple + # keys in a single request. Pass in an array of keys to this method + # and it will: + # + # 1. map the key to the appropriate memcached server + # 2. send a single request to each server that has one or more key values + # + # Returns a hash of values. + # + # cache["a"] = 1 + # cache["b"] = 2 + # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 } + + def get_multi(*keys) + raise MemCacheError, 'No active servers' unless active? + + keys.flatten! + key_count = keys.length + cache_keys = {} + server_keys = Hash.new { |h,k| h[k] = [] } + + # map keys to servers + keys.each do |key| + server, cache_key = request_setup key + cache_keys[cache_key] = key + server_keys[server] << cache_key + end + + results = {} + + server_keys.each do |server, keys_for_server| + keys_for_server = keys_for_server.join ' ' + values = if @multithread then + threadsafe_cache_get_multi server, keys_for_server + else + cache_get_multi server, keys_for_server + end + values.each do |key, value| + results[cache_keys[key]] = Marshal.load value + end + end + + return results + rescue TypeError, SocketError, SystemCallError, IOError => err + handle_error server, err + end + + ## + # Increments the value for +key+ by +amount+ and retruns the new value. + # +key+ must already exist. If +key+ is not an integer, it is assumed to be + # 0. + + def incr(key, amount = 1) + server, cache_key = request_setup key + + if @multithread then + threadsafe_cache_incr server, cache_key, amount + else + cache_incr server, cache_key, amount + end + rescue TypeError, SocketError, SystemCallError, IOError => err + handle_error server, err + end + + ## + # Add +key+ to the cache with value +value+ that expires in +expiry+ + # seconds. If +raw+ is true, +value+ will not be Marshalled. + # + # Warning: Readers should not call this method in the event of a cache miss; + # see MemCache#add. + + def set(key, value, expiry = 0, raw = false) + raise MemCacheError, "Update of readonly cache" if @readonly + server, cache_key = request_setup key + socket = server.socket + + value = Marshal.dump value unless raw + command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n" + + begin + @mutex.lock if @multithread + socket.write command + result = socket.gets + raise_on_error_response! result + result + rescue SocketError, SystemCallError, IOError => err + server.close + raise MemCacheError, err.message + ensure + @mutex.unlock if @multithread + end + end + + ## + # Add +key+ to the cache with value +value+ that expires in +expiry+ + # seconds, but only if +key+ does not already exist in the cache. + # If +raw+ is true, +value+ will not be Marshalled. + # + # Readers should call this method in the event of a cache miss, not + # MemCache#set or MemCache#[]=. + + def add(key, value, expiry = 0, raw = false) + raise MemCacheError, "Update of readonly cache" if @readonly + server, cache_key = request_setup key + socket = server.socket + + value = Marshal.dump value unless raw + command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n" + + begin + @mutex.lock if @multithread + socket.write command + result = socket.gets + raise_on_error_response! result + result + rescue SocketError, SystemCallError, IOError => err + server.close + raise MemCacheError, err.message + ensure + @mutex.unlock if @multithread + end + end + + ## + # Removes +key+ from the cache in +expiry+ seconds. + + def delete(key, expiry = 0) + @mutex.lock if @multithread + + raise MemCacheError, "No active servers" unless active? + cache_key = make_cache_key key + server = get_server_for_key cache_key + + sock = server.socket + raise MemCacheError, "No connection to server" if sock.nil? + + begin + sock.write "delete #{cache_key} #{expiry}\r\n" + result = sock.gets + raise_on_error_response! result + result + rescue SocketError, SystemCallError, IOError => err + server.close + raise MemCacheError, err.message + end + ensure + @mutex.unlock if @multithread + end + + ## + # Flush the cache from all memcache servers. + + def flush_all + raise MemCacheError, 'No active servers' unless active? + raise MemCacheError, "Update of readonly cache" if @readonly + begin + @mutex.lock if @multithread + @servers.each do |server| + begin + sock = server.socket + raise MemCacheError, "No connection to server" if sock.nil? + sock.write "flush_all\r\n" + result = sock.gets + raise_on_error_response! result + result + rescue SocketError, SystemCallError, IOError => err + server.close + raise MemCacheError, err.message + end + end + ensure + @mutex.unlock if @multithread + end + end + + ## + # Reset the connection to all memcache servers. This should be called if + # there is a problem with a cache lookup that might have left the connection + # in a corrupted state. + + def reset + @servers.each { |server| server.close } + end + + ## + # Returns statistics for each memcached server. An explanation of the + # statistics can be found in the memcached docs: + # + # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt + # + # Example: + # + # >> pp CACHE.stats + # {"localhost:11211"=> + # {"bytes"=>4718, + # "pid"=>20188, + # "connection_structures"=>4, + # "time"=>1162278121, + # "pointer_size"=>32, + # "limit_maxbytes"=>67108864, + # "cmd_get"=>14532, + # "version"=>"1.2.0", + # "bytes_written"=>432583, + # "cmd_set"=>32, + # "get_misses"=>0, + # "total_connections"=>19, + # "curr_connections"=>3, + # "curr_items"=>4, + # "uptime"=>1557, + # "get_hits"=>14532, + # "total_items"=>32, + # "rusage_system"=>0.313952, + # "rusage_user"=>0.119981, + # "bytes_read"=>190619}} + # => nil + + def stats + raise MemCacheError, "No active servers" unless active? + server_stats = {} + + @servers.each do |server| + sock = server.socket + raise MemCacheError, "No connection to server" if sock.nil? + + value = nil + begin + sock.write "stats\r\n" + stats = {} + while line = sock.gets do + raise_on_error_response! line + break if line == "END\r\n" + if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then + name, value = $1, $2 + stats[name] = case name + when 'version' + value + when 'rusage_user', 'rusage_system' then + seconds, microseconds = value.split(/:/, 2) + microseconds ||= 0 + Float(seconds) + (Float(microseconds) / 1_000_000) + else + if value =~ /\A\d+\Z/ then + value.to_i + else + value + end + end + end + end + server_stats["#{server.host}:#{server.port}"] = stats + rescue SocketError, SystemCallError, IOError => err + server.close + raise MemCacheError, err.message + end + end + + server_stats + end + + ## + # Shortcut to get a value from the cache. + + alias [] get + + ## + # Shortcut to save a value in the cache. This method does not set an + # expiration on the entry. Use set to specify an explicit expiry. + + def []=(key, value) + set key, value + end + + protected + + ## + # Create a key for the cache, incorporating the namespace qualifier if + # requested. + + def make_cache_key(key) + if namespace.nil? then + key + else + "#{@namespace}:#{key}" + end + end + + ## + # Pick a server to handle the request based on a hash of the key. + + def get_server_for_key(key) + raise ArgumentError, "illegal character in key #{key.inspect}" if + key =~ /\s/ + raise ArgumentError, "key too long #{key.inspect}" if key.length > 250 + raise MemCacheError, "No servers available" if @servers.empty? + return @servers.first if @servers.length == 1 + + hkey = hash_for key + + 20.times do |try| + server = @buckets[hkey % @buckets.nitems] + return server if server.alive? + hkey += hash_for "#{try}#{key}" + end + + raise MemCacheError, "No servers available" + end + + ## + # Returns an interoperable hash value for +key+. (I think, docs are + # sketchy for down servers). + + def hash_for(key) + (key.crc32_ITU_T >> 16) & 0x7fff + end + + ## + # Performs a raw decr for +cache_key+ from +server+. Returns nil if not + # found. + + def cache_decr(server, cache_key, amount) + socket = server.socket + socket.write "decr #{cache_key} #{amount}\r\n" + text = socket.gets + raise_on_error_response! text + return nil if text == "NOT_FOUND\r\n" + return text.to_i + end + + ## + # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache + # miss. + + def cache_get(server, cache_key) + socket = server.socket + socket.write "get #{cache_key}\r\n" + keyline = socket.gets # "VALUE \r\n" + + if keyline.nil? then + server.close + raise MemCacheError, "lost connection to #{server.host}:#{server.port}" + end + + raise_on_error_response! keyline + return nil if keyline == "END\r\n" + + unless keyline =~ /(\d+)\r/ then + server.close + raise MemCacheError, "unexpected response #{keyline.inspect}" + end + value = socket.read $1.to_i + socket.read 2 # "\r\n" + socket.gets # "END\r\n" + return value + end + + ## + # Fetches +cache_keys+ from +server+ using a multi-get. + + def cache_get_multi(server, cache_keys) + values = {} + socket = server.socket + socket.write "get #{cache_keys}\r\n" + + while keyline = socket.gets do + return values if keyline == "END\r\n" + raise_on_error_response! keyline + + unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then + server.close + raise MemCacheError, "unexpected response #{keyline.inspect}" + end + + key, data_length = $1, $3 + values[$1] = socket.read data_length.to_i + socket.read(2) # "\r\n" + end + + server.close + raise MemCacheError, "lost connection to #{server.host}:#{server.port}" + end + + ## + # Performs a raw incr for +cache_key+ from +server+. Returns nil if not + # found. + + def cache_incr(server, cache_key, amount) + socket = server.socket + socket.write "incr #{cache_key} #{amount}\r\n" + text = socket.gets + raise_on_error_response! text + return nil if text == "NOT_FOUND\r\n" + return text.to_i + end + + ## + # Handles +error+ from +server+. + + def handle_error(server, error) + server.close if server + new_error = MemCacheError.new error.message + new_error.set_backtrace error.backtrace + raise new_error + end + + ## + # Performs setup for making a request with +key+ from memcached. Returns + # the server to fetch the key from and the complete key to use. + + def request_setup(key) + raise MemCacheError, 'No active servers' unless active? + cache_key = make_cache_key key + server = get_server_for_key cache_key + raise MemCacheError, 'No connection to server' if server.socket.nil? + return server, cache_key + end + + def threadsafe_cache_decr(server, cache_key, amount) # :nodoc: + @mutex.lock + cache_decr server, cache_key, amount + ensure + @mutex.unlock + end + + def threadsafe_cache_get(server, cache_key) # :nodoc: + @mutex.lock + cache_get server, cache_key + ensure + @mutex.unlock + end + + def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc: + @mutex.lock + cache_get_multi socket, cache_keys + ensure + @mutex.unlock + end + + def threadsafe_cache_incr(server, cache_key, amount) # :nodoc: + @mutex.lock + cache_incr server, cache_key, amount + ensure + @mutex.unlock + end + + def raise_on_error_response!(response) + if response =~ /\A(?:CLIENT_|SERVER_)?ERROR (.*)/ + raise MemCacheError, $1.strip + end + end + + + ## + # This class represents a memcached server instance. + + class Server + + ## + # The amount of time to wait to establish a connection with a memcached + # server. If a connection cannot be established within this time limit, + # the server will be marked as down. + + CONNECT_TIMEOUT = 0.25 + + ## + # The amount of time to wait before attempting to re-establish a + # connection with a server that is marked dead. + + RETRY_DELAY = 30.0 + + ## + # The host the memcached server is running on. + + attr_reader :host + + ## + # The port the memcached server is listening on. + + attr_reader :port + + ## + # The weight given to the server. + + attr_reader :weight + + ## + # The time of next retry if the connection is dead. + + attr_reader :retry + + ## + # A text status string describing the state of the server. + + attr_reader :status + + ## + # Create a new MemCache::Server object for the memcached instance + # listening on the given host and port, weighted by the given weight. + + def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT) + raise ArgumentError, "No host specified" if host.nil? or host.empty? + raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero? + + @memcache = memcache + @host = host + @port = port.to_i + @weight = weight.to_i + + @multithread = @memcache.multithread + @mutex = Mutex.new + + @sock = nil + @retry = nil + @status = 'NOT CONNECTED' + end + + ## + # Return a string representation of the server object. + + def inspect + "" % [@host, @port, @weight, @status] + end + + ## + # Check whether the server connection is alive. This will cause the + # socket to attempt to connect if it isn't already connected and or if + # the server was previously marked as down and the retry time has + # been exceeded. + + def alive? + !!socket + end + + ## + # Try to connect to the memcached server targeted by this object. + # Returns the connected socket object on success or nil on failure. + + def socket + @mutex.lock if @multithread + return @sock if @sock and not @sock.closed? + + @sock = nil + + # If the host was dead, don't retry for a while. + return if @retry and @retry > Time.now + + # Attempt to connect if not already connected. + begin + @sock = timeout CONNECT_TIMEOUT do + TCPSocket.new @host, @port + end + if Socket.constants.include? 'TCP_NODELAY' then + @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 + end + @retry = nil + @status = 'CONNECTED' + rescue SocketError, SystemCallError, IOError, Timeout::Error => err + mark_dead err.message + end + + return @sock + ensure + @mutex.unlock if @multithread + end + + ## + # Close the connection to the memcached server targeted by this + # object. The server is not considered dead. + + def close + @mutex.lock if @multithread + @sock.close if @sock && !@sock.closed? + @sock = nil + @retry = nil + @status = "NOT CONNECTED" + ensure + @mutex.unlock if @multithread + end + + private + + ## + # Mark the server as dead and close its socket. + + def mark_dead(reason = "Unknown error") + @sock.close if @sock && !@sock.closed? + @sock = nil + @retry = Time.now + RETRY_DELAY + + @status = sprintf "DEAD: %s, will retry at %s", reason, @retry + end + end + + ## + # Base MemCache exception class. + + class MemCacheError < RuntimeError; end + +end + diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index cc371b3a7b..e7dac4cc6b 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -160,6 +160,12 @@ uses_memcached 'memcached backed store' do @cache.read('foo').gsub!(/.*/, 'baz') assert_equal 'bar', @cache.read('foo') end + + def test_write_should_return_true_on_success + result = @cache.write('foo', 'bar') + assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written + assert result + end end class CompressedMemCacheStore < Test::Unit::TestCase -- cgit v1.2.3 From 77697e03353ec06a4b12f42a32d7569797d1eb8f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 6 Nov 2008 17:10:16 -0600 Subject: Fix memory leak issue in ActiveRecord scoped_methods --- activerecord/lib/active_record/base.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index a36a137f0d..757102eb6b 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2023,8 +2023,7 @@ module ActiveRecord #:nodoc: end def scoped_methods #:nodoc: - scoped_methods = (Thread.current[:scoped_methods] ||= {}) - scoped_methods[self] ||= [] + Thread.current[:"#{self}_scoped_methods"] ||= [] end def current_scoped_methods #:nodoc: -- cgit v1.2.3 From b5291ed0f1e956cc98e1bfc8e50ee9fb4f6a661b Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 01:01:04 -0500 Subject: Mark utf-8 encoding --- activesupport/lib/active_support/inflector.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 1ccfec4000..ba52e41c08 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'singleton' require 'iconv' -- cgit v1.2.3 From 66d4b558993ab9a92303d905fea6a01800dd0474 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 01:08:21 -0500 Subject: Fix indentation mismatch --- activesupport/test/core_ext/time_ext_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 11ec1c6cd6..fd17f7a812 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -123,7 +123,7 @@ class TimeExtCalculationsTest < Test::Unit::TestCase def test_end_of_year assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,2,22,10,10,10).end_of_year assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,31,10,10,10).end_of_year - end + end def test_beginning_of_year assert_equal Time.local(2005,1,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_year -- cgit v1.2.3 From 983dc8078708fff5d99fc31eb5eac8b532e950b3 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 01:08:59 -0500 Subject: Don't shadow local with black arg --- activesupport/lib/active_support/rescuable.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index f2bc12e832..c27c4ddb1a 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -78,7 +78,7 @@ module ActiveSupport def handler_for_rescue(exception) # We go from right to left because pairs are pushed onto rescue_handlers # as rescue_from declarations are found. - _, handler = Array(rescue_handlers).reverse.detect do |klass_name, handler| + _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| # The purpose of allowing strings in rescue_from is to support the # declaration of handler associations for exception classes whose # definition is yet unknown. @@ -97,11 +97,11 @@ module ActiveSupport exception.is_a?(klass) if klass end - case handler + case rescuer when Symbol - method(handler) + method(rescuer) when Proc - handler.bind(self) + rescuer.bind(self) end end end -- cgit v1.2.3 From 9d4337ea13be371fd3fbf3ca8ba467e810888c37 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Fri, 7 Nov 2008 07:31:01 +0000 Subject: Revert commit which breaks all the tests. This reverts commit 8adb79b9b5983cda8dbdd4ef401661fbd51d8844. Conflicts: activerecord/CHANGELOG --- activerecord/CHANGELOG | 2 -- .../lib/active_record/connection_adapters/mysql_adapter.rb | 12 ++++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 290c0d785c..4ca062b535 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,7 +1,5 @@ *2.2.1 [RC2 or 2.2 final]* -* Stop logging SHOW FIELDS and SET SQL_AUTO_IS_NULL=0 for the MysqlAdapter as they only clutter up the log and offer no value [DHH] - * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] * Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth] diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index edf54026ff..1e452ae88a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -305,12 +305,8 @@ module ActiveRecord rows end - def execute(sql, name = nil, skip_logging = false) #:nodoc: - if skip_logging - @connection.query(sql) - else - log(sql, name) { @connection.query(sql) } - end + def execute(sql, name = nil) #:nodoc: + log(sql, name) { @connection.query(sql) } rescue ActiveRecord::StatementInvalid => exception if exception.message.split(":").first =~ /Packets out of order/ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." @@ -441,7 +437,7 @@ module ActiveRecord def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - execute(sql, name, true).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } + execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } columns end @@ -559,7 +555,7 @@ module ActiveRecord # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 - execute("SET SQL_AUTO_IS_NULL=0", "ID NULL OFF", true) + execute("SET SQL_AUTO_IS_NULL=0") end def select(sql, name = nil) -- cgit v1.2.3 From 26978e3ce84eda4e659fe9724e89c51efdd01781 Mon Sep 17 00:00:00 2001 From: Tekin Suleyman Date: Thu, 6 Nov 2008 20:00:54 +0000 Subject: Added :counter_sql as a valid key for habtm associations Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index c7cb6eb966..7f7819115c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1609,7 +1609,7 @@ module ActiveRecord :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, :select, :conditions, :include, :order, :group, :limit, :offset, :uniq, - :finder_sql, :delete_sql, :insert_sql, + :finder_sql, :counter_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, :extend, :readonly, :validate -- cgit v1.2.3 From 32a5cfcd7f8d14407f0c00ce2cdc82b1b568438e Mon Sep 17 00:00:00 2001 From: Tekin Suleyman Date: Thu, 6 Nov 2008 21:06:40 +0000 Subject: Added tests for HABTM associations with counter_sql Signed-off-by: Michael Koziarski [#1102 state:committed] --- .../has_and_belongs_to_many_associations_test.rb | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 2949f1d304..b5bedf3704 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -68,6 +68,16 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base :foreign_key => "developer_id" end +class DeveloperWithCounterSQL < ActiveRecord::Base + set_table_name 'developers' + has_and_belongs_to_many :projects, + :class_name => "DeveloperWithCounterSQL", + :join_table => "developers_projects", + :association_foreign_key => "project_id", + :foreign_key => "developer_id", + :counter_sql => 'SELECT COUNT(*) AS count_all FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE developers_projects.developer_id =#{id}' +end + class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings @@ -739,6 +749,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') } end + def test_count + david = Developer.find(1) + assert_equal 2, david.projects.count + end + + def test_count_with_counter_sql + developer = DeveloperWithCounterSQL.create(:name => 'tekin') + developer.project_ids = [projects(:active_record).id] + developer.save + developer.reload + assert_equal 1, developer.projects.count + end + uses_mocha 'mocking Post.transaction' do def test_association_proxy_transaction_method_starts_transaction_in_association_class Post.expects(:transaction) -- cgit v1.2.3 From d3ec1d3c22904c8801945b56956466f8ead9f3c1 Mon Sep 17 00:00:00 2001 From: Rich Manalang Date: Thu, 6 Nov 2008 20:02:32 -0800 Subject: auto_link view helper was failing on URLs with colons after a query param Signed-off-by: Michael Koziarski [#1341 state:committed] --- actionpack/lib/action_view/helpers/text_helper.rb | 4 ++-- actionpack/test/template/text_helper_test.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index d80e7c6e57..36f7575652 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -559,7 +559,7 @@ module ActionView (?:\.[-\w]+)* # remaining subdomains or domain (?::\d+)? # port (?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path - (?:\?[\w\+@%&=.;-]+)? # query string + (?:\?[\w\+@%&=.;:-]+)? # query string (?:\#[\w\-]*)? # trailing anchor ) ([[:punct:]]|<|$|) # trailing text @@ -598,4 +598,4 @@ module ActionView end end end -end \ No newline at end of file +end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 5f9f715819..095c952d67 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -221,6 +221,7 @@ class TextHelperTest < ActionView::TestCase http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 http://en.wikipedia.org/wiki/Sprite_(computer_graphics) http://en.wikipedia.org/wiki/Texas_hold'em + https://www.google.com/doku.php?id=gps:resource:scs:start ) urls.each do |url| -- cgit v1.2.3 From 529c2716992490a6eab55486788ca0d35c17e60b Mon Sep 17 00:00:00 2001 From: Nick Sieger Date: Sat, 8 Nov 2008 03:49:25 +0530 Subject: Simplify dispatcher callbacks to eliminate unnecessary stale thread purging. [Nick Sieger, Pratik Naik] Signed-off-by: Pratik Naik --- actionpack/lib/action_controller/dispatcher.rb | 1 - .../active_record/connection_adapters/abstract/connection_pool.rb | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index f3e173004a..2d5e80f0bb 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -23,7 +23,6 @@ module ActionController if defined?(ActiveRecord) after_dispatch :checkin_connections - before_dispatch { ActiveRecord::Base.verify_active_connections! } to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers } end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 54a17e20a9..cf760e334e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -292,10 +292,7 @@ module ActiveRecord # and also returns connections to the pool cached by threads that are no # longer alive. def clear_active_connections! - @connection_pools.each_value do |pool| - pool.release_connection - pool.clear_stale_cached_connections! - end + @connection_pools.each_value {|pool| pool.release_connection } end # Clears the cache which maps classes -- cgit v1.2.3 From d20955f889223b6035dbc7d61acba9091bf7b7ed Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Nov 2008 03:56:52 +0530 Subject: Don't leave open dangling connections in development mode. [#1335 state:resolved] --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c5183357a1..f8fa969dc3 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -125,9 +125,8 @@ module ActiveRecord end # Returns true if its safe to reload the connection between requests for development mode. - # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite. def requires_reloading? - false + true end # Checks whether the connection to the database is still active (i.e. not stale). -- cgit v1.2.3 From aaa2abf73fa39e0d455b4b781fb4d00e51d0bdc7 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:44:31 -0500 Subject: Use delete if the rhs is nil --- actionpack/test/template/asset_tag_helper_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index bade96fe17..1a3a6e86fa 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -239,7 +239,11 @@ class AssetTagHelperTest < ActionView::TestCase File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true) assert_equal 'Rails', image_tag('rails.png') ensure - ENV["RAILS_ASSET_ID"] = old_asset_id + if old_asset_id + ENV["RAILS_ASSET_ID"] = old_asset_id + else + ENV.delete("RAILS_ASSET_ID") + end end end -- cgit v1.2.3 From 07fe3370f8abe49b4c055d4eb8c39f1e73847eea Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:46:03 -0500 Subject: Check whether last arg is a Hash instead of duck-typing against [] --- actionpack/lib/action_view/helpers/atom_feed_helper.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index ccb7df212a..cd25684940 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -1,3 +1,5 @@ +require 'set' + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other # template languages). module ActionView @@ -121,6 +123,8 @@ module ActionView end class AtomBuilder + XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set + def initialize(xml) @xml = xml end @@ -140,14 +144,15 @@ module ActionView @xml.__send__(method, *arguments, &block) end end - + # True if the method name matches one of the five elements defined # in the Atom spec as potentially containing XHTML content and # if :type => 'xhtml' is, in fact, specified. def xhtml_block?(method, arguments) - %w( content rights title subtitle summary ).include?(method.to_s) && - arguments.last.respond_to?(:[]) && - arguments.last[:type].to_s == 'xhtml' + if XHTML_TAG_NAMES.include?(method.to_s) + last = arguments.last + last.is_a?(Hash) && last[:type].to_s == 'xhtml' + end end end -- cgit v1.2.3 From 425382d95fbc93efde52c7a1c369f3fbcba70b2e Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:45:10 -0500 Subject: Don't worry about attribute ordering --- actionpack/test/template/atom_feed_helper_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 9247a42d33..06af8d1d6a 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -255,7 +255,8 @@ class AtomFeedTest < Test::Unit::TestCase def test_feed_xml_processing_instructions with_restful_routing(:scrolls) do get :index, :id => 'feed_with_xml_processing_instructions' - assert_match %r{<\?xml-stylesheet type="text/css" href="t.css"\?>}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body end end -- cgit v1.2.3 From a7f920f674d234f281d2491ebe6d74710a79e663 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Fri, 7 Nov 2008 20:39:06 -0600 Subject: If average value from DB is 0, make sure to convert it to a 0.0 float before calling #to_d on it [#1346 state:resolved] Signed-off-by: Joshua Peek --- activerecord/lib/active_record/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 5e33cf1bd4..dd90580b3d 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -286,7 +286,7 @@ module ActiveRecord case operation when 'count' then value.to_i when 'sum' then type_cast_using_column(value || '0', column) - when 'avg' then value && value.to_d + when 'avg' then value && value.to_f.to_d else type_cast_using_column(value, column) end end -- cgit v1.2.3 From 0be5bc3f5981f11d81c24ccfb97863a69406cf9d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 21:50:39 -0500 Subject: Work around ruby 1.9 segfault --- actionpack/test/controller/session/cookie_store_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 010c00fa14..30422314a1 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -266,6 +266,7 @@ class CookieStoreTest < Test::Unit::TestCase @options = self.class.default_session_options.merge(options) session = CGI::Session.new(cgi, @options) + ObjectSpace.undefine_finalizer(session) assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}" assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}" -- cgit v1.2.3 From dd77733f2fdb6dde2be7115fc31ad1dcbfccb5a1 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 00:24:36 -0500 Subject: Timeout the connection pool monitor on ruby 1.8 only --- .../connection_adapters/abstract/connection_pool.rb | 14 +++++++++++--- activerecord/test/cases/pooled_connections_test.rb | 11 +++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index cf760e334e..901b17124c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -65,15 +65,23 @@ module ActiveRecord # The default ConnectionPool maximum size is 5. def initialize(spec) @spec = spec + # The cache of reserved connections mapped to threads @reserved_connections = {} + # The mutex used to synchronize pool access @connection_mutex = Monitor.new @queue = @connection_mutex.new_cond - # default 5 second timeout - @timeout = spec.config[:wait_timeout] || 5 + + # default 5 second timeout unless on ruby 1.9 + @timeout = + if RUBY_VERSION < '1.9' + spec.config[:wait_timeout] || 5 + end + # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 + @connections = [] @checked_out = [] end @@ -187,7 +195,7 @@ module ActiveRecord # try looting dead threads clear_stale_cached_connections! if @size == @checked_out.size - raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?" + raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it." end end end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 36b45868b9..2a5e9509b3 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -28,10 +28,13 @@ class PooledConnectionsTest < ActiveRecord::TestCase end end - def test_pooled_connection_checkout - checkout_connections - assert_equal @connections.length, 2 - assert_equal @timed_out, 2 + # Will deadlock due to lack of Monitor timeouts in 1.9 + if RUBY_VERSION < '1.9' + def test_pooled_connection_checkout + checkout_connections + assert_equal @connections.length, 2 + assert_equal @timed_out, 2 + end end def checkout_checkin_connections(pool_size, threads) -- cgit v1.2.3 From 5cc27f2b0302698ef517755df41f3212587bceb9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Nov 2008 18:45:19 +0530 Subject: Add some basic controller logging tests --- actionpack/test/controller/logging_test.rb | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 actionpack/test/controller/logging_test.rb diff --git a/actionpack/test/controller/logging_test.rb b/actionpack/test/controller/logging_test.rb new file mode 100644 index 0000000000..3c936854dd --- /dev/null +++ b/actionpack/test/controller/logging_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' + +class LoggingController < ActionController::Base + def show + render :nothing => true + end +end + +class LoggingTest < ActionController::TestCase + tests LoggingController + + class MockLogger + attr_reader :logged + + def method_missing(method, *args) + @logged ||= [] + @logged << args.first + end + end + + setup :set_logger + + def test_logging_without_parameters + get :show + assert_equal 2, logs.size + assert_nil logs.detect {|l| l =~ /Parameters/ } + end + + def test_logging_with_parameters + get :show, :id => 10 + assert_equal 3, logs.size + + params = logs.detect {|l| l =~ /Parameters/ } + assert_equal 'Parameters: {"id"=>"10"}', params + end + + private + + def set_logger + @controller.logger = MockLogger.new + end + + def logs + @logs ||= @controller.logger.logged.compact.map {|l| l.strip} + end +end -- cgit v1.2.3 From a6d6a1c9aca612232228c1111be810736a26ab63 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 18:58:08 -0500 Subject: Move sshpublisher require into the rake tasks that use it so ruby 1.9 and macruby don't need the rake gem installed --- activesupport/Rakefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 1961fb43cf..ccbab525ba 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,7 +1,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/gempackagetask' -require 'rake/contrib/sshpublisher' require File.join(File.dirname(__FILE__), 'lib', 'active_support', 'version') @@ -65,12 +64,14 @@ end desc "Publish the beta gem" task :pgem => [:package] do + require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do + require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload end -- cgit v1.2.3 From 335a31524055c7dd79618ea79b3c18d827e25d3d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 10 Nov 2008 14:16:43 -0600 Subject: Add simple case when DB calculations returns 0 instead of 0.0 [#1346 state:resolved] --- activerecord/lib/active_record/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index dd90580b3d..6f4e02b430 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -286,7 +286,7 @@ module ActiveRecord case operation when 'count' then value.to_i when 'sum' then type_cast_using_column(value || '0', column) - when 'avg' then value && value.to_f.to_d + when 'avg' then value && (value == 0 ? 0.0.to_d : value.to_d) else type_cast_using_column(value, column) end end -- cgit v1.2.3 From 5db9f9b3ad47fadf0b3f12ada1c2ea7b9c15ded5 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 10 Nov 2008 17:41:07 -0800 Subject: Pare down object creation during route building --- .../lib/action_controller/routing/builder.rb | 53 ++++++++++------------ .../lib/action_controller/routing/routing_ext.rb | 4 ++ 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index 7b888fa8d2..d4e501e780 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -1,23 +1,16 @@ module ActionController module Routing class RouteBuilder #:nodoc: - attr_accessor :separators, :optional_separators + attr_reader :separators, :optional_separators + attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp def initialize - self.separators = Routing::SEPARATORS - self.optional_separators = %w( / ) - end - - def separator_pattern(inverted = false) - "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" - end - - def interval_regexp - Regexp.new "(.*?)(#{separators.source}|$)" - end + @separators = Routing::SEPARATORS + @optional_separators = %w( / ) - def multiline_regexp?(expression) - expression.options & Regexp::MULTILINE == Regexp::MULTILINE + @separator_regexp = /[#{Regexp.escape(separators.join)}]/ + @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/ + @interval_regexp = /(.*?)(#{separator_regexp}|$)/ end # Accepts a "route path" (a string defining a route), and returns the array @@ -30,7 +23,7 @@ module ActionController rest, segments = path, [] until rest.empty? - segment, rest = segment_for rest + segment, rest = segment_for(rest) segments << segment end segments @@ -39,20 +32,20 @@ module ActionController # A factory method that returns a new segment instance appropriate for the # format of the given string. def segment_for(string) - segment = case string - when /\A:(\w+)/ - key = $1.to_sym - case key - when :controller then ControllerSegment.new(key) - else DynamicSegment.new key - end - when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) - when /\A\?(.*?)\?/ - StaticSegment.new($1, :optional => true) - when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) - when Regexp.new(separator_pattern) then - DividerSegment.new($&, :optional => (optional_separators.include? $&)) - end + segment = + case string + when /\A:(\w+)/ + key = $1.to_sym + key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) + when /\A\*(\w+)/ + PathSegment.new($1.to_sym, :optional => true) + when /\A\?(.*?)\?/ + StaticSegment.new($1, :optional => true) + when nonseparator_regexp + StaticSegment.new($1) + when separator_regexp + DividerSegment.new($&, :optional => optional_separators.include?($&)) + end [segment, $~.post_match] end @@ -98,7 +91,7 @@ module ActionController if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end - if multiline_regexp?(requirement) + if requirement.multiline? raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" end segment.regexp = requirement diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb index 5f4ba90d0c..4a82b2af5f 100644 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ b/actionpack/lib/action_controller/routing/routing_ext.rb @@ -27,6 +27,10 @@ class Regexp #:nodoc: Regexp.new("|#{source}").match('').captures.length end + def multiline? + options & MULTILINE == MULTILINE + end + class << self def optionalize(pattern) case unoptionalize(pattern) -- cgit v1.2.3 From 278b6cd9529f33286449a9be18f1903687814d3f Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 10 Nov 2008 19:53:53 -0800 Subject: Eliminate excess Regexp creation due to capture counting --- actionpack/lib/action_controller/routing/route.rb | 2 +- .../lib/action_controller/routing/segments.rb | 28 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb index 3b2cb28545..a610ec7e84 100644 --- a/actionpack/lib/action_controller/routing/route.rb +++ b/actionpack/lib/action_controller/routing/route.rb @@ -219,7 +219,7 @@ module ActionController next_capture = 1 extraction = segments.collect do |segment| x = segment.match_extraction(next_capture) - next_capture += Regexp.new(segment.regexp_chunk).number_of_captures + next_capture += segment.number_of_captures x end extraction.compact diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index e5f174ae2c..f6b03edcca 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -13,6 +13,10 @@ module ActionController @is_optional = false end + def number_of_captures + Regexp.new(regexp_chunk).number_of_captures + end + def extraction_code nil end @@ -84,6 +88,10 @@ module ActionController optional? ? Regexp.optionalize(chunk) : chunk end + def number_of_captures + 0 + end + def build_pattern(pattern) escaped = Regexp.escape(value) if optional? && ! pattern.empty? @@ -194,10 +202,16 @@ module ActionController end end + def number_of_captures + if regexp + regexp.number_of_captures + 1 + else + 1 + end + end + def build_pattern(pattern) - chunk = regexp_chunk - chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 - pattern = "#{chunk}#{pattern}" + pattern = "#{regexp_chunk}#{pattern}" optional? ? Regexp.optionalize(pattern) : pattern end @@ -230,6 +244,10 @@ module ActionController "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" end + def number_of_captures + 1 + end + # Don't URI.escape the controller name since it may contain slashes. def interpolation_chunk(value_code = local_name) "\#{#{value_code}.to_s}" @@ -275,6 +293,10 @@ module ActionController regexp || "(.*)" end + def number_of_captures + regexp ? regexp.number_of_captures : 1 + end + def optionality_implied? true end -- cgit v1.2.3 From cbb38bbdba0f7cfb628a0f8716e79d0d079fd7bf Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 10 Nov 2008 21:39:05 -0800 Subject: Only track new constant definitions when we're reloading dependencies --- activesupport/lib/active_support/dependencies.rb | 12 ++++++++++-- activesupport/test/dependencies_test.rb | 10 +++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 3d871eec11..fe568d6127 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -138,14 +138,22 @@ module ActiveSupport #:nodoc: end def load_with_new_constant_marking(file, *extras) #:nodoc: - Dependencies.new_constants_in(Object) { load_without_new_constant_marking(file, *extras) } + if Dependencies.load? + Dependencies.new_constants_in(Object) { load_without_new_constant_marking(file, *extras) } + else + load_without_new_constant_marking(file, *extras) + end rescue Exception => exception # errors from loading file exception.blame_file! file raise end def require(file, *extras) #:nodoc: - Dependencies.new_constants_in(Object) { super } + if Dependencies.load? + Dependencies.new_constants_in(Object) { super } + else + super + end rescue Exception => exception # errors from required file exception.blame_file! file raise diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 6c3bd1a4fd..fe04b91f2b 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -694,17 +694,17 @@ class DependenciesTest < Test::Unit::TestCase with_loading 'autoloading_fixtures' do ActiveSupport::Dependencies.mechanism = :require 2.times do - assert_raise(NameError) {"RaisesNameError".constantize} + assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz } end end end def test_autoload_doesnt_shadow_name_error with_loading 'autoloading_fixtures' do - assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it hasn't been referenced yet!" + Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError) 2.times do begin - ::RaisesNameError.object_id + ::RaisesNameError::FooBarBaz.object_id flunk 'should have raised NameError when autoloaded file referenced FooBarBaz' rescue NameError => e assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message @@ -712,9 +712,9 @@ class DependenciesTest < Test::Unit::TestCase assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" end - assert !defined?(RaisesNameError) + assert !defined?(::RaisesNameError) 2.times do - assert_raise(NameError) { RaisesNameError } + assert_raise(NameError) { ::RaisesNameError } assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" end end -- cgit v1.2.3 From 78a18392e15c3de1c70b44e285d1742687624dd1 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Tue, 11 Nov 2008 11:22:13 +0100 Subject: Remove redundant uniq --- activerecord/lib/active_record/association_preload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 6e194ab9b4..69300e5ce5 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -312,7 +312,7 @@ module ActiveRecord table_name = klass.quoted_table_name primary_key = klass.primary_key column_type = klass.columns.detect{|c| c.name == primary_key}.type - ids = id_map.keys.uniq.map do |id| + ids = id_map.keys.map do |id| if column_type == :integer id.to_i elsif column_type == :float -- cgit v1.2.3 From a62e9e90d8f47a643c9355f782ab4891fa8fefaa Mon Sep 17 00:00:00 2001 From: Joel Chippindale Date: Tue, 11 Nov 2008 09:39:50 -0600 Subject: Fix for ActionMailer::Base.method_missing so that it raises NoMethodError when no method is found [#1330 state:resolved] Signed-off-by: Joshua Peek --- actionmailer/lib/action_mailer/base.rb | 14 ++++++++------ actionmailer/test/mail_service_test.rb | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index d63a608109..17b5eaa8d1 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -386,12 +386,14 @@ module ActionMailer #:nodoc: end def method_missing(method_symbol, *parameters) #:nodoc: - match = matches_dynamic_method?(method_symbol) - case match[1] - when 'create' then new(match[2], *parameters).mail - when 'deliver' then new(match[2], *parameters).deliver! - when 'new' then nil - else super + if match = matches_dynamic_method?(method_symbol) + case match[1] + when 'create' then new(match[2], *parameters).mail + when 'deliver' then new(match[2], *parameters).deliver! + when 'new' then nil + end + else + super end end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index f5cb372b2a..8b1c9a8dca 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -1045,4 +1045,12 @@ class RespondToTest < Test::Unit::TestCase def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_digit assert !RespondToMailer.respond_to?(:deliver_1_template) end + + def test_should_still_raise_exception_with_expected_message_when_calling_an_undefined_method + error = assert_raises NoMethodError do + RespondToMailer.not_a_method + end + + assert_match /undefined method.*not_a_method/, error.message + end end -- cgit v1.2.3 From c65075feb6c4ce15582bc08411e6698d782249a7 Mon Sep 17 00:00:00 2001 From: Joel Chippindale Date: Tue, 11 Nov 2008 09:45:53 -0600 Subject: Fixed method_missing for ActionMailer so it no longer matches methods where deliver or create are not a suffix [#1318 state:resolved] Signed-off-by: Joshua Peek --- actionmailer/lib/action_mailer/base.rb | 3 ++- actionmailer/test/mail_service_test.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 17b5eaa8d1..19ce77ea5a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -391,6 +391,7 @@ module ActionMailer #:nodoc: when 'create' then new(match[2], *parameters).mail when 'deliver' then new(match[2], *parameters).deliver! when 'new' then nil + else super end else super @@ -442,7 +443,7 @@ module ActionMailer #:nodoc: private def matches_dynamic_method?(method_name) #:nodoc: method_name = method_name.to_s - /(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name) + /^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name) end end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 8b1c9a8dca..b88beb3314 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -1046,6 +1046,10 @@ class RespondToTest < Test::Unit::TestCase assert !RespondToMailer.respond_to?(:deliver_1_template) end + def test_should_not_respond_to_method_where_deliver_is_not_a_suffix + assert !RespondToMailer.respond_to?(:foo_deliver_template) + end + def test_should_still_raise_exception_with_expected_message_when_calling_an_undefined_method error = assert_raises NoMethodError do RespondToMailer.not_a_method -- cgit v1.2.3 From 44a3009ff068bf080de6764a8c884fbf0ceb920e Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Wed, 12 Nov 2008 11:00:17 +0000 Subject: Add :only/:except options to map.resources This allows people with huge numbers of resource routes to cut down on the memory consumption caused by the generated code. Signed-off-by: Michael Koziarski [#1215 state:committed] --- actionpack/lib/action_controller/resources.rb | 122 +++++++++++++------ actionpack/test/controller/resources_test.rb | 162 ++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 37 deletions(-) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 872b0dab3d..de529e23ff 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -42,7 +42,11 @@ module ActionController # # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer module Resources + INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except + class Resource #:nodoc: + DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy + attr_reader :collection_methods, :member_methods, :new_methods attr_reader :path_prefix, :name_prefix, :path_segment attr_reader :plural, :singular @@ -57,6 +61,7 @@ module ActionController arrange_actions add_default_actions + set_allowed_actions set_prefixes end @@ -113,6 +118,10 @@ module ActionController @singular.to_s == @plural.to_s end + def has_action?(action) + !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + end + protected def arrange_actions @collection_methods = arrange_actions_by_methods(options.delete(:collection)) @@ -125,6 +134,30 @@ module ActionController add_default_action(new_methods, :get, :new) end + def set_allowed_actions + only, except = @options.values_at(:only, :except) + @allowed_actions ||= {} + + if only == :all || except == :none + only = nil + except = [] + elsif only == :none || except == :all + only = [] + except = nil + end + + if only + @allowed_actions[:only] = Array(only).map(&:to_sym) + elsif except + @allowed_actions[:except] = Array(except).map(&:to_sym) + end + end + + def action_allowed?(action) + only, except = @allowed_actions.values_at(:only, :except) + (!only || only.include?(action)) && (!except || !except.include?(action)) + end + def set_prefixes @path_prefix = options.delete(:path_prefix) @name_prefix = options.delete(:name_prefix) @@ -353,6 +386,25 @@ module ActionController # # map.resources :users, :has_many => { :posts => :comments }, :shallow => true # + # * :only and :except - Specify which of the seven default actions should be routed to. + # + # :only and :except may be set to :all, :none, an action name or a + # list of action names. By default, routes are generated for all seven actions. + # + # For example: + # + # map.resources :posts, :only => [:index, :show] do |post| + # post.resources :comments, :except => [:update, :destroy] + # end + # # --> GET /posts (maps to the PostsController#index action) + # # --> POST /posts (fails) + # # --> GET /posts/1 (maps to the PostsController#show action) + # # --> DELETE /posts/1 (fails) + # # --> POST /posts/1/comments (maps to the CommentsController#create action) + # # --> PUT /posts/1/comments/1 (fails) + # + # The :only and :except options are inherited by any nested resource(s). + # # If map.resources is called with multiple resources, they all get the same options applied. # # Examples: @@ -478,7 +530,7 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end end end @@ -495,7 +547,7 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end end end @@ -507,7 +559,7 @@ module ActionController name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" Array(options[:has_one]).each do |association| - resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow]) + resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) end end @@ -522,7 +574,7 @@ module ActionController map_has_many_associations(resource, association, options) end when Symbol, String - resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many]) + resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) else end end @@ -531,41 +583,39 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - action_options = action_options_for(action, resource, m) - map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options) + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) end end end end def map_default_collection_actions(map, resource) - index_action_options = action_options_for("index", resource) index_route_name = "#{resource.name_prefix}#{resource.plural}" if resource.uncountable? index_route_name << "_index" end - map_named_routes(map, index_route_name, resource.path, index_action_options) - - create_action_options = action_options_for("create", resource) - map_unnamed_routes(map, resource.path, create_action_options) + map_resource_routes(map, resource, :index, resource.path, index_route_name) + map_resource_routes(map, resource, :create, resource.path) end def map_default_singleton_actions(map, resource) - create_action_options = action_options_for("create", resource) - map_unnamed_routes(map, resource.path, create_action_options) + map_resource_routes(map, resource, :create, resource.path) end def map_new_actions(map, resource) resource.new_methods.each do |method, actions| actions.each do |action| - action_options = action_options_for(action, resource, method) - if action == :new - map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options) - else - map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options) + route_path = resource.new_path + route_name = "new_#{resource.name_prefix}#{resource.singular}" + + unless action == :new + route_path = "#{route_path}#{resource.action_separator}#{action}" + route_name = "#{action}_#{route_name}" end + + map_resource_routes(map, resource, action, route_path, route_name, method) end end end @@ -574,34 +624,32 @@ module ActionController resource.member_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - action_options = action_options_for(action, resource, m) - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m) end end end - show_action_options = action_options_for("show", resource) - map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options) - - update_action_options = action_options_for("update", resource) - map_unnamed_routes(map, resource.member_path, update_action_options) - - destroy_action_options = action_options_for("destroy", resource) - map_unnamed_routes(map, resource.member_path, destroy_action_options) + map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}") + map_resource_routes(map, resource, :update, resource.member_path) + map_resource_routes(map, resource, :destroy, resource.member_path) end - def map_unnamed_routes(map, path_without_format, options) - map.connect(path_without_format, options) - map.connect("#{path_without_format}.:format", options) - end - - def map_named_routes(map, name, path_without_format, options) - map.named_route(name, path_without_format, options) - map.named_route("formatted_#{name}", "#{path_without_format}.:format", options) + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) + if resource.has_action?(action) + action_options = action_options_for(action, resource, method) + formatted_route_path = "#{route_path}.:format" + + if route_name + map.named_route(route_name, route_path, action_options) + map.named_route("formatted_#{route_name}", formatted_route_path, action_options) + else + map.connect(route_path, action_options) + map.connect(formatted_route_path, action_options) + end + end end def add_conditions_for(conditions, method) diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1fea82e564..2a86577d8c 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -14,6 +14,8 @@ class LogosController < ResourcesController; end class AccountsController < ResourcesController; end class AdminController < ResourcesController; end +class ProductsController < ResourcesController; end +class ImagesController < ResourcesController; end module Backoffice class ProductsController < ResourcesController; end @@ -776,6 +778,121 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_resource_has_only_show_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + end + end + + def test_singleton_resource_has_only_show_action + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :show + end + + assert_singleton_resource_allowed_routes('accounts', {}, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + end + end + + def test_resource_does_not_have_destroy_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :destroy + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) + end + end + + def test_singleton_resource_does_not_have_destroy_action + with_routing do |set| + set.draw do |map| + map.resource :account, :except => :destroy + end + + assert_singleton_resource_allowed_routes('accounts', {}, [:new, :create, :show, :edit, :update], :destroy) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [:new, :create, :show, :edit, :update], :destroy) + end + end + + def test_resource_has_only_collection_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :all, :collection => { :sale => :get } + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'products', :action => 'sale' }, :path => 'products/sale', :method => :get) + assert_recognizes({ :controller => 'products', :action => 'sale', :format => 'xml' }, :path => 'products/sale.xml', :method => :get) + end + end + + def test_resource_has_only_member_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :all, :member => { :preview => :get } + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1' }, :path => 'products/1/preview', :method => :get) + assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1', :format => 'xml' }, :path => 'products/1/preview.xml', :method => :get) + end + end + + def test_singleton_resource_has_only_member_action + with_routing do |set| + set.draw do |map| + map.resource :account, :except => :all, :member => { :signup => :get } + end + + assert_singleton_resource_allowed_routes('accounts', {}, [], [:new, :create, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [], [:new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'accounts', :action => 'signup' }, :path => 'account/signup', :method => :get) + assert_recognizes({ :controller => 'accounts', :action => 'signup', :format => 'xml' }, :path => 'account/signup.xml', :method => :get) + end + end + + def test_nested_resource_inherits_only_show_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show do |product| + product.resources :images + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + end + end + + def test_nested_resource_has_only_show_and_member_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => [:index, :show] do |product| + product.resources :images, :member => { :thumbnail => :get }, :only => :show + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + + assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2' }, :path => 'products/1/images/2/thumbnail', :method => :get) + assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2', :format => 'jpg' }, :path => 'products/1/images/2/thumbnail.jpg', :method => :get) + end + end + protected def with_restful_routing(*args) with_routing do |set| @@ -979,6 +1096,51 @@ class ResourcesTest < Test::Unit::TestCase end end + def assert_resource_allowed_routes(controller, options, shallow_options, allowed, not_allowed, path = controller) + shallow_path = "#{path}/#{shallow_options[:id]}" + format = options[:format] && ".#{options[:format]}" + options.merge!(:controller => controller) + shallow_options.merge!(options) + + assert_whether_allowed(allowed, not_allowed, options, 'index', "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'show', "#{shallow_path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit', "#{shallow_path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy', "#{shallow_path}#{format}", :delete) + end + + def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize) + format = options[:format] && ".#{options[:format]}" + options.merge!(:controller => controller) + + assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, options, 'show', "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'edit', "#{path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, options, 'destroy', "#{path}#{format}", :delete) + end + + def assert_whether_allowed(allowed, not_allowed, options, action, path, method) + action = action.to_sym + options = options.merge(:action => action.to_s) + path_options = { :path => path, :method => method } + + if Array(allowed).include?(action) + assert_recognizes options, path_options + elsif Array(not_allowed).include?(action) + assert_not_recognizes options, path_options + end + end + + def assert_not_recognizes(expected_options, path) + assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Test::Unit::AssertionFailedError do + assert_recognizes(expected_options, path) + end + end + def distinct_routes? (r1, r2) if r1.conditions == r2.conditions and r1.requirements == r2.requirements then if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then -- cgit v1.2.3 From 02df503d3b4db7a3e7fabe1403c388a059f905b8 Mon Sep 17 00:00:00 2001 From: Phil Ross Date: Wed, 12 Nov 2008 13:42:56 +0000 Subject: TimeZone: Caracas GMT offset changed to -4:30 [#1361 state:resolved] --- activesupport/CHANGELOG | 2 ++ activesupport/lib/active_support/values/time_zone.rb | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index e77affc315..12b300d3ae 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* TimeZone: Caracas GMT offset changed to -4:30 [#1361 state:resolved] [Phil Ross] + * Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] * Fixed the option merging in Array#to_xml #1126 [Rudolf Gavlas] diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 4991f71683..335d75d218 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -304,7 +304,8 @@ module ActiveSupport "Mexico City", "Monterrey", "Central America" ], [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota", "Lima", "Quito" ], - [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ], + [-16_200, "Caracas" ], + [-14_400, "Atlantic Time (Canada)", "La Paz", "Santiago" ], [-12_600, "Newfoundland" ], [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ], [ -7_200, "Mid-Atlantic" ], -- cgit v1.2.3 From fbbcd6f29aeccc938b97b5c01717365f8b67912c Mon Sep 17 00:00:00 2001 From: Jeff Cohen Date: Fri, 31 Oct 2008 23:10:44 -0500 Subject: Changed request forgery protection to only worry about HTML-formatted content requests. Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/mime_type.rb | 4 +- .../request_forgery_protection.rb | 2 +- actionpack/lib/action_controller/test_process.rb | 1 + .../controller/request_forgery_protection_test.rb | 118 ++++++++++++--------- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 26edca3b69..f43ae721c6 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -19,7 +19,7 @@ module Mime # end # end class Type - @@html_types = Set.new [:html, :all] + @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] cattr_reader :html_types, :unverifiable_types @@ -167,7 +167,7 @@ module Mime # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See # ActionController::RequestForgerProtection. def verify_request? - !@@unverifiable_types.include?(to_sym) + html? end def html? diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 05a6d8bb79..3e0e94a06b 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: end def verifiable_request_format? - request.content_type.nil? || request.content_type.verify_request? + !request.content_type.nil? && request.content_type.verify_request? end # Sets the token value for the current session. Pass a :secret option diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 7a31f0e8d5..1e3a646bc9 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -395,6 +395,7 @@ module ActionController #:nodoc: @html_document = nil @request.env['REQUEST_METHOD'] ||= "GET" + @request.action = action.to_s parameters ||= {} diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index f7adaa7d4e..5669b8f358 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -77,57 +77,61 @@ module RequestForgeryProtectionTests ActionController::Base.request_forgery_protection_token = nil end + def test_should_render_form_with_token_tag - get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + get :index + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + end + + def test_should_render_button_to_with_token_tag + get :show_button + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + end + + def test_should_render_remote_form_with_only_one_token_parameter + get :remote_form + assert_equal 1, @response.body.scan(@token).size + end + + def test_should_allow_get + get :index + assert_response :success + end + + def test_should_allow_post_without_token_on_unsafe_action + post :unsafe + assert_response :success + end + + def test_should_not_allow_html_post_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } end - def test_should_render_button_to_with_token_tag - get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token - end - - def test_should_render_remote_form_with_only_one_token_parameter - get :remote_form - assert_equal 1, @response.body.scan(@token).size - end - - def test_should_allow_get - get :index - assert_response :success + def test_should_not_allow_html_put_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } end - def test_should_allow_post_without_token_on_unsafe_action - post :unsafe - assert_response :success + def test_should_not_allow_html_delete_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } end - def test_should_not_allow_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { post :index } - end - - def test_should_not_allow_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { put :index } - end - - def test_should_not_allow_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { delete :index } - end - - def test_should_not_allow_api_formatted_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + def test_should_allow_api_formatted_post_without_token + assert_nothing_raised do post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_nothing_raised do put :index, :format => 'xml' end end - def test_should_not_allow_api_formatted_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + def test_should_allow_api_formatted_delete_without_token + assert_nothing_raised do delete :index, :format => 'xml' end end @@ -174,16 +178,20 @@ module RequestForgeryProtectionTests end end - def test_should_not_allow_xhr_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index } + def test_should_allow_xhr_post_without_token + assert_nothing_raised { xhr :post, :index } + end + def test_should_not_allow_xhr_post_with_html_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index } end - def test_should_not_allow_xhr_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :put, :index } + def test_should_allow_xhr_put_without_token + assert_nothing_raised { xhr :put, :index } end - def test_should_not_allow_xhr_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :delete, :index } + def test_should_allow_xhr_delete_without_token + assert_nothing_raised { xhr :delete, :index } end def test_should_allow_post_with_token @@ -227,6 +235,7 @@ class RequestForgeryProtectionControllerTest < Test::Unit::TestCase def setup @controller = RequestForgeryProtectionController.new @request = ActionController::TestRequest.new + @request.format = :html @response = ActionController::TestResponse.new class << @request.session def session_id() '123' end @@ -248,11 +257,11 @@ class RequestForgeryProtectionWithoutSecretControllerTest < Test::Unit::TestCase ActionController::Base.request_forgery_protection_token = :authenticity_token end - def test_should_raise_error_without_secret - assert_raises ActionController::InvalidAuthenticityToken do - get :index - end - end + # def test_should_raise_error_without_secret + # assert_raises ActionController::InvalidAuthenticityToken do + # get :index + # end + # end end class CsrfCookieMonsterControllerTest < Test::Unit::TestCase @@ -304,10 +313,15 @@ class SessionOffControllerTest < Test::Unit::TestCase @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') end - def test_should_raise_correct_exception - @request.session = {} # session(:off) doesn't appear to work with controller tests - assert_raises(ActionController::InvalidAuthenticityToken) do - post :index, :authenticity_token => @token - end - end + # TODO: Rewrite this test. + # This test was passing but for the wrong reason. + # Sessions aren't really being turned off, so an exception was raised + # because sessions weren't on - not because the token didn't match. + # + # def test_should_raise_correct_exception + # @request.session = {} # session(:off) doesn't appear to work with controller tests + # assert_raises(ActionController::InvalidAuthenticityToken) do + # post :index, :authenticity_token => @token, :format => :html + # end + # end end -- cgit v1.2.3 From 00c46b5eeb858629ef1c7ab50f022aecccca42c3 Mon Sep 17 00:00:00 2001 From: rick Date: Wed, 12 Nov 2008 13:34:29 -0800 Subject: fix two MimeType failing test cases Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/mime_type.rb | 5 ++++- actionpack/test/controller/mime_type_test.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index f43ae721c6..48c4c1ee1e 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -20,8 +20,11 @@ module Mime # end class Type @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] + cattr_reader :html_types + + # UNUSED, deprecate? @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] - cattr_reader :html_types, :unverifiable_types + cattr_reader :unverifiable_types # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb index f16a3c68b4..4cfaf38ac7 100644 --- a/actionpack/test/controller/mime_type_test.rb +++ b/actionpack/test/controller/mime_type_test.rb @@ -61,7 +61,9 @@ class MimeTypeTest < Test::Unit::TestCase types.each do |type| mime = Mime.const_get(type.to_s.upcase) assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?" - (types - [type]).each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } + invalid_types = types - [type] + invalid_types.delete(:html) if Mime::Type.html_types.include?(type) + invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } end end @@ -71,14 +73,12 @@ class MimeTypeTest < Test::Unit::TestCase end def test_verifiable_mime_types - unverified_types = Mime::Type.unverifiable_types all_types = Mime::SET.to_a.map(&:to_sym) all_types.uniq! # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } - - unverified, verified = all_types.partition { |type| Mime::Type.unverifiable_types.include? type } - assert verified.all? { |type| Mime.const_get(type.to_s.upcase).verify_request? }, "Not all Mime Types are verified: #{verified.inspect}" - assert unverified.all? { |type| !Mime.const_get(type.to_s.upcase).verify_request? }, "Some Mime Types are verified: #{unverified.inspect}" + verified, unverified = all_types.partition { |type| Mime::Type.html_types.include? type } + assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is not verified: #{type.inspect}" } + assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is verified: #{type.inspect}" } end end -- cgit v1.2.3 From f1ad8b48aae3ee26613b3e77bc0056e120096846 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 13 Nov 2008 11:19:53 +0100 Subject: Instead of overriding html_types, base the verification on browser_generated_types. Also Deprecate the old unverifiable types. [#1145 state:committed] --- actionpack/lib/action_controller/mime_type.rb | 21 +++++++++++++++++---- actionpack/test/controller/mime_type_test.rb | 6 +++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 48c4c1ee1e..8ca3a70341 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -19,12 +19,21 @@ module Mime # end # end class Type - @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] + @@html_types = Set.new [:html, :all] cattr_reader :html_types - # UNUSED, deprecate? + # These are the content types which browsers can generate without using ajax, flash, etc + # i.e. following a link, getting an image or posting a form. CSRF protection + # only needs to protect against these types. + @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form] + cattr_reader :browser_generated_types + + @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] - cattr_reader :unverifiable_types + def self.unverifiable_types + ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller) + @@unverifiable_types + end # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: @@ -170,13 +179,17 @@ module Mime # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See # ActionController::RequestForgerProtection. def verify_request? - html? + browser_generated? end def html? @@html_types.include?(to_sym) || @string =~ /html/ end + def browser_generated? + @@browser_generated_types.include?(to_sym) + end + private def method_missing(method, *args) if method.to_s =~ /(\w+)\?$/ diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb index 4cfaf38ac7..21ae0419f1 100644 --- a/actionpack/test/controller/mime_type_test.rb +++ b/actionpack/test/controller/mime_type_test.rb @@ -77,8 +77,8 @@ class MimeTypeTest < Test::Unit::TestCase all_types.uniq! # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } - verified, unverified = all_types.partition { |type| Mime::Type.html_types.include? type } - assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is not verified: #{type.inspect}" } - assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is verified: #{type.inspect}" } + verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type } + assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" } + assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" } end end -- cgit v1.2.3 From 020a4113048be7166346cba6c59bbbca819de911 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Thu, 13 Nov 2008 09:04:06 -0600 Subject: TimeZone: fix base offset for Sri Jayawardenepura. Anchor tests for zone offsets to more current date --- activesupport/CHANGELOG | 2 ++ activesupport/lib/active_support/values/time_zone.rb | 4 ++-- activesupport/test/time_zone_test.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 12b300d3ae..1475586cde 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* TimeZone: fix offset for Sri Jayawardenepura. Anchor tests for zone offsets to more current date [Geoff Buesing] + * TimeZone: Caracas GMT offset changed to -4:30 [#1361 state:resolved] [Phil Ross] * Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 335d75d218..1d87fa64b5 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -326,9 +326,9 @@ module ActiveSupport [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ], [ 16_200, "Kabul" ], [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ], - [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ], + [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi", "Sri Jayawardenepura" ], [ 20_700, "Kathmandu" ], - [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty", + [ 21_600, "Astana", "Dhaka", "Almaty", "Novosibirsk" ], [ 23_400, "Rangoon" ], [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ], diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 515ffcf0bf..d999b9f2a8 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -51,7 +51,7 @@ class TimeZoneTest < Test::Unit::TestCase define_method("test_utc_offset_for_#{name}") do silence_warnings do # silence warnings raised by tzinfo gem - period = zone.tzinfo.period_for_utc(Time.utc(2006,1,1,0,0,0)) + period = zone.tzinfo.period_for_utc(Time.utc(2009,1,1,0,0,0)) assert_equal period.utc_offset, zone.utc_offset end end -- cgit v1.2.3 From 57d795bad43d4a3e5eef7151099a8e40808a1031 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Thu, 13 Nov 2008 10:45:57 -0600 Subject: Make sure any Fixnum returned by a DB sum is type cast to a Float before standard converstion to a BigDecimal [#8994 state:resolved] Signed-off-by: Joshua Peek --- activerecord/lib/active_record/calculations.rb | 2 +- activerecord/test/cases/calculations_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 6f4e02b430..65512d534a 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -286,7 +286,7 @@ module ActiveRecord case operation when 'count' then value.to_i when 'sum' then type_cast_using_column(value || '0', column) - when 'avg' then value && (value == 0 ? 0.0.to_d : value.to_d) + when 'avg' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d else type_cast_using_column(value, column) end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 0fa61500c0..8bd0dd0f6e 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -25,6 +25,11 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_return_nil_as_average assert_nil NumericData.average(:bank_balance) end + + def test_type_cast_calculated_value_should_convert_db_averages_of_fixnum_class_to_decimal + assert_equal 0, NumericData.send(:type_cast_calculated_value, 0, nil, 'avg') + assert_equal 53.0, NumericData.send(:type_cast_calculated_value, 53, nil, 'avg') + end def test_should_get_maximum_of_field assert_equal 60, Account.maximum(:credit_limit) -- cgit v1.2.3 From 4c0921024471c0463d67f8b8fb6a115a94d343aa Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Thu, 13 Nov 2008 14:31:36 +0000 Subject: Fix map.resources to always generate named routes if they're needed Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/resources.rb | 13 ++--- actionpack/test/controller/resources_test.rb | 78 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index de529e23ff..d6cc4aa418 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -597,11 +597,11 @@ module ActionController end map_resource_routes(map, resource, :index, resource.path, index_route_name) - map_resource_routes(map, resource, :create, resource.path) + map_resource_routes(map, resource, :create, resource.path, index_route_name) end def map_default_singleton_actions(map, resource) - map_resource_routes(map, resource, :create, resource.path) + map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") end def map_new_actions(map, resource) @@ -632,9 +632,10 @@ module ActionController end end - map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}") - map_resource_routes(map, resource, :update, resource.member_path) - map_resource_routes(map, resource, :destroy, resource.member_path) + route_path = "#{resource.shallow_name_prefix}#{resource.singular}" + map_resource_routes(map, resource, :show, resource.member_path, route_path) + map_resource_routes(map, resource, :update, resource.member_path, route_path) + map_resource_routes(map, resource, :destroy, resource.member_path, route_path) end def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) @@ -642,7 +643,7 @@ module ActionController action_options = action_options_for(action, resource, method) formatted_route_path = "#{route_path}.:format" - if route_name + if route_name && @set.named_routes[route_name.to_sym].nil? map.named_route(route_name, route_path, action_options) map.named_route("formatted_#{route_name}", formatted_route_path, action_options) else diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 2a86577d8c..1f1f7b8a2c 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -822,6 +822,84 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_resource_has_only_create_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :create + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) + + assert_not_nil set.named_routes[:products] + end + end + + def test_resource_has_only_update_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :update + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) + + assert_not_nil set.named_routes[:product] + end + end + + def test_resource_has_only_destroy_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :destroy + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) + + assert_not_nil set.named_routes[:product] + end + end + + def test_singleton_resource_has_only_create_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :create + end + + assert_singleton_resource_allowed_routes('accounts', {}, :create, [:new, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :create, [:new, :show, :edit, :update, :destroy]) + + assert_not_nil set.named_routes[:account] + end + end + + def test_singleton_resource_has_only_update_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :update + end + + assert_singleton_resource_allowed_routes('accounts', {}, :update, [:new, :create, :show, :edit, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :update, [:new, :create, :show, :edit, :destroy]) + + assert_not_nil set.named_routes[:account] + end + end + + def test_singleton_resource_has_only_destroy_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :destroy + end + + assert_singleton_resource_allowed_routes('accounts', {}, :destroy, [:new, :create, :show, :edit, :update]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :destroy, [:new, :create, :show, :edit, :update]) + + assert_not_nil set.named_routes[:account] + end + end + def test_resource_has_only_collection_action with_routing do |set| set.draw do |map| -- cgit v1.2.3 From 4e9abdd7f1b4e05f8d1b50ddaa080b3ff63b92d9 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 13 Nov 2008 21:49:23 +0100 Subject: Tag helper should output an attribute with the value 'false' instead of omitting the attribute, if the associated option is false but not nil. --- actionpack/lib/action_view/helpers/tag_helper.rb | 10 ++++++---- actionpack/test/template/form_tag_helper_test.rb | 2 +- actionpack/test/template/tag_helper_test.rb | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index de08672d2d..d37ca766af 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -133,10 +133,12 @@ module ActionView unless options.blank? attrs = [] if escape - options.each do |key, value| - next unless value - value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value) - attrs << %(#{key}="#{value}") + options.each_pair do |key, value| + if BOOLEAN_ATTRIBUTES.include?(key) + attrs << %(#{key}="#{key}") if value + else + attrs << %(#{key}="#{escape_once(value)}") if !value.nil? + end end else attrs = options.map { |key, value| %(#{key}="#{value}") } diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index de82647813..f8add0bab1 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -235,7 +235,7 @@ class FormTagHelperTest < ActionView::TestCase assert_match VALID_HTML_ID, label_elem['for'] end - def test_boolean_optios + def test_boolean_options assert_dom_equal %(), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) assert_dom_equal %(), select_tag("people", "", :multiple => true) diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index fc49d340ef..ef88cae5b8 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -19,6 +19,10 @@ class TagHelperTest < ActionView::TestCase assert_equal "

    ", tag("p", :ignored => nil) end + def test_tag_options_accepts_false_option + assert_equal "

    ", tag("p", :value => false) + end + def test_tag_options_accepts_blank_option assert_equal "

    ", tag("p", :included => '') end -- cgit v1.2.3 From 9a88ab64bb45ddb2bdcf80fab9203111d8f8abb4 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 12 Nov 2008 11:33:09 -0800 Subject: Move fixtures settings from AR::TestCase to railties test_help --- activerecord/lib/active_record/test_case.rb | 10 +--------- railties/lib/test_help.rb | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index eabf06fc3b..d5f43f56e6 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -1,15 +1,7 @@ require "active_support/test_case" -module ActiveRecord +module ActiveRecord class TestCase < ActiveSupport::TestCase #:nodoc: - self.fixture_path = FIXTURES_ROOT - self.use_instantiated_fixtures = false - self.use_transactional_fixtures = true - - def create_fixtures(*table_names, &block) - Fixtures.create_fixtures(FIXTURES_ROOT, table_names, {}, &block) - end - def assert_date_from_db(expected, actual, message = nil) # SQL Server doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted diff --git a/railties/lib/test_help.rb b/railties/lib/test_help.rb index 3cc61d7932..367533cf0f 100644 --- a/railties/lib/test_help.rb +++ b/railties/lib/test_help.rb @@ -11,11 +11,22 @@ require 'action_controller/test_case' require 'action_controller/integration' require 'action_mailer/test_case' if defined?(ActionMailer) -Test::Unit::TestCase.fixture_path = RAILS_ROOT + "/test/fixtures/" -ActionController::IntegrationTest.fixture_path = Test::Unit::TestCase.fixture_path +if defined?(ActiveRecord) + require 'active_record/test_case' + require 'active_record/fixtures' -def create_fixtures(*table_names) - Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) + class ActiveSupport::TestCase + include ActiveRecord::TestFixtures + self.fixture_path = "#{RAILS_ROOT}/test/fixtures/" + self.use_instantiated_fixtures = false + self.use_transactional_fixtures = true + end + + ActionController::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path + + def create_fixtures(*table_names, &block) + Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, {}, &block) + end end begin -- cgit v1.2.3 From 334178722b8c33aba4acefe7f89e767a5660fc46 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Thu, 13 Nov 2008 21:06:11 -0800 Subject: Properly check silence_spec_warnings class variable [#1372 state:committed] Signed-off-by: David Heinemeier Hansson --- railties/lib/rails/vendor_gem_source_index.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb index dc821693ac..5b7721f303 100644 --- a/railties/lib/rails/vendor_gem_source_index.rb +++ b/railties/lib/rails/vendor_gem_source_index.rb @@ -81,7 +81,7 @@ module Rails spec.files = files else $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+ - " Giving up.") unless @silence_spec_warnings + " Giving up.") unless @@silence_spec_warnings next end end @@ -137,4 +137,4 @@ module Rails end end -end \ No newline at end of file +end -- cgit v1.2.3 From db7daa04b858f224b8b34a5dc94fe5804c1c6400 Mon Sep 17 00:00:00 2001 From: Amos King Date: Mon, 10 Nov 2008 09:26:19 -0600 Subject: Fix typo in pool_conections_test [#1350 state:committed] Signed-off-by: David Heinemeier Hansson --- activerecord/test/cases/pooled_connections_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 2a5e9509b3..2649a9358a 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -77,7 +77,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase conn_pool.checkin(conn) end - def test_not_connected_defined_connection_reutnrs_false + def test_not_connected_defined_connection_returns_false ActiveRecord::Base.establish_connection(@connection) assert ! ActiveRecord::Base.connected? end -- cgit v1.2.3 From ff4ccb8334e4f5b5bdccbd5dc6876b4e43d9565e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 14 Nov 2008 12:01:26 +0100 Subject: Revert "Move fixtures settings from AR::TestCase to railties test_help" -- it broke all the tests! This reverts commit 9a88ab64bb45ddb2bdcf80fab9203111d8f8abb4. --- activerecord/lib/active_record/test_case.rb | 10 +++++++++- railties/lib/test_help.rb | 19 ++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index d5f43f56e6..eabf06fc3b 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -1,7 +1,15 @@ require "active_support/test_case" -module ActiveRecord +module ActiveRecord class TestCase < ActiveSupport::TestCase #:nodoc: + self.fixture_path = FIXTURES_ROOT + self.use_instantiated_fixtures = false + self.use_transactional_fixtures = true + + def create_fixtures(*table_names, &block) + Fixtures.create_fixtures(FIXTURES_ROOT, table_names, {}, &block) + end + def assert_date_from_db(expected, actual, message = nil) # SQL Server doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted diff --git a/railties/lib/test_help.rb b/railties/lib/test_help.rb index 367533cf0f..3cc61d7932 100644 --- a/railties/lib/test_help.rb +++ b/railties/lib/test_help.rb @@ -11,22 +11,11 @@ require 'action_controller/test_case' require 'action_controller/integration' require 'action_mailer/test_case' if defined?(ActionMailer) -if defined?(ActiveRecord) - require 'active_record/test_case' - require 'active_record/fixtures' +Test::Unit::TestCase.fixture_path = RAILS_ROOT + "/test/fixtures/" +ActionController::IntegrationTest.fixture_path = Test::Unit::TestCase.fixture_path - class ActiveSupport::TestCase - include ActiveRecord::TestFixtures - self.fixture_path = "#{RAILS_ROOT}/test/fixtures/" - self.use_instantiated_fixtures = false - self.use_transactional_fixtures = true - end - - ActionController::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - - def create_fixtures(*table_names, &block) - Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, {}, &block) - end +def create_fixtures(*table_names) + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) end begin -- cgit v1.2.3 From 94d6716324126028b89dde886f160474049b1b0c Mon Sep 17 00:00:00 2001 From: hiroshi Date: Mon, 3 Nov 2008 14:09:07 +0900 Subject: Make polymorphic_url compact given array [#1317 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_controller/polymorphic_routes.rb | 2 +- actionpack/test/controller/polymorphic_routes_test.rb | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index e7d5031f1a..97389dfc20 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed that polymorphic_url should compact given array #1317 [hiroshi] + * Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 [antonmos/Ryan McGeary] * Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov] diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index cc228c4230..2644c7f7c7 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -73,7 +73,7 @@ module ActionController # def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) - record_or_hash_or_array = record_or_hash_or_array.dup + record_or_hash_or_array = record_or_hash_or_array.compact end record = extract_record(record_or_hash_or_array) diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 6ddf2826cd..620f2b3ab5 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -169,6 +169,17 @@ uses_mocha 'polymorphic URL helpers' do polymorphic_url([@article, :response, @tag], :format => :pdf) end + def test_nesting_with_array_containing_nil + expects(:article_response_url).with(@article) + polymorphic_url([@article, nil, :response]) + end + + def test_with_array_containing_single_object + @article.save + expects(:article_url).with(@article) + polymorphic_url([nil, @article]) + end + # TODO: Needs to be updated to correctly know about whether the object is in a hash or not def xtest_with_hash expects(:article_url).with(@article) -- cgit v1.2.3 From 2ecec6052f7f290252a9fd9cc27ec804c7aad36c Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Thu, 13 Nov 2008 20:00:11 +0000 Subject: Make inheritance of map.resources :only/:except options behave more predictably Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/resources.rb | 33 ++++++++++++--------------- actionpack/test/controller/resources_test.rb | 26 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index d6cc4aa418..7700b9d4d0 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -42,7 +42,7 @@ module ActionController # # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer module Resources - INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except + INHERITABLE_OPTIONS = :namespace, :shallow, :actions class Resource #:nodoc: DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy @@ -119,7 +119,7 @@ module ActionController end def has_action?(action) - !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + !DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action) end protected @@ -135,29 +135,24 @@ module ActionController end def set_allowed_actions - only, except = @options.values_at(:only, :except) - @allowed_actions ||= {} + only = @options.delete(:only) + except = @options.delete(:except) - if only == :all || except == :none - only = nil - except = [] + if only && except + raise ArgumentError, 'Please supply either :only or :except, not both.' + elsif only == :all || except == :none + options[:actions] = DEFAULT_ACTIONS elsif only == :none || except == :all - only = [] - except = nil - end - - if only - @allowed_actions[:only] = Array(only).map(&:to_sym) + options[:actions] = [] + elsif only + options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym) elsif except - @allowed_actions[:except] = Array(except).map(&:to_sym) + options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym) + else + # leave options[:actions] alone end end - def action_allowed?(action) - only, except = @allowed_actions.values_at(:only, :except) - (!only || only.include?(action)) && (!except || !except.include?(action)) - end - def set_prefixes @path_prefix = options.delete(:path_prefix) @name_prefix = options.delete(:name_prefix) diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1f1f7b8a2c..04f7a0a528 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -971,6 +971,32 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_nested_resource_ignores_only_option + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show do |product| + product.resources :images, :except => :destroy + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') + end + end + + def test_nested_resource_ignores_except_option + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :show do |product| + product.resources :images, :only => :destroy + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') + end + end + protected def with_restful_routing(*args) with_routing do |set| -- cgit v1.2.3 From 61e43700b85de753b6254893d5365e04d3465b9a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 14 Nov 2008 12:26:50 +0100 Subject: Prepare for RC2 --- actionmailer/CHANGELOG | 2 +- actionmailer/lib/action_mailer/version.rb | 2 +- actionpack/CHANGELOG | 4 +++- actionpack/lib/action_pack/version.rb | 2 +- activerecord/CHANGELOG | 2 +- activerecord/lib/active_record/version.rb | 2 +- activeresource/CHANGELOG | 2 +- activeresource/lib/active_resource/version.rb | 2 +- activesupport/CHANGELOG | 8 +------- activesupport/lib/active_support/version.rb | 2 +- railties/CHANGELOG | 2 +- railties/lib/rails/version.rb | 2 +- 12 files changed, 14 insertions(+), 18 deletions(-) diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 4ae7b91327..de5aeab07e 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,4 +1,4 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* * Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth] diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 9728d1b4db..7ba13c7df8 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -2,7 +2,7 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 97389dfc20..dc7ee64358 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,4 +1,6 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* + +* Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] * Fixed that polymorphic_url should compact given array #1317 [hiroshi] diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 288b62778e..126d16e5f4 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4ca062b535..c2299b56ad 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,4 +1,4 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 2479b75789..3c5a9b7df8 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 114a63c415..e05a634a12 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,4 +1,4 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* * Fixed that ActiveResource#post would post an empty string when it shouldn't be posting anything #525 [Paolo Angelini] diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index d56f4cf17e..3747ffc63c 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -2,7 +2,7 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 1475586cde..3526c2e8fc 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,10 +1,4 @@ -*2.2.1 [RC2 or 2.2 final]* - -* TimeZone: fix offset for Sri Jayawardenepura. Anchor tests for zone offsets to more current date [Geoff Buesing] - -* TimeZone: Caracas GMT offset changed to -4:30 [#1361 state:resolved] [Phil Ross] - -* Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] +*2.2.1 [RC2] (November 14th, 2008)* * Fixed the option merging in Array#to_xml #1126 [Rudolf Gavlas] diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 8f5395fca6..6631f233f1 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -2,7 +2,7 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 058afddbde..ae20cb50da 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,4 +1,4 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* * Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements #1137 [Mathias Meyer] diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index a0986a2e05..bd835fba26 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end -- cgit v1.2.3 From 549b18c9286b6cccf4978093576325fd711dc421 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 14 Nov 2008 17:09:40 +0530 Subject: Rails now requires rubygems 1.3.1 of higher. --- railties/environments/boot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/environments/boot.rb b/railties/environments/boot.rb index 6a30b54973..57c256e438 100644 --- a/railties/environments/boot.rb +++ b/railties/environments/boot.rb @@ -82,7 +82,7 @@ module Rails def load_rubygems require 'rubygems' - min_version = '1.1.1' + min_version = '1.3.1' unless rubygems_version >= min_version $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 -- cgit v1.2.3