aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--RELEASING_RAILS.rdoc18
-rw-r--r--actionmailer/CHANGELOG.md16
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb4
-rw-r--r--actionmailer/test/delivery_methods_test.rb13
-rw-r--r--actionpack/CHANGELOG.md192
-rw-r--r--actionpack/README.rdoc285
-rw-r--r--actionpack/examples/performance.rb185
-rw-r--r--actionpack/examples/views/_collection.erb3
-rw-r--r--actionpack/examples/views/_hello.erb1
-rw-r--r--actionpack/examples/views/_hundred_partials.erb3
-rw-r--r--actionpack/examples/views/_partial.erb10
-rw-r--r--actionpack/examples/views/_ten_partials.erb10
-rw-r--r--actionpack/examples/views/hashes/_hash.erb3
-rw-r--r--actionpack/examples/views/my_hashes/_my_hash.erb3
-rw-r--r--actionpack/examples/views/template.html.erb1
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb44
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb4
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb31
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb10
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb21
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb6
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb62
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb38
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb15
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb32
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb40
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb14
-rw-r--r--actionpack/lib/action_view/template/error.rb4
-rw-r--r--actionpack/test/abstract_unit.rb91
-rw-r--r--actionpack/test/controller/caching_test.rb42
-rw-r--r--actionpack/test/controller/resources_test.rb29
-rw-r--r--actionpack/test/dispatch/cookies_test.rb2
-rw-r--r--actionpack/test/dispatch/live_response_test.rb17
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb18
-rw-r--r--actionpack/test/dispatch/response_test.rb27
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb82
-rw-r--r--actionpack/test/fixtures/test/_raise_indentation.html.erb13
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb9
-rw-r--r--actionpack/test/template/date_helper_test.rb29
-rw-r--r--actionpack/test/template/form_helper_test.rb20
-rw-r--r--actionpack/test/template/form_options_helper_test.rb20
-rw-r--r--actionpack/test/template/javascript_helper_test.rb42
-rw-r--r--actionpack/test/template/render_test.rb9
-rw-r--r--actionpack/test/template/sanitize_helper_test.rb4
-rw-r--r--activemodel/CHANGELOG.md25
-rw-r--r--activemodel/lib/active_model/callbacks.rb11
-rw-r--r--activemodel/lib/active_model/naming.rb17
-rwxr-xr-x[-rw-r--r--]activemodel/lib/active_model/serializers/xml.rb4
-rwxr-xr-x[-rw-r--r--]activemodel/test/cases/serializers/xml_serialization_test.rb38
-rw-r--r--activerecord/CHANGELOG.md112
-rw-r--r--activerecord/lib/active_record/associations.rb27
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb91
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb78
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb71
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb49
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb34
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb46
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb19
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb41
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb61
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/multiple_db_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/cases/timestamp_test.rb28
-rw-r--r--activerecord/test/cases/transactions_test.rb17
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/company.rb16
-rw-r--r--activerecord/test/models/member.rb5
-rw-r--r--activerecord/test/support/connection.rb4
-rw-r--r--activesupport/CHANGELOG.md52
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/indent.rb43
-rw-r--r--activesupport/lib/active_support/duration.rb2
-rw-r--r--activesupport/lib/active_support/json/encoding.rb1
-rw-r--r--activesupport/lib/active_support/json/variable.rb17
-rw-r--r--activesupport/lib/active_support/locale/en.yml10
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb6
-rw-r--r--activesupport/lib/active_support/number_helper.rb161
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb18
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb56
-rw-r--r--activesupport/test/json/encoding_test.rb7
-rw-r--r--activesupport/test/notifications/evented_notification_test.rb20
-rw-r--r--activesupport/test/number_helper_i18n_test.rb42
-rw-r--r--activesupport/test/number_helper_test.rb7
-rw-r--r--activesupport/test/string_inquirer_test.rb14
-rw-r--r--guides/source/4_0_release_notes.textile35
-rw-r--r--guides/source/action_controller_overview.textile4
-rw-r--r--guides/source/action_mailer_basics.textile22
-rw-r--r--guides/source/active_model_basics.textile12
-rw-r--r--guides/source/active_record_querying.textile3
-rw-r--r--guides/source/active_support_core_extensions.textile35
-rw-r--r--guides/source/api_documentation_guidelines.textile2
-rw-r--r--guides/source/asset_pipeline.textile2
-rw-r--r--guides/source/association_basics.textile18
-rw-r--r--guides/source/caching_with_rails.textile4
-rw-r--r--guides/source/configuring.textile8
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile111
-rw-r--r--guides/source/engines.textile78
-rw-r--r--guides/source/i18n.textile2
-rw-r--r--guides/source/initialization.textile14
-rw-r--r--guides/source/kindle/KINDLE.md6
-rw-r--r--guides/source/kindle/welcome.html.erb2
-rw-r--r--guides/source/performance_testing.textile3
-rw-r--r--guides/source/plugins.textile14
-rw-r--r--guides/source/routing.textile30
-rw-r--r--railties/CHANGELOG.md7
-rw-r--r--railties/lib/rails/console/app.rb3
-rw-r--r--railties/lib/rails/engine.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/locales/en.yml22
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt7
-rw-r--r--railties/lib/rails/tasks/tmp.rake12
-rw-r--r--railties/test/application/assets_test.rb26
-rw-r--r--railties/test/generators/app_generator_test.rb11
-rw-r--r--railties/test/isolation/abstract_unit.rb1
-rw-r--r--railties/test/railties/engine_test.rb48
-rw-r--r--railties/test/railties/mounted_engine_test.rb15
140 files changed, 2114 insertions, 1424 deletions
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index bdf0ffc2d0..af1def223a 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -25,10 +25,10 @@ for Rails. You can check the status of his tests here:
Do not release with Red AWDwR tests.
-=== Do we have any git dependencies? If so, contact those authors.
+=== Do we have any Git dependencies? If so, contact those authors.
-Having git dependencies indicates that we depend on unreleased code.
-Obviously rails cannot be released when it depends on unreleased code.
+Having Git dependencies indicates that we depend on unreleased code.
+Obviously Rails cannot be released when it depends on unreleased code.
Contact the authors of those particular gems and work out a release date that
suits them.
@@ -115,14 +115,14 @@ what to do in case anything goes wrong:
=== Send Rails release announcements
Write a release announcement that includes the version, changes, and links to
-github where people can find the specific commit list. Here are the mailing
+GitHub where people can find the specific commit list. Here are the mailing
lists where you should announce:
* rubyonrails-core@googlegroups.com
* rubyonrails-talk@googlegroups.com
* ruby-talk@ruby-lang.org
-Use markdown format for your announcement. Remember to ask people to report
+Use Markdown format for your announcement. Remember to ask people to report
issues with the release candidate to the rails-core mailing list.
IMPORTANT: If any users experience regressions when using the release
@@ -131,16 +131,16 @@ break existing applications.
=== Post the announcement to the Rails blog.
-If you used markdown format for your email, you can just paste it in to the
+If you used Markdown format for your email, you can just paste it in to the
blog.
* http://weblog.rubyonrails.org
-=== Post the announcement to the Rails twitter account.
+=== Post the announcement to the Rails Twitter account.
== Time between release candidate and actual release
-Check the rails-core mailing list and the github issue list for regressions in
+Check the rails-core mailing list and the GitHub issue list for regressions in
the RC.
If any regressions are found, fix the regressions and repeat the release
@@ -167,7 +167,7 @@ Today, do this stuff in this order:
* Email security lists
* Email general announcement lists
-=== Emailing the rails security announce list
+=== Emailing the Rails security announce list
Email the security announce list once for each vulnerability fixed.
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 96cfb43e0b..d2b8c35124 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -6,6 +6,22 @@
* Asynchronously send messages via the Rails Queue *Brian Cardarella*
+
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 73ac8ea12b..900da9697e 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -683,7 +683,7 @@ module ActionMailer #:nodoc:
m.charset = charset = headers[:charset]
# Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:perform_deliveries))
+ wrap_delivery_behavior!(headers.delete(:delivery_method))
# Assign all headers except parts_order, content_type and body
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 0b8d0019eb..3b38dbccc7 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -57,7 +57,7 @@ module ActionMailer
self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
end
- def wrap_delivery_behavior(mail, method=nil, perform_deliveries_header=nil) #:nodoc:
+ def wrap_delivery_behavior(mail, method=nil) #:nodoc:
method ||= self.delivery_method
mail.delivery_handler = self
@@ -74,7 +74,7 @@ module ActionMailer
mail.delivery_method(method)
end
- mail.perform_deliveries = perform_deliveries && (perform_deliveries_header.nil? || perform_deliveries_header)
+ mail.perform_deliveries = perform_deliveries
mail.raise_delivery_errors = raise_delivery_errors
end
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 886e79aae9..08f84dbf3b 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -82,7 +82,6 @@ class MailDeliveryTest < ActiveSupport::TestCase
def welcome(hash={})
mail(DEFAULT_HEADERS.merge(hash))
end
-
end
def setup
@@ -130,18 +129,6 @@ class MailDeliveryTest < ActiveSupport::TestCase
DeliveryMailer.welcome.deliver
end
- test "does not perform deliveries if customized per instance" do
- DeliveryMailer.perform_deliveries = true
- m = DeliveryMailer.welcome(:perform_deliveries => false)
- assert_equal(false,m.perform_deliveries)
- end
-
- test "does not perform deliveries if globally set to off but instance instructs delivery" do
- DeliveryMailer.perform_deliveries = false
- m = DeliveryMailer.welcome(:perform_deliveries => true)
- assert_equal(false,m.perform_deliveries)
- end
-
test "does not append the deliveries collection if told not to perform the delivery" do
DeliveryMailer.perform_deliveries = false
DeliveryMailer.deliveries.clear
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4b4658a82b..516fcbe62f 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,88 @@
## Rails 4.0.0 (unreleased) ##
+* Add Request#formats=(extensions) that lets you set multiple formats directly in a prioritized order *DHH*
+
+ Example of using this for custom iphone views with an HTML fallback:
+
+ class ApplicationController < ActionController::Base
+ before_filter :adjust_format_for_iphone_with_html_fallback
+
+ private
+ def adjust_format_for_iphone_with_html_fallback
+ request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
+ end
+ end
+
+
+* Add Routing Concerns to declare common routes that can be reused inside
+ others resources and routes.
+
+ Code before:
+
+ resources :messages do
+ resources :comments
+ end
+
+ resources :posts do
+ resources :comments
+ resources :images, only: :index
+ end
+
+ Code after:
+
+ concern :commentable do
+ resources :comments
+ end
+
+ concern :image_attachable do
+ resources :images, only: :index
+ end
+
+ resources :messages, concerns: :commentable
+
+ resources :posts, concerns: [:commentable, :image_attachable]
+
+ *DHH + Rafael Mendonça França*
+
+* Add start_hour and end_hour options to the select_hour helper. *Evan Tann*
+
+* Raises an ArgumentError when the first argument in `form_for` contain `nil`
+ or is empty.
+
+ *Richard Schneeman*
+
+* Add 'X-Frame-Options' => 'SAMEORIGIN' and
+ 'X-XSS-Protection' => '1; mode=block'
+ as default headers.
+
+ *Egor Homakov*
+
+* Allow data attributes to be set as a first-level option for form_for, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH*
+
+* Deprecate `button_to_function` and `link_to_function` helpers.
+
+ We recommend the use of Unobtrusive JavaScript instead. For example:
+
+ link_to "Greeting", "#", :class => "nav_link"
+
+ $(function() {
+ $('.nav_link').click(function() {
+ // Some complex code
+
+ return false;
+ });
+ });
+
+ or
+
+ link_to "Greeting", '#', onclick: "alert('Hello world!'); return false", class: "nav_link"
+
+ for simple cases.
+
+ *Rafael Mendonça França*
+
+* `javascript_include_tag :all` will now not include `application.js` if the file does not exists. *Prem Sichanugrist*
+
* Send an empty response body when call `head` with status between 100 and 199, 204, 205 or 304.
*Armand du Plessis*
@@ -9,18 +92,18 @@
* Added ActionController::Live. Mix it in to your controller and you can
stream data to the client live. For example:
- class FooController < ActionController::Base
- include ActionController::Live
+ class FooController < ActionController::Base
+ include ActionController::Live
- def index
- 100.times {
- # Client will see this as it's written
- response.stream.write "hello world\n"
- sleep 1
- }
- response.stream.close
+ def index
+ 100.times {
+ # Client will see this as it's written
+ response.stream.write "hello world\n"
+ sleep 1
+ }
+ response.stream.close
+ end
end
- end
* Remove ActionDispatch::Head middleware in favor of Rack::Head. *Santiago Pastorino*
@@ -144,8 +227,6 @@
* Replace `include_seconds` boolean argument with `:include_seconds => true` option
in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko*
-* Remove `button_to_function` and `link_to_function` helpers. *Rafael Mendonça França*
-
* Make current object and counter (when it applies) variables accessible when
rendering templates with :object / :collection. *Carlos Antonio da Silva*
@@ -231,13 +312,13 @@
* Add `collection_check_boxes` form helper, similar to `collection_select`:
Example:
- collection_check_boxes :post, :author_ids, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
- <label for="post_author_ids_1">D. Heinemeier Hansson</label>
- <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
- <label for="post_author_ids_2">D. Thomas</label>
- <input name="post[author_ids][]" type="hidden" value="" />
+ collection_check_boxes :post, :author_ids, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
+ <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ <label for="post_author_ids_2">D. Thomas</label>
+ <input name="post[author_ids][]" type="hidden" value="" />
The label/check_box pairs can be customized with a block.
@@ -246,12 +327,12 @@
* Add `collection_radio_buttons` form helper, similar to `collection_select`:
Example:
- collection_radio_buttons :post, :author_id, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
- <label for="post_author_id_1">D. Heinemeier Hansson</label>
- <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
- <label for="post_author_id_2">D. Thomas</label>
+ collection_radio_buttons :post, :author_id, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
+ <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ <label for="post_author_id_2">D. Thomas</label>
The label/radio_button pairs can be customized with a block.
@@ -295,6 +376,67 @@
HTML5 `mark` element. *Brian Cardarella*
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* There is an XSS vulnerability in the strip_tags helper in Ruby on Rails, the
+ helper doesn't correctly handle malformed html. As a result an attacker can
+ execute arbitrary javascript through the use of specially crafted malformed
+ html.
+
+ *Marek from Nethemba (www.nethemba.com) & Santiago Pastorino*
+
+* When a "prompt" value is supplied to the `select_tag` helper, the "prompt" value is not escaped.
+ If untrusted data is not escaped, and is supplied as the prompt value, there is a potential for XSS attacks.
+ Vulnerable code will look something like this:
+ select_tag("name", options, :prompt => UNTRUSTED_INPUT)
+
+ *Santiago Pastorino*
+
+* Reverted the deprecation of `:confirm`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `:disable_with`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `:mouseover` option to `image_tag`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `button_to_function` and `link_to_function` helpers.
+
+ *Rafael Mendonça França*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* Do not convert digest auth strings to symbols. CVE-2012-3424
+
+* Bump Journey requirements to 1.0.4
+
+* Add support for optional root segments containing slashes
+
+* Fixed bug creating invalid HTML in select options
+
+* Show in log correct wrapped keys
+
+* Fix NumberHelper options wrapping to prevent verbatim blocks being rendered instead of line continuations.
+
+* ActionController::Metal doesn't have logger method, check it and then delegate
+
+* ActionController::Caching depends on RackDelegation and AbstractController::Callbacks
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* nil is removed from array parameter values
+
+ CVE-2012-2694
+
+* Deprecate `:confirm` in favor of `':data => { :confirm => "Text" }'` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers.
+
+ *Carlos Galdino*
+
+* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
+
+* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki*
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 1fdc57e14d..ccd0193515 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -28,291 +28,6 @@ by default and Action View rendering is implicitly triggered by Action
Controller. However, these modules are designed to function on their own and
can be used outside of Rails.
-A short rundown of some of the major features:
-
-* Actions grouped in controller as methods instead of separate command objects
- and can therefore share helper methods
-
- class CustomersController < ActionController::Base
- def show
- @customer = find_customer
- end
-
- def update
- @customer = find_customer
- if @customer.update_attributes(params[:customer])
- redirect_to :action => "show"
- else
- render :action => "edit"
- end
- end
-
- private
- def find_customer
- Customer.find params[:id]
- end
- end
-
- {Learn more}[link:classes/ActionController/Base.html]
-
-
-* ERB templates (static content mixed with dynamic output from ruby)
-
- <% @posts.each do |post| %>
- Title: <%= post.title %>
- <% end %>
-
- All post titles: <%= @posts.collect{ |p| p.title }.join(", ") %>
-
- <% unless @person.is_client? %>
- Not for clients to see...
- <% end %>
-
- {Learn more}[link:classes/ActionView.html]
-
-
-* "Builder" templates (great for XML content, like RSS)
-
- xml.rss("version" => "2.0") do
- xml.channel do
- xml.title(@feed_title)
- xml.link(@url)
- xml.description "Basecamp: Recent items"
- xml.language "en-us"
- xml.ttl "40"
-
- @recent_items.each do |item|
- xml.item do
- xml.title(item_title(item))
- xml.description(item_description(item))
- xml.pubDate(item_pubDate(item))
- xml.guid(@recent_items.url(item))
- xml.link(@recent_items.url(item))
- end
- end
- end
- end
-
- {Learn more}[link:classes/ActionView/Base.html]
-
-
-* Filters for pre- and post-processing of the response
-
- class WeblogController < ActionController::Base
- # filters as methods
- before_filter :authenticate, :cache, :audit
-
- # filter as a proc
- after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
-
- # class filter
- after_filter LocalizeFilter
-
- def index
- # Before this action is run, the user will be authenticated, the cache
- # will be examined to see if a valid copy of the results already
- # exists, and the action will be logged for auditing.
-
- # After this action has run, the output will first be localized then
- # compressed to minimize bandwidth usage
- end
-
- private
- def authenticate
- # Implement the filter with full access to both request and response
- end
- end
-
- {Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
-
-
-* Helpers for forms, dates, action links, and text
-
- <%= text_field_tag "post", "title", "size" => 30 %>
- <%= link_to "New post", :controller => "post", :action => "new" %>
- <%= truncate(post.title, :length => 25) %>
-
- {Learn more}[link:classes/ActionView/Helpers.html]
-
-
-* Layout sharing for template reuse
-
- class WeblogController < ActionController::Base
- layout "weblog_layout"
-
- def hello_world
- end
- end
-
- Layout file (called weblog_layout):
- <html><body><%= yield %></body></html>
-
- Template for hello_world action:
- <h1>Hello world</h1>
-
- Result of running hello_world action:
- <html><body><h1>Hello world</h1></body></html>
-
- {Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
-
-
-* Routing makes pretty URLs incredibly easy
-
- match 'clients/:client_name/:project_name/:controller/:action'
-
- Accessing "/clients/37signals/basecamp/project/index" calls ProjectController#index with
- { "client_name" => "37signals", "project_name" => "basecamp" } in `params`
-
- From that action, you can write the redirect in a number of ways:
-
- redirect_to(:action => "edit") =>
- /clients/37signals/basecamp/project/edit
-
- redirect_to(:client_name => "nextangle", :project_name => "rails") =>
- /clients/nextangle/rails/project/index
-
- {Learn more}[link:classes/ActionDispatch/Routing.html]
-
-
-* Easy testing of both controller and rendered template through ActionController::TestCase
-
- class LoginControllerTest < ActionController::TestCase
- def test_failing_authenticate
- process :authenticate, :user_name => "nop", :password => ""
- assert flash.has_key?(:alert)
- assert_redirected_to :action => "index"
- end
- end
-
- {Learn more}[link:classes/ActionController/TestCase.html]
-
-
-* Automated benchmarking and integrated logging
-
- Started GET "/weblog" for 127.0.0.1 at Fri May 28 00:41:55
- Processing by WeblogController#index as HTML
- Rendered weblog/index.html.erb within layouts/application (25.7ms)
- Completed 200 OK in 29.3ms
-
- If Active Record is used as the model, you'll have the database debugging
- as well:
-
- Started POST "/posts" for 127.0.0.1 at Sat Jun 19 14:04:23
- Processing by PostsController#create as HTML
- Parameters: {"post"=>{"title"=>"this is good"}}
- SQL (0.6ms) INSERT INTO posts (title) VALUES('this is good')
- Redirected to http://example.com/posts/5
- Completed 302 Found in 221ms (Views: 215ms | ActiveRecord: 0.6ms)
-
- You specify a logger through a class method, such as:
-
- ActionController::Base.logger = ActiveSupport::Logger.new("Application Log")
- ActionController::Base.logger = Log4r::Logger.new("Application Log")
-
-
-* Caching at three levels of granularity (page, action, fragment)
-
- class WeblogController < ActionController::Base
- caches_page :show
- caches_action :account
-
- def show
- # the output of the method will be cached as
- # ActionController::Base.page_cache_directory + "/weblog/show.html"
- # and the web server will pick it up without even hitting Rails
- end
-
- def account
- # the output of the method will be cached in the fragment store
- # but Rails is hit to retrieve it, so filters are run
- end
-
- def update
- List.update(params[:list][:id], params[:list])
- expire_page :action => "show", :id => params[:list][:id]
- expire_action :action => "account"
- redirect_to :action => "show", :id => params[:list][:id]
- end
- end
-
- {Learn more}[link:classes/ActionController/Caching.html]
-
-
-* Powerful debugging mechanism for local requests
-
- All exceptions raised on actions performed on the request of a local user
- will be presented with a tailored debugging screen that includes exception
- message, stack trace, request parameters, session contents, and the
- half-finished response.
-
- {Learn more}[link:classes/ActionController/Rescue.html]
-
-
-== Simple example (from outside of Rails)
-
-This example will implement a simple weblog system using inline templates and
-an Active Record model. So let's build that WeblogController with just a few
-methods:
-
- require 'action_controller'
- require 'post'
-
- class WeblogController < ActionController::Base
- layout "weblog/layout"
-
- def index
- @posts = Post.all
- end
-
- def show
- @post = Post.find(params[:id])
- end
-
- def new
- @post = Post.new
- end
-
- def create
- @post = Post.create(params[:post])
- redirect_to :action => "show", :id => @post.id
- end
- end
-
- WeblogController::Base.view_paths = [ File.dirname(__FILE__) ]
- WeblogController.process_cgi if $0 == __FILE__
-
-The last two lines are responsible for telling ActionController where the
-template files are located and actually running the controller on a new
-request from the web-server (e.g., Apache).
-
-And the templates look like this:
-
- weblog/layout.html.erb:
- <html><body>
- <%= yield %>
- </body></html>
-
- weblog/index.html.erb:
- <% @posts.each do |post| %>
- <p><%= link_to(post.title, :action => "show", :id => post.id) %></p>
- <% end %>
-
- weblog/show.html.erb:
- <p>
- <b><%= @post.title %></b><br/>
- <b><%= @post.content %></b>
- </p>
-
- weblog/new.html.erb:
- <%= form "post" %>
-
-This simple setup will list all the posts in the system on the index page,
-which is called by accessing /weblog/. It uses the form builder for the Active
-Record model to make the new screen, which in turn hands everything over to
-the create action (that's the default target for the form builder when given a
-new model). After creating the post, it'll redirect to the show page using
-an URL such as /weblog/5 (where 5 is the id of the post).
-
== Download and installation
diff --git a/actionpack/examples/performance.rb b/actionpack/examples/performance.rb
deleted file mode 100644
index 8ea4758961..0000000000
--- a/actionpack/examples/performance.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-ENV['RAILS_ENV'] ||= 'production'
-
-require File.expand_path('../../../load_paths', __FILE__)
-require 'action_pack'
-require 'action_controller'
-require 'action_view'
-require 'active_model'
-require 'benchmark'
-
-MyHash = Class.new(Hash)
-
-Hash.class_eval do
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-end
-
-class Runner
- def initialize(app, output)
- @app, @output = app, output
- end
-
- def puts(*)
- super if @output
- end
-
- def call(env)
- env['n'].to_i.times { @app.call(env) }
- @app.call(env).tap { |response| report(env, response) }
- end
-
- def report(env, response)
- return unless ENV["DEBUG"]
- out = env['rack.errors']
- out.puts response[0], response[1].to_yaml, '---'
- response[2].each { |part| out.puts part }
- out.puts '---'
- end
-
- def self.puts(*)
- super if @output
- end
-
- def self.print(*)
- super if @output
- end
-
- def self.app_and_env_for(action, n)
- env = Rack::MockRequest.env_for("/")
- env.merge!('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
- app = lambda { |env| BasePostController.action(action).call(env) }
- return app, env
- end
-
- $ran = []
-
- def self.run(action, n, output = true)
- print "."
- STDOUT.flush
- @output = output
- label = action.to_s
- app, env = app_and_env_for(action, n)
- t = Benchmark.realtime { new(app, output).call(env) }
- $ran << [label, (t * 1000).to_i.to_s] if output
- end
-
- def self.done
- puts
- header, content = "", ""
- $ran.each do |k,v|
- size = [k.size, v.size].max + 1
- header << format("%#{size}s", k)
- content << format("%#{size}s", v)
- end
- puts header
- puts content
- end
-end
-
-ActionController::Base.logger = nil
-ActionController::Base.config.compile_methods!
-ActionView::Resolver.caching = ENV["RAILS_ENV"] == "production"
-
-class BasePostController < ActionController::Base
- append_view_path "#{File.dirname(__FILE__)}/views"
-
- def overhead
- self.response_body = ''
- end
-
- def index
- render :text => ''
- end
-
- $OBJECT = {:name => "Hello my name is omg", :address => "333 omg"}
-
- def partial
- render :partial => "/collection", :object => $OBJECT
- end
-
- def partial_10
- render :partial => "/ten_partials"
- end
-
- def partial_100
- render :partial => "/hundred_partials"
- end
-
- $COLLECTION1 = []
- 10.times do |i|
- $COLLECTION1 << { :name => "Hello my name is omg", :address => "333 omg" }
- end
-
- def coll_10
- render :partial => "/collection", :collection => $COLLECTION1
- end
-
- $COLLECTION2 = []
- 100.times do |i|
- $COLLECTION2 << { :name => "Hello my name is omg", :address => "333 omg" }
- end
-
- def coll_100
- render :partial => "/collection", :collection => $COLLECTION2
- end
-
- def uniq_100
- render :partial => $COLLECTION2
- end
-
- $COLLECTION3 = []
- 50.times do |i|
- $COLLECTION3 << {:name => "Hello my name is omg", :address => "333 omg"}
- $COLLECTION3 << MyHash.new(:name => "Hello my name is omg", :address => "333 omg")
- end
-
- def diff_100
- render :partial => $COLLECTION3
- end
-
- def template_1
- render :template => "template"
- end
-
- module Foo
- def omg
- "omg"
- end
- end
-
- helper Foo
-end
-
-N = (ENV['N'] || 1000).to_i
-# ActionController::Base.use_accept_header = false
-
-def run_all!(times, verbose)
- Runner.run(:overhead, times, verbose)
- Runner.run(:index, times, verbose)
- Runner.run(:template_1, times, verbose)
- Runner.run(:partial, times, verbose)
- Runner.run(:partial_10, times, verbose)
- Runner.run(:coll_10, times, verbose)
- Runner.run(:partial_100, times, verbose)
- Runner.run(:coll_100, times, verbose)
- Runner.run(:uniq_100, times, verbose)
- Runner.run(:diff_100, times, verbose)
-end
-
-if ENV["PROFILE"]
- Runner.run(ENV["PROFILE"].to_sym, 1, false)
- require "ruby-prof"
- RubyProf.start
- Runner.run(ENV["PROFILE"].to_sym, N, true)
- result = RubyProf.stop
- printer = RubyProf::CallStackPrinter.new(result)
- printer.print(File.open("output.html", "w"))
-else
- run_all!(1, false)
-
- (ENV["M"] || 1).to_i.times do
- $ran = []
- run_all!(N, true)
- Runner.done
- end
-end \ No newline at end of file
diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb
deleted file mode 100644
index cee3fe64c0..0000000000
--- a/actionpack/examples/views/_collection.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<%= collection[:name] %>
-<%= collection[:address] %>
-<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb
deleted file mode 100644
index 5ab2f8a432..0000000000
--- a/actionpack/examples/views/_hello.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello \ No newline at end of file
diff --git a/actionpack/examples/views/_hundred_partials.erb b/actionpack/examples/views/_hundred_partials.erb
deleted file mode 100644
index 35c2a6c9d3..0000000000
--- a/actionpack/examples/views/_hundred_partials.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<% 100.times do %>
- <%= render :partial => "/collection", :object => $OBJECT %>
-<% end %> \ No newline at end of file
diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb
deleted file mode 100644
index 3ca8e80b52..0000000000
--- a/actionpack/examples/views/_partial.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
diff --git a/actionpack/examples/views/_ten_partials.erb b/actionpack/examples/views/_ten_partials.erb
deleted file mode 100644
index fd02991e22..0000000000
--- a/actionpack/examples/views/_ten_partials.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %>
-<%= render :partial => '/collection', :object => $OBJECT %> \ No newline at end of file
diff --git a/actionpack/examples/views/hashes/_hash.erb b/actionpack/examples/views/hashes/_hash.erb
deleted file mode 100644
index c100a290bd..0000000000
--- a/actionpack/examples/views/hashes/_hash.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<%= hash[:name] %>
-<%= hash[:address] %>
-<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/my_hashes/_my_hash.erb b/actionpack/examples/views/my_hashes/_my_hash.erb
deleted file mode 100644
index e25d84101a..0000000000
--- a/actionpack/examples/views/my_hashes/_my_hash.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<%= my_hash[:name] %>
-<%= my_hash[:address] %>
-<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb
deleted file mode 100644
index 5ab2f8a432..0000000000
--- a/actionpack/examples/views/template.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello \ No newline at end of file
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index abeb49d16f..9c77b0ccf4 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -5,48 +5,18 @@ module ActionController #:nodoc:
# useful when certain elements of an action change frequently or
# depend on complicated state while other parts rarely change or
# can be shared amongst multiple parties. The caching is done using
- # the <tt>cache</tt> helper available in the Action View. A
- # template with fragment caching might look like:
+ # the <tt>cache</tt> helper available in the Action View. See
+ # ActionView::Helpers::CacheHelper for more information.
#
- # <b>Hello <%= @name %></b>
+ # While it's strongly recommended that you use key-based cache
+ # expiration (see links in CacheHelper for more information),
+ # it is also possible to manually expire caches. For example:
#
- # <% cache do %>
- # All the topics in the system:
- # <%= render :partial => "topic", :collection => Topic.all %>
- # <% end %>
- #
- # This cache will bind the name of the action that called it, so if
- # this code was part of the view for the topics/list action, you
- # would be able to invalidate it using:
- #
- # expire_fragment(:controller => "topics", :action => "list")
- #
- # This default behavior is limited if you need to cache multiple
- # fragments per action or if the action itself is cached using
- # <tt>caches_action</tt>. To remedy this, there is an option to
- # qualify the name of the cached fragment by using the
- # <tt>:action_suffix</tt> option:
- #
- # <% cache(:action => "list", :action_suffix => "all_topics") do %>
- #
- # That would result in a name such as
- # <tt>/topics/list/all_topics</tt>, avoiding conflicts with the
- # action cache and with any fragments that use a different suffix.
- # Note that the URL doesn't have to really exist or be callable
- # - the url_for system is just used to generate unique cache names
- # that we can refer to when we need to expire the cache.
- #
- # The expiration call for this example is:
- #
- # expire_fragment(:controller => "topics",
- # :action => "list",
- # :action_suffix => "all_topics")
+ # expire_fragment("name_of_cache")
module Fragments
# Given a key (as described in <tt>expire_fragment</tt>), returns
# a key suitable for use in reading, writing, or expiring a
- # cached fragment. If the key is a hash, the generated key is the
- # return value of url_for on that hash (without the protocol).
- # All keys are prefixed with <tt>views/</tt> and uses
+ # cached fragment. All keys are prefixed with <tt>views/</tt> and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 379ff97048..5422cb93c4 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -76,8 +76,8 @@ module ActionController #:nodoc:
end
# Avoid having to pass an open file handle as the response body.
- # Rack::Sendfile will usually intercepts the response and just uses
- # the path directly, so no reason to open the file.
+ # Rack::Sendfile will usually intercept the response and uses
+ # the path directly, so there is no reason to open the file.
class FileBody #:nodoc:
attr_reader :to_path
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 77d799a38a..e905a3cf1d 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -32,7 +32,7 @@ module ActionController
# ==== Options
# * <tt>host</tt> - Redirect to a different host name
# * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except<tt> - The callback should be run for all actions except this action
+ # * <tt>except</tt> - The callback should be run for all actions except this action
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a true value.
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 43a9e3aa9d..32e5afa335 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -33,13 +33,13 @@ module ActionController
module Live
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
- super(response, Queue.new)
+ super(response, SizedQueue.new(10))
end
def write(string)
unless @response.committed?
@response.headers["Cache-Control"] = "no-cache"
- @response.headers.delete("Content-Length")
+ @response.headers.delete "Content-Length"
end
super
@@ -47,13 +47,13 @@ module ActionController
def each
while str = @buf.pop
- yield(str)
+ yield str
end
end
def close
super
- @buf.push(nil)
+ @buf.push nil
end
end
@@ -72,16 +72,15 @@ module ActionController
super
end
+ def merge(other)
+ self.class.new @response, __getobj__.merge(other)
+ end
+
def to_hash
__getobj__.dup
end
end
- def initialize(status = 200, header = {}, body = [])
- header = Header.new(self, header)
- super(status, header, body)
- end
-
def commit!
headers.freeze
super
@@ -89,11 +88,15 @@ module ActionController
private
- def build_buffer(response, body)
- buf = Live::Buffer.new(response)
- body.each { |part| buf.write(part) }
- buf
- end
+ def build_buffer(response, body)
+ buf = Live::Buffer.new response
+ body.each { |part| buf.write part }
+ buf
+ end
+
+ def merge_default_headers(original, default)
+ Header.new self, super
+ end
end
def process(name)
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index dd5ceb3478..0cdd17bc2e 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,15 @@ module ActionController
:_recall => request.symbolized_path_parameters
).freeze
- if _routes.equal?(env["action_dispatch.routes"])
+ if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
+ (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
+ (original_script_name = env['SCRIPT_NAME'])
@_url_options.dup.tap do |options|
- options[:script_name] = request.script_name.dup
+ if original_script_name
+ options[:original_script_name] = original_script_name
+ else
+ options[:script_name] = same_origin ? request.script_name.dup : script_name
+ end
options.freeze
end
else
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index cc81970ddc..b382997052 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -26,7 +26,6 @@ require 'active_support/rails'
require 'active_support/core_ext/module/attribute_accessors'
require 'action_pack'
-require 'active_model'
require 'rack'
module Rack
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index e31f3b823d..0f98e84788 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -80,6 +80,27 @@ module ActionDispatch
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
end
+ # Sets the \formats by string extensions. This differs from #format= by allowing you
+ # to set multiple, ordered formats, which is useful when you want to have a fallback.
+ #
+ # In this example, the :iphone format will be used if it's available, otherwise it'll fallback
+ # to the :html format.
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :adjust_format_for_iphone_with_html_fallback
+ #
+ # private
+ # def adjust_format_for_iphone_with_html_fallback
+ # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
+ # end
+ # end
+ def formats=(extensions)
+ parameters[:format] = extensions.first.to_s
+ @env["action_dispatch.request.formats"] = extensions.collect do |extension|
+ Mime::Type.lookup_by_extension(extension)
+ end
+ end
+
# Receives an array of mimes and return the first user sent mime that
# matches the order array.
#
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index bcfd0b0d00..9a7b5bc8c7 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -4,6 +4,11 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module Http
module Parameters
+ def initialize(env)
+ super
+ @symbolized_path_params = nil
+ end
+
# Returns both GET and POST \parameters in a single hash.
def parameters
@env["action_dispatch.request.parameters"] ||= begin
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 1377e53ce8..d24c7c7f3f 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -38,6 +38,17 @@ module ActionDispatch
METHOD
end
+ def initialize(env)
+ super
+ @method = nil
+ @request_method = nil
+ @remote_ip = nil
+ @original_fullpath = nil
+ @fullpath = nil
+ @ip = nil
+ @uuid = nil
+ end
+
def key?(key)
@env.key?(key)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index d336808e7c..11b7534ea4 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -58,6 +58,7 @@ module ActionDispatch # :nodoc:
LOCATION = "Location".freeze
cattr_accessor(:default_charset) { "utf-8" }
+ cattr_accessor(:default_headers)
include Rack::Response::Helpers
include ActionDispatch::Http::Cache::Response
@@ -96,6 +97,8 @@ module ActionDispatch # :nodoc:
def initialize(status = 200, header = {}, body = [])
super()
+ header = merge_default_headers(header, self.class.default_headers)
+
self.body, self.header, self.status = body, header, status
@sending_file = false
@@ -238,6 +241,12 @@ module ActionDispatch # :nodoc:
private
+ def merge_default_headers(original, default)
+ return original unless default.respond_to?(:merge)
+
+ default.merge(original)
+ end
+
def build_buffer(response, body)
Buffer.new response, body
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 4266ec042e..8aa02ec482 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -87,6 +87,12 @@ module ActionDispatch
end
end
+ def initialize(env)
+ super
+ @protocol = nil
+ @port = nil
+ end
+
# Returns the complete URL used for this request.
def url
protocol + host_with_port + fullpath
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 62f906219c..0dcf1fc4fe 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -19,10 +19,16 @@ module ActionDispatch
:verbose => false
}
+ config.action_dispatch.default_headers = {
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'X-XSS-Protection' => '1; mode=block'
+ }
+
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
+ ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 5e2f1ff1e0..ea5028a7c0 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -909,7 +909,7 @@ module ActionDispatch
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
VALID_ON_OPTIONS = [:new, :collection, :member]
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param]
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
@@ -1046,6 +1046,8 @@ module ActionDispatch
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
yield if block_given?
+ concerns(options[:concerns]) if options[:concerns]
+
collection do
post :create
end if parent_resource.actions.include?(:create)
@@ -1210,6 +1212,8 @@ module ActionDispatch
resource_scope(:resources, Resource.new(resources.pop, options)) do
yield if block_given?
+ concerns(options[:concerns]) if options[:concerns]
+
collection do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
@@ -1580,15 +1584,71 @@ module ActionDispatch
end
end
+ # Routing Concerns allows you to declare common routes that can be reused
+ # inside others resources and routes.
+ #
+ # concern :commentable do
+ # resources :comments
+ # end
+ #
+ # concern :image_attachable do
+ # resources :images, only: :index
+ # end
+ #
+ # These concerns are used in Resources routing:
+ #
+ # resources :messages, concerns: [:commentable, :image_attachable]
+ #
+ # or in a scope or namespace:
+ #
+ # namespace :posts do
+ # concerns :commentable
+ # end
+ module Concerns
+ # Define a routing concern using a name.
+ #
+ # concern :commentable do
+ # resources :comments
+ # end
+ #
+ # Any routing helpers can be used inside a concern.
+ def concern(name, &block)
+ @concerns[name] = block
+ end
+
+ # Use the named concerns
+ #
+ # resources :posts do
+ # concerns :commentable
+ # end
+ #
+ # concerns also work in any routes helper that you want to use:
+ #
+ # namespace :posts do
+ # concerns :commentable
+ # end
+ def concerns(*names)
+ names.flatten.each do |name|
+ if concern = @concerns[name]
+ instance_eval(&concern)
+ else
+ raise ArgumentError, "No concern named #{name} was found!"
+ end
+ end
+ end
+ end
+
def initialize(set) #:nodoc:
@set = set
@scope = { :path_names => @set.resources_path_names }
+ @concerns = {}
end
include Base
include HttpHelpers
include Redirection
include Scoping
+ include Concerns
include Resources
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 62c921ff97..32d267d1d6 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -163,9 +163,9 @@ module ActionDispatch
private
def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
+ define_url_helper route, :"#{name}_path",
route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
+ define_url_helper route, :"#{name}_url",
route.defaults.merge(:use_route => name, :only_path => false)
end
@@ -226,7 +226,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :default_url_options, :request_class
alias :routes :set
@@ -238,13 +238,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
-
self.request_class = request_class
- @valid_conditions = { :controller => true, :action => true }
- request_class.public_instance_methods.each { |m|
- @valid_conditions[m] = true
- }
- @valid_conditions.delete(:id)
@append = []
@prepend = []
@@ -375,7 +369,7 @@ module ActionDispatch
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
- conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
+ conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name && !named_routes[name]
@@ -412,21 +406,22 @@ module ActionDispatch
end
private :build_path
- def build_conditions(current_conditions, req_predicates, path_values)
+ def build_conditions(current_conditions, path_values)
conditions = current_conditions.dup
- verbs = conditions[:request_method] || []
-
# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
+ verbs = conditions[:request_method] || []
unless verbs.empty?
conditions[:request_method] = %r[^#{verbs.join('|')}$]
end
- conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) }
- conditions
+ conditions.keep_if do |k, _|
+ k == :action || k == :controller ||
+ @request_class.public_method_defined?(k) || path_values.include?(k)
+ end
end
private :build_conditions
@@ -468,7 +463,7 @@ module ActionDispatch
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
- @options[key] = @recall.delete(key)
+ @options[key] = @recall.delete(key)
end
end
end
@@ -577,7 +572,8 @@ module ActionDispatch
end
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
- :trailing_slash, :anchor, :params, :only_path, :script_name]
+ :trailing_slash, :anchor, :params, :only_path, :script_name,
+ :original_script_name]
def mounted?
false
@@ -597,7 +593,13 @@ module ActionDispatch
user, password = extract_authentication(options)
recall = options.delete(:_recall)
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ original_script_name = options.delete(:original_script_name).presence
+ script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ if script_name && original_script_name
+ script_name = original_script_name + script_name
+ end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 14d62af67b..139f4d19ab 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -26,7 +26,8 @@ module ActionView
def expand_sources(sources, recursive = false)
if sources.include?(:all)
- all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application'
+ all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application'])
+ add_application_js(all_asset_files, sources)
((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq
else
expanded_sources = sources.inject([]) do |list, source|
@@ -39,7 +40,7 @@ module ActionView
end
def add_application_js(expanded_sources, sources)
- if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}"))
+ if (sources.include?(:defaults) || sources.include?(:all)) && File.exist?(File.join(custom_dir, "application.#{extension}"))
expanded_sources.delete('application')
expanded_sources << "application"
end
@@ -106,8 +107,8 @@ module ActionView
#
# config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
#
- # When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
- # <tt>public/javascripts</tt> it will be included as well at the end.
+ # When using <tt>:defaults</tt> or <tt>:all</tt>, if an <tt>application.js</tt> file exists
+ # in <tt>public/javascripts</tt> it will be included as well at the end.
#
# You can modify the HTML attributes of the script tag by passing a hash as the
# last argument.
@@ -133,14 +134,16 @@ module ActionView
# # <script src="/javascripts/rails.js?1284139606"></script>
# # <script src="/javascripts/application.js?1284139606"></script>
#
+ # Note: The application.js file is only referenced if it exists
+ #
# You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source:
#
# javascript_include_tag :all
# # => <script src="/javascripts/jquery.js?1284139606"></script>
# # <script src="/javascripts/rails.js?1284139606"></script>
- # # <script src="/javascripts/application.js?1284139606"></script>
# # <script src="/javascripts/shop.js?1284139606"></script>
# # <script src="/javascripts/checkout.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
#
# Note that your defaults of choice will be included first, so they will be available to all subsequently
# included files.
@@ -161,9 +164,9 @@ module ActionView
# javascript_include_tag :all, :cache => true
# # => <script src="/javascripts/jquery.js?1284139606"></script>
# # <script src="/javascripts/rails.js?1284139606"></script>
- # # <script src="/javascripts/application.js?1284139606"></script>
# # <script src="/javascripts/shop.js?1284139606"></script>
# # <script src="/javascripts/checkout.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
#
# # assuming config.perform_caching is true
# javascript_include_tag :all, :cache => true
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 33799d7d71..39518268df 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -8,28 +8,28 @@ module ActionView
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
- # See ActionController::Caching::Fragments for usage instructions.
+ # The best way to use this is by doing key-based cache expiration
+ # on top of a cache store like Memcached that'll automatically
+ # kick out old entries. For more on key-based expiration, see:
+ # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
#
- # If you want to cache a navigation menu, you can do following:
+ # When using this method, you list the cache dependencies as part of
+ # the name of the cache, like so:
#
- # <% cache do %>
- # <%= render :partial => "menu" %>
+ # <% cache [ "v1", project ] do %>
+ # <b>All the topics on this project</b>
+ # <%= render project.topics %>
# <% end %>
#
- # You can also cache static content:
+ # This approach will assume that when a new topic is added, you'll touch
+ # the project. The cache key generated from this call will be something like:
#
- # <% cache do %>
- # <p>Hello users! Welcome to our website!</p>
- # <% end %>
- #
- # Static content with embedded ruby content can be cached as
- # well:
+ # views/v1/projects/123-20120806214154
+ # ^class ^id ^updated_at
#
- # <% cache do %>
- # Topics:
- # <%= render :partial => "topics", :collection => @topic_list %>
- # <i>Topics listed alphabetically</i>
- # <% end %>
+ # If you update the rendering of topics, you just bump the version to v2.
+ # Otherwise the cache is automatically bumped whenever the project updated_at
+ # is touched.
def cache(name = {}, options = nil, &block)
if controller.perform_caching
safe_concat(fragment_for(name, options, &block))
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 659aacf6d7..dea2aa69dd 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -427,6 +427,9 @@ module ActionView
# # Generate a time select field with hours in the AM/PM format
# select_time(my_time, :ampm => true)
#
+ # # Generates a time select field with hours that range from 2 to 14
+ # select_time(my_time, :start_hour => 2, :end_hour => 14)
+ #
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
# select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
@@ -504,6 +507,9 @@ module ActionView
#
# # Generate a select field for hours in the AM/PM format
# select_hour(my_time, :ampm => true)
+ #
+ # # Generates a select field that includes options for hours from 2 to 14.
+ # select_hour(my_time, :start_hour => 2, :end_hour => 14)
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
@@ -734,7 +740,11 @@ module ActionView
if @options[:use_hidden] || @options[:discard_hour]
build_hidden(:hour, hour)
else
- build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm])
+ options = {}
+ options[:ampm] = @options[:ampm] || false
+ options[:start] = @options[:start_hour] || 0
+ options[:end] = @options[:end_hour] || 23
+ build_options_and_select(:hour, hour, options)
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 13a5671a17..5dc5bb8a98 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -323,6 +323,24 @@ module ActionView
# ...
# </form>
#
+ # === Setting HTML options
+ #
+ # You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in
+ # the HTML key. Example:
+ #
+ # <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # The HTML generated for this would be:
+ #
+ # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
+ # <div style='margin:0;padding:0;display:inline'>
+ # <input name='_method' type='hidden' value='put' />
+ # </div>
+ # ...
+ # </form>
+ #
# === Removing hidden model id's
#
# The form_for method automatically includes the model id as a hidden field in the form.
@@ -405,10 +423,12 @@ module ActionView
object = nil
else
object = record.is_a?(Array) ? record.last : record
+ raise ArgumentError, "First argument in form cannot contain nil or be empty" if object.blank?
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
+ options[:html][:data] = options.delete(:data) if options.has_key?(:data)
options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index f3237f82d5..e4f4ebc7ff 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -708,9 +708,11 @@ module ActionView
private
def option_html_attributes(element)
- return {} unless Array === element
-
- Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, v] }]
+ if Array === element
+ element.select { |e| Hash === e }.reduce({}, :merge!)
+ else
+ {}
+ end
end
def option_text_and_value(option)
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 46ee196a2a..a9b91d1db3 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/module/attribute_accessors'
module ActionView
# = Action View Form Tag Helpers
module Helpers
- # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
+ # Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually.
#
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index cc20518b93..9f8cd8caaa 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -67,6 +67,46 @@ module ActionView
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
+
+ # Returns a button whose +onclick+ handler triggers the passed JavaScript.
+ #
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as button label and the JavaScript code goes into its +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
+ #
+ # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
+ # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
+ #
+ def button_to_function(name, function=nil, html_options={})
+ message = "button_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead."
+ ActiveSupport::Deprecation.warn message
+
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
+
+ tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
+ end
+
+ # Returns a link whose +onclick+ handler triggers the passed JavaScript.
+ #
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
+ # the JavaScript is set, the helper appends "; return false;".
+ #
+ # The +href+ attribute of the tag is set to "#" unless +html_options+ has one.
+ #
+ # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
+ # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
+ #
+ def link_to_function(name, function, html_options={})
+ message = "link_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead."
+ ActiveSupport::Deprecation.warn message
+
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
+ href = html_options[:href] || '#'
+
+ content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index a727b910e5..aaf0e0344a 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -78,7 +78,7 @@ module ActionView
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# # => Welcome to my website!
def strip_tags(html)
- self.class.full_sanitizer.sanitize(html).try(:html_safe)
+ self.class.full_sanitizer.sanitize(html)
end
# Strips all link tags from +text+ leaving just the link text.
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 3d86790a8f..fe3240fdc1 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -60,11 +60,15 @@ module ActionView
#
# ==== Relying on named routes
#
- # Passing a record (like an Active Record) instead of a Hash as the options parameter will
+ # Passing a record (like an Active Record) instead of a hash as the options parameter will
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
#
+ # ==== Implicit Controller Namespacing
+ #
+ # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
+ #
# ==== Examples
# <%= url_for(:action => 'index') %>
# # => /blog/
@@ -102,6 +106,14 @@ module ActionView
# <%= url_for(:back) %>
# # if request.env["HTTP_REFERER"] is not set or is blank
# # => javascript:history.back()
+ #
+ # <%= url_for(:action => 'index', :controller => 'users') %>
+ # # Assuming an "admin" namespace
+ # # => /admin/users
+ #
+ # <%= url_for(:action => 'index', :controller => '/users') %>
+ # # Specify absolute path with beginning slash
+ # # => /users
def url_for(options = nil)
case options
when String
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index d8258f7b11..f2bef4bded 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -84,13 +84,13 @@ module ActionView
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
- indent = ' ' * indentation
+ indent = end_on_line.to_s.size + indentation
line_counter = start_on_line
return unless source_code = source_code[start_on_line..end_on_line]
source_code.sum do |line|
line_counter += 1
- "#{indent}#{line_counter}: #{line}\n"
+ "%#{indent}s: %s\n" % [line_counter, line]
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 56cebee678..e5054a9eb8 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -85,39 +85,28 @@ module RenderERBUtils
end
end
-module SetupOnce
- extend ActiveSupport::Concern
-
- included do
- cattr_accessor :setup_once_block
- self.setup_once_block = nil
-
- setup :run_setup_once
- end
+SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
- module ClassMethods
- def setup_once(&block)
- self.setup_once_block = block
+module ActionDispatch
+ module SharedRoutes
+ def before_setup
+ @routes = SharedTestRoutes
+ super
end
end
- private
- def run_setup_once
- if self.setup_once_block
- self.setup_once_block.call
- self.setup_once_block = nil
- end
+ # Hold off drawing routes until all the possible controller classes
+ # have been loaded.
+ module DrawOnce
+ class << self
+ attr_accessor :drew
end
-end
+ self.drew = false
-SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
+ def before_setup
+ super
+ return if DrawOnce.drew
-module ActiveSupport
- class TestCase
- include SetupOnce
- # Hold off drawing routes until all the possible controller classes
- # have been loaded.
- setup_once do
SharedTestRoutes.draw do
get ':controller(/:action)'
end
@@ -125,10 +114,18 @@ module ActiveSupport
ActionDispatch::IntegrationTest.app.routes.draw do
get ':controller(/:action)'
end
+
+ DrawOnce.drew = true
end
end
end
+module ActiveSupport
+ class TestCase
+ include ActionDispatch::DrawOnce
+ end
+end
+
class RoutedRackApp
attr_reader :routes
@@ -159,9 +156,7 @@ class BasicController
end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
- setup do
- @routes = SharedTestRoutes
- end
+ include ActionDispatch::SharedRoutes
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
@@ -290,10 +285,7 @@ module ActionController
class TestCase
include ActionDispatch::TestProcess
-
- setup do
- @routes = SharedTestRoutes
- end
+ include ActionDispatch::SharedRoutes
end
end
@@ -304,9 +296,7 @@ module ActionView
class TestCase
# Must repeat the setup because AV::TestCase is a duplication
# of AC::TestCase
- setup do
- @routes = SharedTestRoutes
- end
+ include ActionDispatch::SharedRoutes
end
end
@@ -359,3 +349,32 @@ module RoutingTestHelpers
set.send(:url_for, options.merge(:only_path => true, :_recall => recall))
end
end
+
+class ResourcesController < ActionController::Base
+ def index() render :nothing => true end
+ alias_method :show, :index
+end
+
+class ThreadsController < ResourcesController; end
+class MessagesController < ResourcesController; end
+class CommentsController < ResourcesController; end
+class AuthorsController < ResourcesController; end
+class LogosController < ResourcesController; end
+
+class AccountsController < ResourcesController; end
+class AdminController < ResourcesController; end
+class ProductsController < ResourcesController; end
+class ImagesController < ResourcesController; end
+class PreferencesController < ResourcesController; end
+
+module Backoffice
+ class ProductsController < ResourcesController; end
+ class TagsController < ResourcesController; end
+ class ManufacturersController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+
+ module Admin
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+ end
+end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index d5afef9086..0efba5b77f 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -349,15 +349,18 @@ class ActionCachingMockController
end
class ActionCacheTest < ActionController::TestCase
+ tests ActionCachingTestController
+
def setup
super
- reset!
+ @request.host = 'hostname.com'
FileUtils.mkdir_p(FILE_STORE_PATH)
@path_class = ActionController::Caching::Actions::ActionCachePath
@mock_controller = ActionCachingMockController.new
end
def teardown
+ super
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
end
@@ -367,7 +370,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test')
- reset!
get :index
assert_response :success
@@ -380,7 +382,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert !fragment_exist?('hostname.com/action_caching_test/destroy')
- reset!
get :destroy
assert_response :success
@@ -395,7 +396,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout')
- reset!
get :with_layout
assert_response :success
@@ -410,7 +410,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/layout_false')
- reset!
get :layout_false
assert_response :success
@@ -425,7 +424,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
- reset!
get :with_layout_proc_param, :layout => false
assert_response :success
@@ -440,7 +438,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
- reset!
get :with_layout_proc_param, :layout => true
assert_response :success
@@ -477,7 +474,6 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('test.host/custom/show')
- reset!
get :show
assert_response :success
@@ -488,7 +484,6 @@ class ActionCacheTest < ActionController::TestCase
get :edit
assert_response :success
assert fragment_exist?('test.host/edit')
- reset!
get :edit, :id => 1
assert_response :success
@@ -499,22 +494,18 @@ class ActionCacheTest < ActionController::TestCase
get :index
assert_response :success
cached_time = content_to_cache
- reset!
get :index
assert_response :success
assert_equal cached_time, @response.body
- reset!
get :expire
assert_response :success
- reset!
get :index
assert_response :success
new_cached_time = content_to_cache
assert_not_equal cached_time, @response.body
- reset!
get :index
assert_response :success
@@ -524,12 +515,10 @@ class ActionCacheTest < ActionController::TestCase
def test_cache_expiration_isnt_affected_by_request_format
get :index
cached_time = content_to_cache
- reset!
@request.request_uri = "/action_caching_test/expire.xml"
get :expire, :format => :xml
assert_response :success
- reset!
get :index
assert_response :success
@@ -539,12 +528,10 @@ class ActionCacheTest < ActionController::TestCase
def test_cache_expiration_with_url_string
get :index
cached_time = content_to_cache
- reset!
@request.request_uri = "/action_caching_test/expire_with_url_string"
get :expire_with_url_string
assert_response :success
- reset!
get :index
assert_response :success
@@ -557,23 +544,17 @@ class ActionCacheTest < ActionController::TestCase
assert_response :success
jamis_cache = content_to_cache
- reset!
-
@request.host = 'david.hostname.com'
get :index
assert_response :success
david_cache = content_to_cache
assert_not_equal jamis_cache, @response.body
- reset!
-
@request.host = 'jamis.hostname.com'
get :index
assert_response :success
assert_equal jamis_cache, @response.body
- reset!
-
@request.host = 'david.hostname.com'
get :index
assert_response :success
@@ -583,8 +564,6 @@ class ActionCacheTest < ActionController::TestCase
def test_redirect_is_not_cached
get :redirected
assert_response :redirect
- reset!
-
get :redirected
assert_response :redirect
end
@@ -592,8 +571,6 @@ class ActionCacheTest < ActionController::TestCase
def test_forbidden_is_not_cached
get :forbidden
assert_response :forbidden
- reset!
-
get :forbidden
assert_response :forbidden
end
@@ -609,17 +586,14 @@ class ActionCacheTest < ActionController::TestCase
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/index.xml')
- reset!
get :index, :format => 'xml'
assert_response :success
assert_equal cached_time, @response.body
assert_equal 'application/xml', @response.content_type
- reset!
get :expire_xml
assert_response :success
- reset!
get :index, :format => 'xml'
assert_response :success
@@ -724,14 +698,6 @@ class ActionCacheTest < ActionController::TestCase
assigns(:cache_this)
end
- def reset!
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- @controller = ActionCachingTestController.new
- @controller.singleton_class.send(:include, @routes.url_helpers)
- @request.host = 'hostname.com'
- end
-
def fragment_exist?(path)
@controller.fragment_exist?(path)
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 236e16c68e..305659b219 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -2,35 +2,6 @@ require 'abstract_unit'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/with_options'
-class ResourcesController < ActionController::Base
- def index() render :nothing => true end
- alias_method :show, :index
-end
-
-class ThreadsController < ResourcesController; end
-class MessagesController < ResourcesController; end
-class CommentsController < ResourcesController; end
-class AuthorsController < ResourcesController; end
-class LogosController < ResourcesController; end
-
-class AccountsController < ResourcesController; end
-class AdminController < ResourcesController; end
-class ProductsController < ResourcesController; end
-class ImagesController < ResourcesController; end
-class PreferencesController < ResourcesController; end
-
-module Backoffice
- class ProductsController < ResourcesController; end
- class TagsController < ResourcesController; end
- class ManufacturersController < ResourcesController; end
- class ImagesController < ResourcesController; end
-
- module Admin
- class ProductsController < ResourcesController; end
- class ImagesController < ResourcesController; end
- end
-end
-
class ResourcesTest < ActionController::TestCase
def test_default_restful_routes
with_restful_routing :messages do
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 2467654a70..347b3b3b5a 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -190,7 +190,7 @@ class CookiesTest < ActionController::TestCase
def test_setting_the_same_value_to_permanent_cookie
request.cookies[:user_name] = 'Jamie'
get :set_permanent_cookie
- assert response.cookies, 'user_name' => 'Jamie'
+ assert_equal response.cookies, 'user_name' => 'Jamie'
end
def test_setting_with_escapable_characters
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 87a6b1383d..e16f23914b 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -8,6 +8,23 @@ module ActionController
@response = Live::Response.new
end
+ def test_header_merge
+ header = @response.header.merge('Foo' => 'Bar')
+ assert_kind_of(ActionController::Live::Response::Header, header)
+ refute_equal header, @response.header
+ end
+
+ def test_initialize_with_default_headers
+ r = Class.new(Live::Response) do
+ def self.default_headers
+ { 'omg' => 'g' }
+ end
+ end
+
+ header = r.new.header
+ assert_kind_of(ActionController::Live::Response::Header, header)
+ end
+
def test_parallel
latch = ActiveSupport::Concurrency::Latch.new
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index ab2f7612ce..6d75c5ec7a 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -166,18 +166,6 @@ module TestGenerationPrefix
assert_equal "/generate", last_response.body
end
- test "[ENGINE] generating application's url includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application"
- assert_equal "/something/generate", last_response.body
- end
-
- test "[ENGINE] generating application's url should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo'
- assert_equal "/something/generate", last_response.body
- end
-
test "[ENGINE] generating engine's url with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine"
assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
@@ -200,12 +188,6 @@ module TestGenerationPrefix
assert_equal "/something/awesome/blog/posts/1", last_response.body
end
- test "[APP] generating engine's route should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/generate", {}, 'SCRIPT_NAME' => "/foo"
- assert_equal "/something/awesome/blog/posts/1", last_response.body
- end
-
test "[APP] generating engine's url with polymorphic path" do
get "/polymorphic_path_for_engine"
assert_equal "/awesome/blog/posts/1", last_response.body
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index e2903d4b36..71609d7340 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -176,6 +176,33 @@ class ResponseTest < ActiveSupport::TestCase
ActionDispatch::Response.default_charset = original
end
end
+
+ test "read x_frame_options and x_xss_protection" do
+ ActionDispatch::Response.default_headers = {
+ 'X-Frame-Options' => 'DENY',
+ 'X-XSS-Protection' => '1;'
+ }
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('DENY', resp.headers['X-Frame-Options'])
+ assert_equal('1;', resp.headers['X-XSS-Protection'])
+ end
+
+ test "read custom default_header" do
+ ActionDispatch::Response.default_headers = {
+ 'X-XX-XXXX' => 'Here is my phone number'
+ }
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('Here is my phone number', resp.headers['X-XX-XXXX'])
+ end
+
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
new file mode 100644
index 0000000000..21da3bd77a
--- /dev/null
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -0,0 +1,82 @@
+require 'abstract_unit'
+
+class RoutingConcernsTest < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ concern :commentable do
+ resources :comments
+ end
+
+ concern :image_attachable do
+ resources :images, only: :index
+ end
+
+ resources :posts, concerns: [:commentable, :image_attachable] do
+ resource :video, concerns: :commentable
+ end
+
+ resource :picture, concerns: :commentable do
+ resources :posts, concerns: :commentable
+ end
+
+ scope "/videos" do
+ concerns :commentable
+ end
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ def test_accessing_concern_from_resources
+ get "/posts/1/comments"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/comments", post_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resource
+ get "/picture/comments"
+ assert_equal "200", @response.code
+ assert_equal "/picture/comments", picture_comments_path
+ end
+
+ def test_accessing_concern_from_nested_resource
+ get "/posts/1/video/comments"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/video/comments", post_video_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_nested_resources
+ get "/picture/posts/1/comments"
+ assert_equal "200", @response.code
+ assert_equal "/picture/posts/1/comments", picture_post_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resources_with_more_than_one_concern
+ get "/posts/1/images"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/images", post_images_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resources_using_only_option
+ get "/posts/1/image/1"
+ assert_equal "404", @response.code
+ end
+
+ def test_accessing_concern_from_a_scope
+ get "/videos/comments"
+ assert_equal "200", @response.code
+ end
+
+ def test_with_an_invalid_concern_name
+ e = assert_raise ArgumentError do
+ ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ resources :posts, concerns: :foo
+ end
+ end
+ end
+
+ assert_equal "No concern named foo was found!", e.message
+ end
+end
diff --git a/actionpack/test/fixtures/test/_raise_indentation.html.erb b/actionpack/test/fixtures/test/_raise_indentation.html.erb
new file mode 100644
index 0000000000..f9a93728fe
--- /dev/null
+++ b/actionpack/test/fixtures/test/_raise_indentation.html.erb
@@ -0,0 +1,13 @@
+<p>First paragraph</p>
+<p>Second paragraph</p>
+<p>Third paragraph</p>
+<p>Fourth paragraph</p>
+<p>Fifth paragraph</p>
+<p>Sixth paragraph</p>
+<p>Seventh paragraph</p>
+<p>Eight paragraph</p>
+<p>Ninth paragraph</p>
+<p>Tenth paragraph</p>
+<%= raise "error here!" %>
+<p>Eleventh paragraph</p>
+<p>Twelfth paragraph</p> \ No newline at end of file
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index bcc55189b9..6a44197525 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -417,6 +417,15 @@ class AssetTagHelperTest < ActionView::TestCase
assert_raise(ArgumentError) { javascript_include_tag(:defaults) }
end
+ def test_all_javascript_expansion_not_include_application_js_if_not_exists
+ FileUtils.mv(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.js'),
+ File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.bak'))
+ assert_no_match(/application\.js/, javascript_include_tag(:all))
+ ensure
+ FileUtils.mv(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.bak'),
+ File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.js'))
+ end
+
def test_stylesheet_path
ENV["RAILS_ASSET_ID"] = ""
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index ff85a675a2..c13878af98 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1243,6 +1243,35 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_select_datetime_with_custom_hours
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="">Choose hour</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :start_hour => 1, :end_hour => 9, :prefix => "date[first]",
+ :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
+ end
+
def test_select_datetime_with_hidden
expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index a20a68c1b2..5c6cb45530 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1045,6 +1045,20 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_form_for_requires_arguments
+ error = assert_raises(ArgumentError) do
+ form_for(nil, :html => { :id => 'create-post' }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+
+ error = assert_raises(ArgumentError) do
+ form_for([nil, nil], :html => { :id => 'create-post' }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+ end
+
def test_form_for
form_for(@post, :html => { :id => 'create-post' }) do |f|
concat f.label(:title) { "The Title" }
@@ -2637,6 +2651,12 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_data_attributes
+ form_for(@post, data: { behavior: "stuff" }, remote: true) {}
+ assert_match %r|data-behavior="stuff"|, output_buffer
+ assert_match %r|data-remote="true"|, output_buffer
+ end
+
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index d234e6633c..f908de865e 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -1164,11 +1164,12 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_option_html_attributes_from_without_hash
- assert_equal(
- {},
- option_html_attributes([ 'foo', 'bar' ])
- )
+ def test_option_html_attributes_with_no_array_element
+ assert_equal({}, option_html_attributes('foo'))
+ end
+
+ def test_option_html_attributes_without_hash
+ assert_equal({}, option_html_attributes([ 'foo', 'bar' ]))
end
def test_option_html_attributes_with_single_element_hash
@@ -1192,6 +1193,15 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_option_html_attributes_with_multiple_hashes_does_not_modify_them
+ options1 = { class: 'fancy' }
+ options2 = { onclick: "alert('Hello World');" }
+ option_html_attributes([ 'foo', 'bar', options1, options2 ])
+
+ assert_equal({ class: 'fancy' }, options1)
+ assert_equal({ onclick: "alert('Hello World');" }, options2)
+ end
+
def test_grouped_collection_select
@post = Post.new
@post.origin = 'dk'
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index fe7607ee26..4a9a382afa 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -42,6 +42,48 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given))
end
+ def test_button_to_function
+ assert_deprecated "button_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal %(<input type="button" onclick="alert(&#x27;Hello world!&#x27;);" value="Greeting" />),
+ button_to_function("Greeting", "alert('Hello world!')")
+ end
+ end
+
+ def test_button_to_function_with_onclick
+ assert_deprecated "button_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal "<input onclick=\"alert(&#x27;Goodbye World :(&#x27;); alert(&#x27;Hello world!&#x27;);\" type=\"button\" value=\"Greeting\" />",
+ button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
+ end
+ end
+
+ def test_button_to_function_without_function
+ assert_deprecated "button_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
+ button_to_function("Greeting")
+ end
+ end
+
+ def test_link_to_function
+ assert_deprecated "link_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal %(<a href="#" onclick="alert(&#x27;Hello world!&#x27;); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')")
+ end
+ end
+
+ def test_link_to_function_with_existing_onclick
+ assert_deprecated "link_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal %(<a href="#" onclick="confirm(&#x27;Sanity!&#x27;); alert(&#x27;Hello world!&#x27;); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
+ end
+ end
+
+ def test_function_with_href
+ assert_deprecated "link_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead." do
+ assert_dom_equal %(<a href="http://example.com/" onclick="alert(&#x27;Hello world!&#x27;); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
+ end
+ end
+
def test_javascript_tag
self.output_buffer = 'foo'
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 164b8b9fa1..b26354e7cc 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -203,6 +203,15 @@ module RenderTestCases
assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name
end
+ def test_render_error_indentation
+ e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise_indentation") }
+ error_lines = e.annoted_source_code.split("\n")
+ assert_match %r!error\shere!, e.message
+ assert_equal "11", e.line_number
+ assert_equal " 9: <p>Ninth paragraph</p>", error_lines.second
+ assert_equal " 10: <p>Tenth paragraph</p>", error_lines.third
+ end
+
def test_render_sub_template_with_errors
e = assert_raises(ActionView::Template::Error) { @view.render(:template => "test/sub_template_raise") }
assert_match %r!method.*doesnt_exist!, e.message
diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionpack/test/template/sanitize_helper_test.rb
index 4182af590e..7626cdf386 100644
--- a/actionpack/test/template/sanitize_helper_test.rb
+++ b/actionpack/test/template/sanitize_helper_test.rb
@@ -40,9 +40,9 @@ class SanitizeHelperTest < ActionView::TestCase
[nil, '', ' '].each do |blank|
stripped = strip_tags(blank)
assert_equal blank, stripped
- assert stripped.html_safe? unless blank.nil?
end
- assert strip_tags("<script>").html_safe?
+ assert_equal "", strip_tags("<script>")
+ assert_equal "something &lt;img onerror=alert(1337)", ERB::Util.html_escape(strip_tags("something <img onerror=alert(1337)"))
end
def test_sanitize_is_marked_safe
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index b9966cb2fa..a8f470397b 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,15 @@
## Rails 4.0.0 (unreleased) ##
-* `AM::Validation#validates` ability to pass custom exception to `:strict` option *Bogdan Gusiev*
+* `AM::Validation#validates` ability to pass custom exception to `:strict` option.
+
+ *Bogdan Gusiev*
+
+* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default
+ propagate `:skip_types, :dasherize, :camelize` keys to included associations.
+ It can be overriden on each association by explicitly specifying the option on one
+ or more associations
+
+ *Anthony Alberto*
* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
@@ -41,10 +50,24 @@
* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide.
+
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* `validates_inclusion_of` and `validates_exclusion_of` now accept `:within` option as alias of `:in` as documented.
+
+* Fix the the backport of the object dup with the ruby 1.9.3p194.
+
+
## Rails 3.2.6 (Jun 12, 2012) ##
* No changes.
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e669113001..e442455a53 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -38,6 +38,17 @@ module ActiveModel
# # Your code here
# end
#
+ # When defining an around callback remember to yield to the block, otherwise
+ # it won't be executed:
+ #
+ # around_create :log_status
+ #
+ # def log_status
+ # puts 'going to call the block...'
+ # yield
+ # puts 'block successfully called.'
+ # end
+ #
# You can choose not to have all three callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 7ba439fb3e..c0d93e5d53 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -298,14 +298,15 @@ module ActiveModel
model_name_from_record_or_class(record_or_class).param_key
end
- private
- def self.model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
- end
-
- def self.convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
+ if record_or_class.respond_to?(:model_name)
+ record_or_class.model_name
+ elsif record_or_class.respond_to?(:to_model)
+ record_or_class.to_model.class.model_name
+ else
+ record_or_class.class.model_name
end
+ end
+ private_class_method :model_name_from_record_or_class
end
-
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 016d821fdf..cf742d0569 100644..100755
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -115,6 +115,10 @@ module ActiveModel
merged_options = opts.merge(options.slice(:builder, :indent))
merged_options[:skip_instruct] = true
+ [:skip_types, :dasherize, :camelize].each do |key|
+ merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
+ end
+
if records.respond_to?(:to_ary)
records = records.to_ary
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index a93323a3a8..8c5a3c5efd 100644..100755
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -28,7 +28,7 @@ class Address
extend ActiveModel::Naming
include ActiveModel::Serializers::Xml
- attr_accessor :street, :city, :state, :zip
+ attr_accessor :street, :city, :state, :zip, :apt_number
def attributes
instance_values
@@ -56,6 +56,7 @@ class XmlSerializationTest < ActiveModel::TestCase
@contact.address.city = "Springfield"
@contact.address.state = "CA"
@contact.address.zip = 11111
+ @contact.address.apt_number = 35
@contact.friends = [Contact.new, Contact.new]
end
@@ -222,4 +223,39 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<friends>}, xml
assert_match %r{<friend>}, xml
end
+
+ test "propagates skip-types option to included associations and attributes" do
+ xml = @contact.to_xml :skip_types => true, :include => :address, :indent => 0
+ assert_match %r{<address>}, xml
+ assert_match %r{<apt-number>}, xml
+ end
+
+ test "propagates camelize option to included associations and attributes" do
+ xml = @contact.to_xml :camelize => true, :include => :address, :indent => 0
+ assert_match %r{<Address>}, xml
+ assert_match %r{<AptNumber type="integer">}, xml
+ end
+
+ test "propagates dasherize option to included associations and attributes" do
+ xml = @contact.to_xml :dasherize => false, :include => :address, :indent => 0
+ assert_match %r{<apt_number type="integer">}, xml
+ end
+
+ test "don't propagate skip_types if skip_types is defined at the included association level" do
+ xml = @contact.to_xml :skip_types => true, :include => { :address => { :skip_types => false } }, :indent => 0
+ assert_match %r{<address>}, xml
+ assert_match %r{<apt-number type="integer">}, xml
+ end
+
+ test "don't propagate camelize if camelize is defined at the included association level" do
+ xml = @contact.to_xml :camelize => true, :include => { :address => { :camelize => false } }, :indent => 0
+ assert_match %r{<address>}, xml
+ assert_match %r{<apt-number type="integer">}, xml
+ end
+
+ test "don't propagate dasherize if dasherize is defined at the included association level" do
+ xml = @contact.to_xml :dasherize => false, :include => { :address => { :dasherize => true } }, :indent => 0
+ assert_match %r{<address>}, xml
+ assert_match %r{<apt-number type="integer">}, xml
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a99d7fdde8..faf1bbf232 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,17 @@
## Rails 4.0.0 (unreleased) ##
+* Raise `ArgumentError` if list of attributes to change is empty in `update_all`.
+
+ *Roman Shatsov*
+
+* Fix AR#create to return an unsaved record when AR::RecordInvalid is
+ raised. Fixes #3217.
+
+ *Dave Yeu*
+
+* Fixed table name prefix that is generated in engines for namespaced models
+ *Wojciech Wnętrzak*
+
* Make sure `:environment` task is executed before `db:schema:load` or `db:structure:load`
Fixes #4772.
@@ -465,11 +477,11 @@
The `add_index` method now supports a `where` option that receives a
string with the partial index criteria.
- add_index(:accounts, :code, :where => "active")
+ add_index(:accounts, :code, :where => "active")
- Generates
+ Generates
- CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
+ CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
*Marcelo Silveira*
@@ -486,24 +498,13 @@
* Added the `ActiveRecord::NullRelation` class implementing the null
object pattern for the Relation class. *Juanjo Bazán*
-* Added deprecation for the `:dependent => :restrict` association option.
-
- Please note:
+* Added new `:dependent => :restrict_with_error` option. This will add
+ an error to the model, rather than raising an exception.
- * Up until now `has_many` and `has_one`, `:dependent => :restrict`
- option raised a `DeleteRestrictionError` at the time of destroying
- the object. Instead, it will add an error on the model.
+ The `:restrict` option is renamed to `:restrict_with_exception` to
+ make this distinction explicit.
- * To fix this warning, make sure your code isn't relying on a
- `DeleteRestrictionError` and then add
- `config.active_record.dependent_restrict_raises = false` to your
- application config.
-
- * New rails application would be generated with the
- `config.active_record.dependent_restrict_raises = false` in the
- application config.
-
- *Manoj Kumar*
+ *Manoj Kumar & Jon Leighton*
* Added `create_join_table` migration helper to create HABTM join tables
@@ -589,6 +590,79 @@
* PostgreSQL hstore types are automatically deserialized from the database.
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* Do not consider the numeric attribute as changed if the old value is zero and the new value
+ is not a string.
+ Fixes #7237.
+
+ *Rafael Mendonça França*
+
+* Removes the deprecation of `update_attribute`. *fxn*
+
+* Reverted the deprecation of `composed_of`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `*_sql` association options. They will
+ be deprecated in 4.0 instead. *Jon Leighton*
+
+* Do not eager load AR session store. ActiveRecord::SessionStore depends on the abstract store
+ in Action Pack. Eager loading this class would break client code that eager loads Active Record
+ standalone.
+ Fixes #7160
+
+ *Xavier Noria*
+
+* Do not set RAILS_ENV to "development" when using `db:test:prepare` and related rake tasks.
+ This was causing the truncation of the development database data when using RSpec.
+ Fixes #7175.
+
+ *Rafael Mendonça França*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* `:finder_sql` and `:counter_sql` options on collection associations
+ are deprecated. Please transition to using scopes.
+
+ *Jon Leighton*
+
+* `:insert_sql` and `:delete_sql` options on `has_and_belongs_to_many`
+ associations are deprecated. Please transition to using `has_many
+ :through`
+
+ *Jon Leighton*
+
+* `composed_of` has been deprecated. You'll have to write your own accessor
+ and mutator methods if you'd like to use value objects to represent some
+ portion of your models.
+
+ *Steve Klabnik*
+
+* `update_attribute` has been deprecated. Use `update_column` if
+ you want to bypass mass-assignment protection, validations, callbacks,
+ and touching of updated_at. Otherwise please use `update_attributes`.
+
+ *Steve Klabnik*
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* protect against the nesting of hashes changing the
+ table context in the next call to build_from_hash. This fix
+ covers this case as well.
+
+ CVE-2012-2695
+
+* Revert earlier 'perf fix' (see 3.2.4 changelog / GH #6289). This
+ change introduced a regression (GH #6609). assoc.clear and
+ assoc.delete_all have loaded the association before doing the delete
+ since at least Rails 2.3. Doing the delete without loading the
+ records means that the `before_remove` and `after_remove` callbacks do
+ not get invoked. Therefore, this change was less a fix a more an
+ optimisation, which should only have gone into master.
+
+ *Jon Leighton*
+
## Rails 3.2.5 (Jun 1, 2012) ##
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f5ee4f3ebe..9ba3323bc7 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1094,12 +1094,14 @@ module ActiveRecord
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:dependent]
- # If set to <tt>:destroy</tt> all the associated objects are destroyed
- # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
- # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
- # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
- # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated
- # objects are present.
+ # Controls what happens to the associated objects when
+ # their owner is destroyed:
+ #
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
+ # * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1203,11 +1205,14 @@ module ActiveRecord
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
# if the real class name is Person, you'll have to specify it with this option.
# [:dependent]
- # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
- # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
- # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an
- # associated object is present.
+ # Controls what happens to the associated object when
+ # its owner is destroyed:
+ #
+ # * <tt>:destroy</tt> causes the associated object to also be destroyed
+ # * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index ddfc6f6c05..75f72c1a46 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -2,6 +2,11 @@ module ActiveRecord
# = Active Record Belongs To Associations
module Associations
class BelongsToAssociation < SingularAssociation #:nodoc:
+
+ def handle_dependency
+ target.send(options[:dependent]) if load_target
+ end
+
def replace(record)
raise_on_type_mismatch(record) if record
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index c3f32b5ed9..1df876bf62 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -39,6 +39,7 @@ module ActiveRecord::Associations::Builder
def build
validate_options
define_accessors
+ configure_dependency if options[:dependent]
@reflection = model.create_reflection(macro, name, scope, options, model)
super # provides an extension point
@reflection
@@ -52,70 +53,54 @@ module ActiveRecord::Associations::Builder
Association.valid_options
end
- private
-
- def validate_options
- options.assert_valid_keys(valid_options)
- end
+ def validate_options
+ options.assert_valid_keys(valid_options)
+ end
- def define_accessors
- define_readers
- define_writers
- end
+ def define_accessors
+ define_readers
+ define_writers
+ end
- def define_readers
- name = self.name
- mixin.redefine_method(name) do |*params|
- association(name).reader(*params)
+ def define_readers
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}(*args)
+ association(:#{name}).reader(*args)
end
- end
+ CODE
+ end
- def define_writers
- name = self.name
- mixin.redefine_method("#{name}=") do |value|
- association(name).writer(value)
+ def define_writers
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}=(value)
+ association(:#{name}).writer(value)
end
- end
-
- def check_valid_dependent!(dependent, valid_options)
- unless valid_options.include?(dependent)
- valid_options_message = valid_options.map(&:inspect).to_sentence(
- words_connector: ', ', two_words_connector: ' or ', last_word_connector: ' or ')
+ CODE
+ end
- raise ArgumentError, "The :dependent option expects either " \
- "#{valid_options_message} (#{dependent.inspect})"
- end
+ def configure_dependency
+ unless valid_dependent_options.include? options[:dependent]
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
end
- def dependent_restrict_raises?
- ActiveRecord::Base.dependent_restrict_raises == true
+ if options[:dependent] == :restrict
+ ActiveSupport::Deprecation.warn(
+ "The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
+ "provides the same functionality."
+ )
end
- def dependent_restrict_deprecation_warning
- if dependent_restrict_raises?
- msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
- "Instead, it will add an error on the model. To fix this warning, make sure your code " \
- "isn't relying on a `DeleteRestrictionError` and then add " \
- "`config.active_record.dependent_restrict_raises = false` to your application config."
- ActiveSupport::Deprecation.warn msg
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{macro}_dependent_for_#{name}
+ association(:#{name}).handle_dependency
end
- end
+ CODE
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- has_one_macro = association(name).reflection.macro == :has_one
- if has_one_macro ? !send(name).nil? : send(name).exists?
- if dependent_restrict_raises?
- raise ActiveRecord::DeleteRestrictionError.new(name)
- else
- key = has_one_macro ? "one" : "many"
- errors.add(:base, :"restrict_dependent_destroy.#{key}",
- :record => self.class.human_attribute_name(name).downcase)
- return false
- end
- end
- end
- end
+ model.before_destroy "#{macro}_dependent_for_#{name}"
+ end
+
+ def valid_dependent_options
+ raise NotImplementedError
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index f205a456f7..2f2600b7fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
def macro
@@ -17,72 +16,51 @@ module ActiveRecord::Associations::Builder
reflection = super
add_counter_cache_callbacks(reflection) if options[:counter_cache]
add_touch_callbacks(reflection) if options[:touch]
- configure_dependency
reflection
end
- private
-
- def add_counter_cache_callbacks(reflection)
- cache_column = reflection.counter_cache_column
- name = self.name
+ def add_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
- method_name = "belongs_to_counter_cache_after_create_for_#{name}"
- mixin.redefine_method(method_name) do
- record = send(name)
- record.class.increment_counter(cache_column, record.id) unless record.nil?
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_counter_cache_after_create_for_#{name}
+ record = #{name}
+ record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
end
- model.after_create(method_name)
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
- mixin.redefine_method(method_name) do
+ def belongs_to_counter_cache_before_destroy_for_#{name}
unless marked_for_destruction?
- record = send(name)
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
+ record = #{name}
+ record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
end
end
- model.before_destroy(method_name)
+ CODE
- model.send(:module_eval,
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
- )
- end
+ model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
+ model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
- def add_touch_callbacks(reflection)
- name = self.name
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
- touch = options[:touch]
+ klass = reflection.class_name.safe_constantize
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
+ end
- mixin.redefine_method(method_name) do
- record = send(name)
+ def add_touch_callbacks(reflection)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_touch_after_save_or_destroy_for_#{name}
+ record = #{name}
unless record.nil?
- if touch == true
- record.touch
- else
- record.touch(touch)
- end
+ record.touch #{options[:touch].inspect if options[:touch] != true}
end
end
+ CODE
- model.after_save(method_name)
- model.after_touch(method_name)
- model.after_destroy(method_name)
- end
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete]
+ model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ end
- method_name = "belongs_to_dependent_#{dependent}_for_#{name}"
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
- def #{method_name}
- association = #{name}
- association.#{dependent} if association
- end
- eoruby
- model.after_destroy method_name
- end
- end
+ def valid_dependent_options
+ [:destroy, :delete]
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 3fb0a57450..1b382f7285 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
@@ -34,53 +33,53 @@ module ActiveRecord::Associations::Builder
end
end
- private
-
- def wrap_block_extension
- if block_extension
- @extension_module = mod = Module.new(&block_extension)
- silence_warnings do
- model.parent.const_set(extension_module_name, mod)
- end
+ def wrap_block_extension
+ if block_extension
+ @extension_module = mod = Module.new(&block_extension)
+ silence_warnings do
+ model.parent.const_set(extension_module_name, mod)
+ end
- prev_scope = @scope
+ prev_scope = @scope
- if prev_scope
- @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
- else
- @scope = proc { extending(mod) }
- end
+ if prev_scope
+ @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
+ else
+ @scope = proc { extending(mod) }
end
end
+ end
- def extension_module_name
- @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- end
+ def extension_module_name
+ @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
+ end
- def define_callback(callback_name)
- full_callback_name = "#{callback_name}_for_#{name}"
+ def define_callback(callback_name)
+ full_callback_name = "#{callback_name}_for_#{name}"
- # TODO : why do i need method_defined? I think its because of the inheritance chain
- model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
- model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
- end
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
+ model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
+ model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
+ end
- def define_readers
- super
+ def define_readers
+ super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
- association(name).ids_reader
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids
+ association(:#{name}).ids_reader
end
- end
+ CODE
+ end
- def define_writers
- super
+ def define_writers
+ super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
- association(name).ids_writer(ids)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids=(ids)
+ association(:#{name}).ids_writer(ids)
end
- end
+ CODE
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 8df28ad876..bdac02b5bf 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -24,18 +24,16 @@ module ActiveRecord::Associations::Builder
end
end
- private
-
- def define_destroy_hook
- name = self.name
- model.send(:include, Module.new {
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy_associations
- association(#{name.to_sym.inspect}).delete_all
- super
- end
- RUBY
- })
- end
+ def define_destroy_hook
+ name = self.name
+ model.send(:include, Module.new {
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def destroy_associations
+ association(:#{name}).delete_all
+ super
+ end
+ RUBY
+ })
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 9e60dbc30b..ab8225460a 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
def macro
@@ -9,52 +8,8 @@ module ActiveRecord::Associations::Builder
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
end
- def build
- reflection = super
- configure_dependency
- reflection
+ def valid_dependent_options
+ [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
end
-
- private
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete_all, :nullify, :restrict]
- dependent_restrict_deprecation_warning if dependent == :restrict
-
- send("define_#{dependent}_dependency_method")
- model.before_destroy dependency_method_name
- end
- end
-
- def define_destroy_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- send(name).each do |o|
- # No point in executing the counter update since we're going to destroy the parent anyway
- o.mark_for_destruction
- end
-
- send(name).delete_all
- end
- end
-
- def define_delete_all_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- association(name).delete_all
- end
- end
-
- def define_nullify_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- send(name).delete_all
- end
- end
-
- def dependency_method_name
- "has_many_dependent_for_#{name}"
- end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 9c84f1913a..0da564f402 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
def macro
@@ -15,35 +14,12 @@ module ActiveRecord::Associations::Builder
!options[:through]
end
- def build
- reflection = super
- configure_dependency unless options[:through]
- reflection
+ def configure_dependency
+ super unless options[:through]
end
- private
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete, :nullify, :restrict]
- dependent_restrict_deprecation_warning if dependent == :restrict
-
- send("define_#{dependent}_dependency_method")
- model.before_destroy dependency_method_name
- end
- end
-
- def define_destroy_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- association(name).delete
- end
- end
- alias :define_delete_dependency_method :define_destroy_dependency_method
- alias :define_nullify_dependency_method :define_destroy_dependency_method
-
- def dependency_method_name
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
- end
+ def valid_dependent_options
+ [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 90a4b7c2ef..6a5830e57f 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -13,22 +13,20 @@ module ActiveRecord::Associations::Builder
define_constructors if constructable?
end
- private
-
- def define_constructors
- name = self.name
-
- mixin.redefine_method("build_#{name}") do |*params, &block|
- association(name).build(*params, &block)
+ def define_constructors
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def build_#{name}(*args, &block)
+ association(:#{name}).build(*args, &block)
end
- mixin.redefine_method("create_#{name}") do |*params, &block|
- association(name).create(*params, &block)
+ def create_#{name}(*args, &block)
+ association(:#{name}).create(*args, &block)
end
- mixin.redefine_method("create_#{name}!") do |*params, &block|
- association(name).create!(*params, &block)
+ def create_#{name}!(*args, &block)
+ association(:#{name}).create!(*args, &block)
end
- end
+ CODE
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 7a363c896e..74864d271f 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -7,6 +7,28 @@ module ActiveRecord
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ def handle_dependency
+ case options[:dependent]
+ when :restrict, :restrict_with_exception
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
+
+ when :restrict_with_error
+ unless empty?
+ record = klass.human_attribute_name(reflection.name).downcase
+ owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
+ false
+ end
+
+ else
+ if options[:dependent] == :destroy
+ # No point in executing the counter update since we're going to destroy the parent anyway
+ load_target.each(&:mark_for_destruction)
+ end
+
+ delete_all
+ end
+ end
+
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 191c4e8e39..dd7da59a86 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -3,23 +3,43 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
- def replace(record, save = true)
- raise_on_type_mismatch(record) if record
- load_target
- reflection.klass.transaction do
- if target && target != record
- remove_target!(options[:dependent]) unless target.destroyed?
+ def handle_dependency
+ case options[:dependent]
+ when :restrict, :restrict_with_exception
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
+
+ when :restrict_with_error
+ if load_target
+ record = klass.human_attribute_name(reflection.name).downcase
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
+ false
end
- if record
- set_owner_attributes(record)
- set_inverse_instance(record)
+ else
+ delete
+ end
+ end
- if owner.persisted? && save && !record.save
- nullify_owner_attributes(record)
- set_owner_attributes(target) if target
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
+ def replace(record, save = true)
+ raise_on_type_mismatch(record) if record
+ load_target
+
+ # If target and record are nil, or target is equal to record,
+ # we don't need to have transaction.
+ if (target || record) && target != record
+ reflection.klass.transaction do
+ remove_target!(options[:dependent]) if target && !target.destroyed?
+
+ if record
+ set_owner_attributes(record)
+ set_inverse_instance(record)
+
+ if owner.persisted? && save && !record.save
+ nullify_owner_attributes(record)
+ set_owner_attributes(target) if target
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
+ end
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1145d2138c..0fddfdf0cb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -82,15 +82,6 @@ module ActiveRecord
# The connection handler
config_attribute :connection_handler
- ##
- # :singleton-method:
- # Specifies whether or not has_many or has_one association option
- # :dependent => :restrict raises an exception. If set to true, the
- # ActiveRecord::DeleteRestrictionError exception will be raised
- # along with a DEPRECATION WARNING. If set to false, an error would
- # be added to the model instead.
- config_attribute :dependent_restrict_raises
-
%w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
config_attribute name, global: true
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 9ed3256ae9..1abbc58314 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -258,6 +258,8 @@ module ActiveRecord
# # Update all books that match conditions, but limit it to 5 ordered by date
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
def update_all(updates)
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
+
stmt = Arel::UpdateManager.new(arel.engine)
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
@@ -466,7 +468,7 @@ module ActiveRecord
# Returns sql statement for the relation.
#
# Users.where(name: 'Oscar').to_sql
- # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
+ # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
def to_sql
@to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 9cb9b4627b..5c3399e2aa 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -327,7 +327,7 @@ module ActiveRecord
def restore_transaction_record_state(force = false) #:nodoc:
if defined?(@_start_transaction_state)
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- if @_start_transaction_state[:level] < 1
+ if @_start_transaction_state[:level] < 1 || force
restore_state = remove_instance_variable(:@_start_transaction_state)
was_frozen = @attributes.frozen?
@attributes = @attributes.dup if was_frozen
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 202f7e27c5..1ff307c735 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -85,16 +85,17 @@ module ActiveRecord
assert @connection.active?
original_connection_pid = @connection.query('select pg_backend_pid()')
- # Fail with bad connection after next query attempt.
- connection_class = class << @connection ; self ; end
- connection_class.class_eval <<-CODE
+ # Fail with bad connection on next query attempt.
+ raw_connection = @connection.raw_connection
+ raw_connection_class = class << raw_connection ; self ; end
+ raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
def query_fake(*args)
- if @called ||= false
- @connection.stubs(:status).returns(PCconn::CONNECTION_BAD)
+ if !( @called ||= false )
+ self.stubs(:status).returns(PGconn::CONNECTION_BAD)
+ @called = true
raise PGError
else
- @called = true
- @connection.unstub(:status)
+ self.unstub(:status)
query_unfake(*args)
end
end
@@ -107,13 +108,13 @@ module ActiveRecord
@connection.verify!
new_connection_pid = @connection.query('select pg_backend_pid()')
ensure
- connection_class.class_eval <<-CODE
+ raw_connection_class.class_eval <<-CODE
alias query query_unfake
undef query_fake
CODE
end
- assert_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid"
+ assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid"
end
# Must have with_manual_interventions set to true for this
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index ec7e4f5fb7..5f7825783b 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -524,13 +524,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_invalid_belongs_to_dependent_option_nullify_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :nullify
+ Class.new(Author).belongs_to :special_author_address, :dependent => :nullify
end
end
def test_invalid_belongs_to_dependent_option_restrict_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :restrict
+ Class.new(Author).belongs_to :special_author_address, :dependent => :restrict
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 43440c1146..04714f42e9 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1091,9 +1091,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
firm = RestrictedFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
@@ -1101,15 +1098,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedFirm.exists?(:name => 'restrict')
assert firm.companies.exists?(:name => 'child')
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_restrict_when_dependent_restrict_raises_config_set_to_false
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
+ def test_restrict_is_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ assert_deprecated { klass.has_many :posts, dependent: :restrict }
+ end
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_exception
+ firm = RestrictedWithExceptionFirm.create!(:name => 'restrict')
+ firm.companies.create(:name => 'child')
+
+ assert !firm.companies.empty?
+ assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedWithExceptionFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ end
+
+ def test_restrict_with_error
+ firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
assert !firm.companies.empty?
@@ -1119,10 +1126,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !firm.errors.empty?
assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert RestrictedWithErrorFirm.exists?(:name => 'restrict')
assert firm.companies.exists?(:name => 'child')
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_included_in_collection
@@ -1602,18 +1607,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb1, bulb3], result
end
- def test_building_has_many_association_with_restrict_dependency
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
- klass = Class.new(ActiveRecord::Base)
-
- assert_deprecated { klass.has_many :companies, :dependent => :restrict }
- assert_not_deprecated { klass.has_many :companies }
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
- end
-
def test_collection_association_with_private_kernel_method
firm = companies(:first_firm)
assert_equal [accounts(:signals37)], firm.accounts.open
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 112735839f..8bc633f2b5 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -156,10 +156,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { firm.destroy }
end
- def test_dependence_with_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
+ def test_restrict
firm = RestrictedFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
@@ -168,38 +165,26 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
+ def test_restrict_is_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ assert_deprecated { klass.has_one :post, dependent: :restrict }
+ end
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_exception
+ firm = RestrictedWithExceptionFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
assert_not_nil firm.account
- firm.destroy
-
- assert !firm.errors.empty?
- assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedWithExceptionFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name
- old_backend = I18n.backend
- I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}}
-
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
-
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_error
+ firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
assert_not_nil firm.account
@@ -207,12 +192,9 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
assert !firm.errors.empty?
- assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
- I18n.backend = old_backend
end
def test_successful_build_association
@@ -524,15 +506,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
- def test_building_has_one_association_with_dependent_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
+ def test_has_one_transaction
+ company = companies(:first_firm)
+ account = Account.find(1)
- klass = Class.new(ActiveRecord::Base)
+ company.account # force loading
+ assert_no_queries { company.account = account }
- assert_deprecated { klass.has_one :account, :dependent => :restrict }
- assert_not_deprecated { klass.has_one :account }
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
+ company.account = nil
+ assert_no_queries { company.account = nil }
+ account = Account.find(2)
+ assert_queries { company.account = account }
end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 3cea20527e..86893ec4b3 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -385,7 +385,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).tagging
+ assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 018064233a..4c6d4666ed 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -19,9 +19,6 @@ require 'support/connection'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true
-ActiveRecord::Base.dependent_restrict_raises = false
-
# Connect to the database
ARTest.connect
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 06d6596725..42461e8ecb 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -1,9 +1,7 @@
require "cases/helper"
require 'models/entrant'
require 'models/bird'
-
-# So we can test whether Course.connection survives a reload.
-require_dependency 'models/course'
+require 'models/course'
class MultipleDbTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0bd48913e1..84027ea5ae 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1122,6 +1122,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:david), Author.order('id DESC , name DESC').last
end
+ def test_update_all_with_blank_argument
+ assert_raises(ArgumentError) { Comment.update_all({}) }
+ end
+
def test_update_all_with_joins
comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
count = comments.count
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7444dc5de1..bb034848e1 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -114,9 +114,12 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
- Pet.belongs_to :owner, :touch => :happy_at
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :touch => :happy_at
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
previously_owner_happy_at = owner.happy_at
@@ -124,14 +127,15 @@ class TimestampTest < ActiveRecord::TestCase
pet.save
assert_not_equal previously_owner_happy_at, pet.owner.happy_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent
- Pet.belongs_to :owner, :counter_cache => :use_count, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :counter_cache => :use_count, :touch => true
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
@@ -140,15 +144,15 @@ class TimestampTest < ActiveRecord::TestCase
pet.save
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_touches_parent_record_and_grandparent_record
- Toy.belongs_to :pet, :touch => true
- Pet.belongs_to :owner, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, :touch => true
+ end
- toy = Toy.first
+ toy = klass.first
pet = toy.pet
owner = pet.owner
time = 3.days.ago
@@ -158,8 +162,6 @@ class TimestampTest < ActiveRecord::TestCase
owner.reload
assert_not_equal time, owner.updated_at
- ensure
- Toy.belongs_to :pet
end
def test_timestamp_attributes_for_create
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index a9ccd00fac..d5597a68ad 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -204,6 +204,23 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_callback_rollback_in_create_with_record_invalid_exception
+ begin
+ Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
+ remove_method(:after_create_for_transaction)
+ def after_create_for_transaction
+ raise ActiveRecord::RecordInvalid.new(Author.new)
+ end
+ eoruby
+
+ new_topic = Topic.create(:title => "A new topic")
+ assert !new_topic.persisted?, "The topic should not be persisted"
+ assert_nil new_topic.id, "The topic should not have an ID"
+ ensure
+ remove_exception_raising_after_create_callback_to_topic
+ end
+ end
+
def test_nested_explicit_transactions
Topic.transaction do
Topic.transaction do
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 3157d8fe7f..77f4a2ec87 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -93,8 +93,8 @@ class Author < ActiveRecord::Base
has_many :author_favorites
has_many :favorite_authors, -> { order('name') }, :through => :author_favorites
- has_many :tagging, :through => :posts
has_many :taggings, :through => :posts
+ has_many :taggings_2, :through => :posts, :source => :tagging
has_many :tags, :through => :posts
has_many :post_categories, :through => :posts, :source => :categories
has_many :tagging_tags, :through => :taggings, :source => :tag
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 5bfbb5e855..75f38d275c 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -115,8 +115,20 @@ class DependentFirm < Company
end
class RestrictedFirm < Company
- has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict
- has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict
+ ActiveSupport::Deprecation.silence do
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict
+ end
+end
+
+class RestrictedWithExceptionFirm < Company
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_exception
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_exception
+end
+
+class RestrictedWithErrorFirm < Company
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_error
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_error
end
class Client < Company
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 359b29fac3..1134b09d8b 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -24,11 +24,10 @@ class Member < ActiveRecord::Base
has_one :club_category, :through => :club, :source => :category
- has_many :current_memberships
- has_one :club_through_many, :through => :current_memberships, :source => :club
-
has_many :current_memberships, -> { where :favourite => true }
has_many :clubs, :through => :current_memberships
+
+ has_one :club_through_many, :through => :current_memberships, :source => :club
end
class SelfMember < ActiveRecord::Base
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index c176316a05..92736e0ca9 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,6 +1,6 @@
require 'active_support/logger'
-require_dependency 'models/college'
-require_dependency 'models/course'
+require 'models/college'
+require 'models/course'
module ARTest
def self.connection_name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 5fcea299f8..b0b21a1563 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,8 +1,29 @@
## Rails 4.0.0 (unreleased) ##
-* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. *David Celis*
+* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid
+ errors with empty locales or missing values.
-* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!` *DHH*
+ *Carlos Antonio da Silva*
+
+* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and
+ #encode_json methods for custom JSON string literals.
+
+ *Erich Menge*
+
+* Add String#indent. *fxn & Ace Suares*
+
+* Inflections can now be defined per locale. `singularize` and `pluralize`
+ accept locale as an extra argument.
+
+ *David Celis*
+
+* `Object#try` will now return nil instead of raise a NoMethodError if the
+ receiving object does not implement the method, but you can still get the
+ old behavior by using the new `Object#try!`.
+
+ *DHH*
+
+* `ERB::Util.html_escape` now escapes single quotes. *Santiago Pastorino*
* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White*
@@ -84,6 +105,33 @@
* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* Fix ActiveSupport integration with Mocha > 0.12.1. *Mike Gunderloy*
+
+* Reverted the deprecation of ActiveSupport::JSON::Variable. *Rafael Mendonça França*
+
+* ERB::Util.html_escape now escapes single quotes. *Santiago Pastorino*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* Hash#fetch(fetch) is not the same as doing hash[key]
+
+* adds a missing require [fixes #6896]
+
+* make sure the inflection rules are loaded when cherry-picking active_support/core_ext/string/inflections.rb [fixes #6884]
+
+* Merge pull request #6857 from rsutphin/as_core_ext_time_missing_require
+
+* bump AS deprecation_horizon to 4.0
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 9974b61078..1079ddde98 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -37,7 +37,7 @@ class Object
public_send(*a, &b) if respond_to?(a.first)
end
end
-
+
# Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
# does not implemented the tried method.
def try!(*a, &b)
@@ -63,7 +63,7 @@ class NilClass
def try(*args)
nil
end
-
+
def try!(*args)
nil
end
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index fb36262528..ad864765a3 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -10,3 +10,4 @@ require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/inquiry'
+require 'active_support/core_ext/string/indent'
diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb
new file mode 100644
index 0000000000..afc3032272
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/indent.rb
@@ -0,0 +1,43 @@
+class String
+ # Same as +indent+, except it indents the receiver in-place.
+ #
+ # Returns the indented string, or +nil+ if there was nothing to indent.
+ def indent!(amount, indent_string=nil, indent_empty_lines=false)
+ indent_string = indent_string || self[/^[ \t]/] || ' '
+ re = indent_empty_lines ? /^/ : /^(?!$)/
+ gsub!(re, indent_string * amount)
+ end
+
+ # Indents the lines in the receiver:
+ #
+ # <<EOS.indent(2)
+ # def some_method
+ # some_code
+ # end
+ # EOS
+ # # =>
+ # def some_method
+ # some_code
+ # end
+ #
+ # The second argument, +indent_string+, specifies which indent string to
+ # use. The default is +nil+, which tells the method to make a guess by
+ # peeking at the first indented line, and fallback to a space if there is
+ # none.
+ #
+ # " foo".indent(2) # => " foo"
+ # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
+ # "foo".indent(2, "\t") # => "\t\tfoo"
+ #
+ # While +indent_string+ is tipically one space or tab, it may be any string.
+ #
+ # The third argument, +indent_empty_lines+, is a flag that says whether
+ # empty lines should be indented. Default is false.
+ #
+ # "foo\n\nbar".indent(2) # => " foo\n\n bar"
+ # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
+ #
+ def indent(amount, indent_string=nil, indent_empty_lines=false)
+ dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
+ end
+end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 2cdc991120..a0139b7d8e 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -70,7 +70,7 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- consolidated = parts.inject(::Hash.new(0)) { |h,part| h[part.first] += part.last; h }
+ consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
n = consolidated[length]
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 389df58ec4..1a95bd63e6 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
+require 'active_support/json/variable'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
new file mode 100644
index 0000000000..8af661a795
--- /dev/null
+++ b/activesupport/lib/active_support/json/variable.rb
@@ -0,0 +1,17 @@
+require 'active_support/deprecation'
+
+module ActiveSupport
+ module JSON
+ # Deprecated: A string that returns itself as its JSON-encoded form.
+ class Variable < String
+ def initialize(*args)
+ ActiveSupport::Deprecation.warn 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \
+ 'For your own custom JSON literals, define #as_json and #encode_json yourself.'
+ super
+ end
+
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) self end #:nodoc:
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index 18c7d47026..f4900dc935 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -49,7 +49,7 @@ en:
significant: false
# If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
strip_insignificant_zeros: false
-
+
# Used in NumberHelper.number_to_currency()
currency:
format:
@@ -62,7 +62,7 @@ en:
precision: 2
significant: false
strip_insignificant_zeros: false
-
+
# Used in NumberHelper.number_to_percentage()
percentage:
format:
@@ -73,7 +73,7 @@ en:
# significant: false
# strip_insignificant_zeros: false
format: "%n%"
-
+
# Used in NumberHelper.number_to_rounded()
precision:
format:
@@ -83,7 +83,7 @@ en:
# precision:
# significant: false
# strip_insignificant_zeros: false
-
+
# Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
human:
format:
@@ -131,5 +131,3 @@ en:
billion: Billion
trillion: Trillion
quadrillion: Quadrillion
-
- \ No newline at end of file
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 6ffc091233..2e5bcf4639 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -59,10 +59,10 @@ module ActiveSupport
module Subscribers # :nodoc:
def self.new(pattern, listener)
- if listener.respond_to?(:call)
- subscriber = Timed.new pattern, listener
- else
+ if listener.respond_to?(:start) and listener.respond_to?(:finish)
subscriber = Evented.new pattern, listener
+ else
+ subscriber = Timed.new pattern, listener
end
unless pattern
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 99f6489adb..3849f94a31 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -7,12 +7,108 @@ module ActiveSupport
module NumberHelper
extend self
+ DEFAULTS = {
+ # Used in number_to_delimited
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format: {
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: ".",
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ",",
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3,
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false,
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+ },
+
+ # Used in number_to_currency
+ currency: {
+ format: {
+ format: "%u%n",
+ negative_format: "-%u%n",
+ unit: "$",
+ # These five are to override number.format and are optional
+ separator: ".",
+ delimiter: ",",
+ precision: 2,
+ significant: false,
+ strip_insignificant_zeros: false
+ }
+ },
+
+ # Used in number_to_percentage
+ percentage: {
+ format: {
+ delimiter: "",
+ format: "%n%"
+ }
+ },
+
+ # Used in number_to_rounded
+ precision: {
+ format: {
+ delimiter: ""
+ }
+ },
+
+ # Used in number_to_human_size and number_to_human
+ human: {
+ format: {
+ # These five are to override number.format and are optional
+ delimiter: "",
+ precision: 3,
+ significant: true,
+ strip_insignificant_zeros: true
+ },
+ # Used in number_to_human_size
+ storage_units: {
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u",
+ units: {
+ byte: "Bytes",
+ kb: "KB",
+ mb: "MB",
+ gb: "GB",
+ tb: "TB"
+ }
+ },
+ # Used in number_to_human
+ decimal_units: {
+ format: "%n %u",
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units: {
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: "",
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: "Thousand",
+ million: "Million",
+ billion: "Billion",
+ trillion: "Trillion",
+ quadrillion: "Quadrillion"
+ }
+ }
+ }
+ }
+
DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
# Formats a +number+ into a US phone number (e.g., (555)
@@ -106,10 +202,10 @@ module ActiveSupport
return unless number
options = options.symbolize_keys
- currency = translations_for('currency', options[:locale])
+ currency = i18n_format_options(options[:locale], :currency)
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
+ defaults = default_format_options(:currency).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
@@ -160,7 +256,7 @@ module ActiveSupport
return unless number
options = options.symbolize_keys
- defaults = format_translations('percentage', options[:locale])
+ defaults = format_options(options[:locale], :percentage)
options = defaults.merge!(options)
format = options[:format] || "%n%"
@@ -197,7 +293,7 @@ module ActiveSupport
return number unless valid_float?(number)
- options = defaults_translations(options[:locale]).merge(options)
+ options = format_options(options[:locale]).merge!(options)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
@@ -248,7 +344,7 @@ module ActiveSupport
number = Float(number)
options = options.symbolize_keys
- defaults = format_translations('precision', options[:locale])
+ defaults = format_options(options[:locale], :precision)
options = defaults.merge!(options)
precision = options.delete :precision
@@ -328,18 +424,18 @@ module ActiveSupport
return number unless valid_float?(number)
number = Float(number)
- defaults = format_translations('human', options[:locale])
+ defaults = format_options(options[:locale], :human)
options = defaults.merge!(options)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+ storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
base = options[:prefix] == :si ? 1000 : 1024
if number.to_i < base
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
else
max_exp = STORAGE_UNITS.size - 1
@@ -348,7 +444,7 @@ module ActiveSupport
number /= base ** exponent
unit_key = STORAGE_UNITS[exponent]
- unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+ unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
formatted_number = self.number_to_rounded(number, options)
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
@@ -458,7 +554,7 @@ module ActiveSupport
return number unless valid_float?(number)
number = Float(number)
- defaults = format_translations('human', options[:locale])
+ defaults = format_options(options[:locale], :human)
options = defaults.merge!(options)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -473,7 +569,7 @@ module ActiveSupport
when String, Symbol
I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
when nil
- I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
+ translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
else
raise ArgumentError, ":units must be a Hash or String translation scope."
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
@@ -488,34 +584,47 @@ module ActiveSupport
when String, Symbol
I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
else
- I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
end
- decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
+ decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
formatted_number = self.number_to_rounded(number, options)
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
end
- def self.private_module_and_instance_method(method_name)
+ def self.private_module_and_instance_method(method_name) #:nodoc:
private method_name
private_class_method method_name
end
private_class_method :private_module_and_instance_method
- def format_translations(namespace, locale) #:nodoc:
- defaults_translations(locale).merge(translations_for(namespace, locale))
+ def format_options(locale, namespace = nil) #:nodoc:
+ default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
+ end
+ private_module_and_instance_method :format_options
+
+ def default_format_options(namespace = nil) #:nodoc:
+ options = DEFAULTS[:format].dup
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
+ options
end
- private_module_and_instance_method :format_translations
+ private_module_and_instance_method :default_format_options
- def defaults_translations(locale) #:nodoc:
- I18n.translate(:'number.format', :locale => locale, :default => {})
+ def i18n_format_options(locale, namespace = nil) #:nodoc:
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
+ if namespace
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
+ end
+ options
end
- private_module_and_instance_method :defaults_translations
+ private_module_and_instance_method :i18n_format_options
+
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
- def translations_for(namespace, locale) #:nodoc:
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
end
- private_module_and_instance_method :translations_for
+ private_module_and_instance_method :translate_number_value_with_default
def valid_float?(number) #:nodoc:
Float(number)
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index f3f3909a90..5f20bfa7bc 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -10,12 +10,18 @@ module ActiveSupport
# Rails.env.production?
#
class StringInquirer < String
- def method_missing(method_name, *arguments)
- if method_name[-1, 1] == "?"
- self == method_name[0..-2]
- else
- super
+ private
+
+ def respond_to_missing?(method_name, include_private = false)
+ method_name[-1] == '?'
+ end
+
+ def method_missing(method_name, *arguments)
+ if method_name[-1] == '?'
+ self == method_name[0..-2]
+ else
+ super
+ end
end
- end
end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 3b08ebe35f..dc5ae0eafc 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -9,6 +9,7 @@ require 'active_support/core_ext/string'
require 'active_support/time'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/core_ext/string/indent'
module Ace
module Base
@@ -521,3 +522,58 @@ class StringExcludeTest < ActiveSupport::TestCase
assert_equal true, 'foo'.exclude?('p')
end
end
+
+class StringIndentTest < ActiveSupport::TestCase
+ test 'does not indent strings that only contain newlines (edge cases)' do
+ ['', "\n", "\n" * 7].each do |str|
+ assert_nil str.indent!(8)
+ assert_equal str, str.indent(8)
+ assert_equal str, str.indent(1, "\t")
+ end
+ end
+
+ test "by default, indents with spaces if the existing indentation uses them" do
+ assert_equal " foo\n bar", "foo\n bar".indent(4)
+ end
+
+ test "by default, indents with tabs if the existing indentation uses them" do
+ assert_equal "\tfoo\n\t\t\bar", "foo\n\t\bar".indent(1)
+ end
+
+ test "by default, indents with spaces as a fallback if there is no indentation" do
+ assert_equal " foo\n bar\n baz", "foo\nbar\nbaz".indent(3)
+ end
+
+ # Nothing is said about existing indentation that mixes spaces and tabs, so
+ # there is nothing to test.
+
+ test 'uses the indent char if passed' do
+ assert_equal <<EXPECTED, <<ACTUAL.indent(4, '.')
+.... def some_method(x, y)
+.... some_code
+.... end
+EXPECTED
+ def some_method(x, y)
+ some_code
+ end
+ACTUAL
+
+ assert_equal <<EXPECTED, <<ACTUAL.indent(2, '&nbsp;')
+&nbsp;&nbsp;&nbsp;&nbsp;def some_method(x, y)
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;some_code
+&nbsp;&nbsp;&nbsp;&nbsp;end
+EXPECTED
+&nbsp;&nbsp;def some_method(x, y)
+&nbsp;&nbsp;&nbsp;&nbsp;some_code
+&nbsp;&nbsp;end
+ACTUAL
+ end
+
+ test "does not indent blank lines by default" do
+ assert_equal " foo\n\n bar", "foo\n\nbar".indent(1)
+ end
+
+ test 'indents blank lines if told so' do
+ assert_equal " foo\n \n bar", "foo\n\nbar".indent(1, nil, true)
+ end
+end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a947635f4a..7ed71f9abc 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -85,6 +85,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_json_variable
+ assert_deprecated do
+ assert_equal ActiveSupport::JSON::Variable.new('foo'), 'foo'
+ assert_equal ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")'
+ end
+ end
+
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb
index f77a0eb3fa..f690ad43fc 100644
--- a/activesupport/test/notifications/evented_notification_test.rb
+++ b/activesupport/test/notifications/evented_notification_test.rb
@@ -19,6 +19,12 @@ module ActiveSupport
end
end
+ class ListenerWithTimedSupport < Listener
+ def call(name, start, finish, id, payload)
+ @events << [:call, name, start, finish, id, payload]
+ end
+ end
+
def test_evented_listener
notifier = Fanout.new
listener = Listener.new
@@ -62,6 +68,20 @@ module ActiveSupport
[:finish, 'hello', 1, {}],
], listener.events
end
+
+ def test_evented_listener_priority
+ notifier = Fanout.new
+ listener = ListenerWithTimedSupport.new
+ notifier.subscribe 'hi', listener
+
+ notifier.start 'hi', 1, {}
+ notifier.finish 'hi', 1, {}
+
+ assert_equal [
+ [:start, 'hi', 1, {}],
+ [:finish, 'hi', 1, {}]
+ ], listener.events
+ end
end
end
end
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index e07198027b..65aecece71 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -56,6 +56,13 @@ module ActiveSupport
assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty'))
end
+ def test_locale_default_format_has_precedence_over_helper_defaults
+ I18n.backend.store_translations 'ts',
+ { :number => { :format => { :separator => ";" } } }
+
+ assert_equal("&$ - 10;00", number_to_currency(10, :locale => 'ts'))
+ end
+
def test_number_to_currency_without_currency_negative_format
I18n.backend.store_translations 'no_negative_format', :number => {
:currency => { :format => { :unit => '@', :format => '%n %u' } }
@@ -72,11 +79,24 @@ module ActiveSupport
assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts'))
end
+ def test_number_with_i18n_precision_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty'))
+ assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty'))
+ end
+
def test_number_with_i18n_delimiter
#Delimiter "," and separator "."
assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts'))
end
+ def test_number_with_i18n_delimiter_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty'))
+ end
+
def test_number_to_i18n_percentage
# to see if strip_insignificant_zeros is true
assert_equal("1%", number_to_percentage(1, :locale => 'ts'))
@@ -86,12 +106,27 @@ module ActiveSupport
assert_equal("12434%", number_to_percentage(12434, :locale => 'ts'))
end
+ def test_number_to_i18n_percentage_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("1.000%", number_to_percentage(1, :locale => 'empty'))
+ assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty'))
+ assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty'))
+ end
+
def test_number_to_i18n_human_size
#b for bytes and k for kbytes
assert_equal("2 k", number_to_human_size(2048, :locale => 'ts'))
assert_equal("42 b", number_to_human_size(42, :locale => 'ts'))
end
+ def test_number_to_i18n_human_size_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty'))
+ assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty'))
+ end
+
def test_number_to_human_with_default_translation_scope
#Using t for thousand
assert_equal "2 t", number_to_human(2000, :locale => 'ts')
@@ -106,6 +141,13 @@ module ActiveSupport
assert_equal "2 Tens", number_to_human(20, :locale => 'ts')
end
+ def test_number_to_human_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty')
+ assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty')
+ end
+
def test_number_to_human_with_custom_translation_scope
#Significant was set to true with precision 2, with custom translated units
assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 9b7d7f020c..5f54587f93 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -4,7 +4,7 @@ require 'active_support/number_helper'
module ActiveSupport
module NumberHelper
class NumberHelperTest < ActiveSupport::TestCase
-
+
class TestClassWithInstanceNumberHelpers
include ActiveSupport::NumberHelper
end
@@ -16,7 +16,7 @@ module ActiveSupport
def setup
@instance_with_helpers = TestClassWithInstanceNumberHelpers.new
end
-
+
def kilobytes(number)
number * 1024
end
@@ -362,14 +362,13 @@ module ActiveSupport
assert_equal "x", number_helper.number_to_human('x')
end
end
-
+
def test_extending_or_including_number_helper_correctly_hides_private_methods
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert !number_helper.respond_to?(:valid_float?)
assert number_helper.respond_to?(:valid_float?, true)
end
end
-
end
end
end
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index bb15916e9e..94d5fe197d 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -1,15 +1,23 @@
require 'abstract_unit'
class StringInquirerTest < ActiveSupport::TestCase
+ def setup
+ @string_inquirer = ActiveSupport::StringInquirer.new('production')
+ end
+
def test_match
- assert ActiveSupport::StringInquirer.new("production").production?
+ assert @string_inquirer.production?
end
def test_miss
- assert !ActiveSupport::StringInquirer.new("production").development?
+ refute @string_inquirer.development?
end
def test_missing_question_mark
- assert_raise(NoMethodError) { ActiveSupport::StringInquirer.new("production").production }
+ assert_raise(NoMethodError) { @string_inquirer.production }
+ end
+
+ def test_respond_to
+ assert_respond_to @string_inquirer, :development?
end
end
diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile
index d545798f6f..2f21f8cc71 100644
--- a/guides/source/4_0_release_notes.textile
+++ b/guides/source/4_0_release_notes.textile
@@ -82,8 +82,6 @@ will generate the model with <tt>belongs_to :supplier, polymorphic: true</tt> as
* Load all environments available in <tt>config.paths["config/environments"]</tt>.
-* The application generator generates <tt>public/humans.txt</tt> with some basic data.
-
* Add <tt>config.queue_consumer</tt> to allow the default consumer to be configurable.
* Add <tt>Rails.queue</tt> as an interface with a default implementation that consumes jobs in a separate thread.
@@ -196,6 +194,37 @@ h5(#actioncontroller_deprecations). Deprecations
h4. Action Dispatch
+* Add Routing Concerns to declare common routes that can be reused inside others resources and routes.
+
+Code before:
+
+<ruby>
+resources :messages do
+ resources :comments
+end
+
+resources :posts do
+ resources :comments
+ resources :images, only: :index
+end
+</ruby>
+
+Code after:
+
+<ruby>
+concern :commentable do
+ resources :comments
+end
+
+concern :image_attachable do
+ resources :images, only: :index
+end
+
+resources :messages, concerns: :commentable
+
+resources :posts, concerns: [:commentable, :image_attachable]
+</ruby>
+
* Show routes in exception page while debugging a <tt>RoutingError</tt> in development.
* Include <tt>mounted_helpers</tt> (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default.
@@ -747,6 +776,8 @@ h3. Active Resource
h3. Active Support
+* Add default values to all <tt>ActiveSupport::NumberHelper</tt> methods, to avoid errors with empty locales or missing values.
+
* <tt>Time#change</tt> now works with time values with offsets other than UTC or the local time zone.
* Add <tt>Time#prev_quarter</tt> and <tt>Time#next_quarter</tt> short-hands for <tt>months_ago(3)</tt> and <tt>months_since(3)</tt>.
diff --git a/guides/source/action_controller_overview.textile b/guides/source/action_controller_overview.textile
index cc3350819b..3c828735ae 100644
--- a/guides/source/action_controller_overview.textile
+++ b/guides/source/action_controller_overview.textile
@@ -478,9 +478,9 @@ class ChangesController < ActionController::Base
end
</ruby>
-Note that an around filter wraps also rendering. In particular, if in the example above the view itself reads from the database via a scope or whatever, it will do so within the transaction and thus present the data to preview.
+Note that an around filter also wraps rendering. In particular, if in the example above, the view itself reads from the database (e.g. via a scope), it will do so within the transaction and thus present the data to preview.
-They can choose not to yield and build the response themselves, in which case the action is not run.
+You can choose not to yield and build the response yourself, in which case the action will not be run.
h4. Other Ways to Use Filters
diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile
index 54e55d7260..abfa68b76d 100644
--- a/guides/source/action_mailer_basics.textile
+++ b/guides/source/action_mailer_basics.textile
@@ -4,7 +4,7 @@ This guide should provide you with all you need to get started in sending and re
endprologue.
-WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
@@ -84,7 +84,7 @@ Create a file called +welcome_email.html.erb+ in +app/views/user_mailer/+. This
</html>
</erb>
-It is also a good idea to make a text part for this email, to do this, create a file called +welcome_email.text.erb+ in +app/views/user_mailer/+:
+It is also a good idea to make a text part for this email. To do this, create a file called +welcome_email.text.erb+ in +app/views/user_mailer/+:
<erb>
Welcome to example.com, <%= @user.name %>
@@ -144,7 +144,7 @@ The method +welcome_email+ returns a <tt>Mail::Message</tt> object which can the
NOTE: In previous versions of Rails, you would call +deliver_welcome_email+ or +create_welcome_email+. This has been deprecated in Rails 3.0 in favour of just calling the method name itself.
-WARNING: Sending out an email should only take a fraction of a second, but if you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like Delayed Job.
+WARNING: Sending out an email should only take a fraction of a second. If you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like Delayed Job.
h4. Auto encoding header values
@@ -152,14 +152,14 @@ Action Mailer now handles the auto encoding of multibyte characters inside of he
If you are using UTF-8 as your character set, you do not have to do anything special, just go ahead and send in UTF-8 data to the address fields, subject, keywords, filenames or body of the email and Action Mailer will auto encode it into quoted printable for you in the case of a header field or Base64 encode any body parts that are non US-ASCII.
-For more complex examples such as defining alternate character sets or self encoding text first, please refer to the Mail library.
+For more complex examples such as defining alternate character sets or self-encoding text first, please refer to the Mail library.
h4. Complete List of Action Mailer Methods
There are just three methods that you need to send pretty much any email message:
-* <tt>headers</tt> - Specifies any header on the email you want, you can pass a hash of header field names and value pairs, or you can call <tt>headers[:field_name] = 'value'</tt>
-* <tt>attachments</tt> - Allows you to add attachments to your email, for example <tt>attachments['file-name.jpg'] = File.read('file-name.jpg')</tt>
+* <tt>headers</tt> - Specifies any header on the email you want. You can pass a hash of header field names and value pairs, or you can call <tt>headers[:field_name] = 'value'</tt>.
+* <tt>attachments</tt> - Allows you to add attachments to your email. For example, <tt>attachments['file-name.jpg'] = File.read('file-name.jpg')</tt>.
* <tt>mail</tt> - Sends the actual email itself. You can pass in headers as a hash to the mail method as a parameter, mail will then create an email, either plain text, or multipart, depending on what email templates you have defined.
h5. Custom Headers
@@ -184,7 +184,7 @@ headers["X-Spam"] = value
headers {"X-Spam" => value, "X-Special" => another_value}
</ruby>
-TIP: All <tt>X-Value</tt> headers per the RFC2822 can appear more than one time. If you want to delete an <tt>X-Value</tt> header, you need to assign it a value of <tt>nil</tt>.
+TIP: All <tt>X-Value</tt> headers per the RFC2822 can appear more than once. If you want to delete an <tt>X-Value</tt> header, you need to assign it a value of <tt>nil</tt>.
h5. Adding Attachments
@@ -196,7 +196,7 @@ Adding attachments has been simplified in Action Mailer 3.0.
attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
</ruby>
-NOTE: Mail will automatically Base64 encode an attachment, if you want something different, pre-encode your content and pass in the encoded content and encoding in a +Hash+ to the +attachments+ method.
+NOTE: Mail will automatically Base64 encode an attachment. If you want something different, pre-encode your content and pass in the encoded content and encoding in a +Hash+ to the +attachments+ method.
* Pass the file name and specify headers and content and Action Mailer and Mail will use the settings you pass in.
@@ -229,7 +229,7 @@ end
<%= image_tag attachments['image.jpg'].url %>
</erb>
-* As this is a standard call to +image_tag+ you can pass in an options hash after the attachment url as you could for any other image:
+* As this is a standard call to +image_tag+ you can pass in an options hash after the attachment URL as you could for any other image:
<erb>
<p>Hello there, this is our image</p>
@@ -240,7 +240,7 @@ end
h5. Sending Email To Multiple Recipients
-It is possible to send email to one or more recipients in one email (for e.g. informing all admins of a new signup) by setting the list of emails to the <tt>:to</tt> key. The list of emails can be an array of email addresses or a single string with the addresses separated by commas.
+It is possible to send email to one or more recipients in one email (e.g., informing all admins of a new signup) by setting the list of emails to the <tt>:to</tt> key. The list of emails can be an array of email addresses or a single string with the addresses separated by commas.
<ruby>
class AdminMailer < ActionMailer::Base
@@ -458,6 +458,7 @@ The following configuration options are best made in one of the environment file
|+perform_deliveries+|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
|+deliveries+|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
|+async+|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to <tt>Rails.queue</tt> for processing.|
+|+default_options+|Allows you to set default values for the <tt>mail</tt> method options (<tt>:from</tt>, <tt>:reply_to</tt>, etc.).|
h4. Example Action Mailer Configuration
@@ -472,6 +473,7 @@ config.action_mailer.delivery_method = :sendmail
# }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
+config.action_mailer.default_options = {from: "no-replay@example.org"}
</ruby>
h4. Action Mailer Configuration for GMail
diff --git a/guides/source/active_model_basics.textile b/guides/source/active_model_basics.textile
index 7cafff2ad8..2c30ddb84c 100644
--- a/guides/source/active_model_basics.textile
+++ b/guides/source/active_model_basics.textile
@@ -1,18 +1,18 @@
h2. Active Model Basics
-This guide should provide you with all you need to get started using model classes. Active Model allow for Action Pack helpers to interact with non-ActiveRecord models. Active Model also helps building custom ORMs for use outside of the Rails framework.
+This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-ActiveRecord models. Active Model also helps building custom ORMs for use outside of the Rails framework.
endprologue.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
-Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below -
+Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below.
h4. AttributeMethods
-AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes, which methods on the object will use them.
+The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes, which methods on the object will use them.
<ruby>
class Person
@@ -92,7 +92,7 @@ person.to_param #=> nil
h4. Dirty
-An object becomes dirty when an object is gone through one or more changes to its attributes and not yet saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Lets consider a Person class with attributes first_name and last_name
+An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes first_name and last_name
<ruby>
require 'active_model'
@@ -168,7 +168,7 @@ Track what was the previous value of the attribute.
person.first_name_was #=> "First Name"
</ruby>
-Track both previous and current value of the changed attribute. Returns an array if changed else returns nil
+Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil.
<ruby>
#attr_name_change
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index dff829a4c1..80c9260a0d 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -53,6 +53,7 @@ To retrieve objects from the database, Active Record provides several finder met
The methods are:
* +bind+
* +create_with+
+* +eager_load+
* +extending+
* +from+
* +group+
@@ -61,9 +62,11 @@ The methods are:
* +joins+
* +limit+
* +lock+
+* +none+
* +offset+
* +order+
* +none+
+* +preload+
* +readonly+
* +references+
* +reorder+
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index 66de6fd310..109228f8c7 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -1325,6 +1325,41 @@ that amount of leading whitespace.
NOTE: Defined in +active_support/core_ext/string/strip.rb+.
+h4. +indent+
+
+Indents the lines in the receiver:
+
+<ruby>
+<<EOS.indent(2)
+def some_method
+ some_code
+end
+EOS
+# =>
+ def some_method
+ some_code
+ end
+</ruby>
+
+The second argument, +indent_string+, specifies which indent string to use. The default is +nil+, which tells the method to make an educated guess peeking at the first indented line, and fallback to a space if there is none.
+
+<ruby>
+" foo".indent(2) # => " foo"
+"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
+"foo".indent(2, "\t") # => "\t\tfoo"
+</ruby>
+
+While +indent_string+ is tipically one space or tab, it may be any string.
+
+The third argument, +indent_empty_lines+, is a flag that says whether empty lines should be indented. Default is false.
+
+<ruby>
+"foo\n\nbar".indent(2) # => " foo\n\n bar"
+"foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
+</ruby>
+
+The +indent!+ method performs indentation in-place.
+
h4. Access
h5. +at(position)+
diff --git a/guides/source/api_documentation_guidelines.textile b/guides/source/api_documentation_guidelines.textile
index c6aa1f0a2b..3de5b119ee 100644
--- a/guides/source/api_documentation_guidelines.textile
+++ b/guides/source/api_documentation_guidelines.textile
@@ -127,7 +127,7 @@ class Array
end
</ruby>
-WARNING: Using a pair of +&#43;...&#43;+ for fixed-width font only works with *words*; that is: anything matching <tt>\A\w&#43;\z</tt>. For anything else use +&lt;tt&gt;...&lt;/tt&gt;+, notably symbols, setters, inline snippets, etc:
+WARNING: Using a pair of +&#43;...&#43;+ for fixed-width font only works with *words*; that is: anything matching <tt>\A\w&#43;\z</tt>. For anything else use +&lt;tt&gt;...&lt;/tt&gt;+, notably symbols, setters, inline snippets, etc.
h4. Regular Font
diff --git a/guides/source/asset_pipeline.textile b/guides/source/asset_pipeline.textile
index 8fec109951..e385ec4f17 100644
--- a/guides/source/asset_pipeline.textile
+++ b/guides/source/asset_pipeline.textile
@@ -443,6 +443,8 @@ If you have other manifests or individual stylesheets and JavaScript files to in
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
</erb>
+NOTE. Always specify an expected compiled filename that ends with js or css, even if you want to add Sass or CoffeeScript files to the precompile array.
+
The rake task also generates a +manifest.yml+ that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods to avoid handing the mapping requests back to Sprockets. A typical manifest file looks like:
<plain>
diff --git a/guides/source/association_basics.textile b/guides/source/association_basics.textile
index 2b8b1bd937..d4c0a1ba74 100644
--- a/guides/source/association_basics.textile
+++ b/guides/source/association_basics.textile
@@ -967,10 +967,13 @@ end
h6(#has_one-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated object when its owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes the associated object to also be destroyed
+* +:delete+ causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign key to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there is an associated record
+* +:restrict_with_error+ causes an error to be added to the owner if there is an associated object
h6(#has_one-foreign_key). +:foreign_key+
@@ -1307,10 +1310,13 @@ end
h6(#has_many-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete_all+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated objects when their owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes all the associated objects to also be destroyed
+* +:delete_all+ causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there are any associated records
+* +:restrict_with_error+ causes an error to be added to the owner if there are any associated objects
NOTE: This option is ignored when you use the +:through+ option on the association.
diff --git a/guides/source/caching_with_rails.textile b/guides/source/caching_with_rails.textile
index 3ee36ae971..815b2ef9c2 100644
--- a/guides/source/caching_with_rails.textile
+++ b/guides/source/caching_with_rails.textile
@@ -33,7 +33,7 @@ class ProductsController < ActionController
caches_page :index
def index
- @products = Products.all
+ @products = Product.all
end
end
</ruby>
@@ -52,7 +52,7 @@ class ProductsController < ActionController
caches_page :index
def index
- @products = Products.all
+ @products = Product.all
end
def create
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index cc5c352df4..5ed3ad4a6b 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -286,8 +286,6 @@ h4. Configuring Active Record
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
-* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
-
* +config.active_record.mass_assignment_sanitizer+ will determine the strictness of the mass assignment sanitization within Rails. Defaults to +:strict+. In this mode, mass assigning any non-+attr_accessible+ attribute in a +create+ or +update_attributes+ call will raise an exception. Setting this option to +:logger+ will only print to the log file when an attribute is being assigned and will not raise an exception.
The MySQL adapter adds one additional configuration option:
@@ -340,6 +338,12 @@ h4. Configuring Action Dispatch
* +config.action_dispatch.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class.
+* +config.action_dispatch.default_headers+ is a hash with HTTP headers that are set by default in each response. By default, this is defined as:
+
+<ruby>
+config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block' }
+</ruby>
+
* +config.action_dispatch.tld_length+ sets the TLD (top-level domain) length for the application. Defaults to +1+.
* +ActionDispatch::Callbacks.before+ takes a block of code to run before the request.
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index dd43ef795f..4bb4e3b546 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -36,20 +36,26 @@ Please don't put "feature request" items into GitHub Issues. If there's a new fe
If you'd like feedback on an idea for a feature before doing the work for make a patch, please send an email to the "rails-core mailing list":https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core. You might get no response, which means that everyone is indifferent. You might find someone who's also interested in building that feature. You might get a "This won't be accepted." But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require.
-h3. Running the Test Suite
+h3. Setting Up a Development Environment
To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer.
-h4. Install Git
+h4. The Easy Way
-Ruby on Rails uses git for source code control. The "git homepage":http://git-scm.com/ has installation instructions. There are a variety of resources on the net that will help you get familiar with git:
+The easiest way to get a development environment ready to hack is to use the "Rails development box":https://github.com/rails/rails-dev-box.
-* "Everyday Git":http://schacon.github.com/git/everyday.html will teach you just enough about git to get by.
-* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
-* "GitHub":http://help.github.com offers links to a variety of git resources.
-* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
+h4. The Hard Way
-h4. Clone the Ruby on Rails Repository
+h5. Install Git
+
+Ruby on Rails uses Git for source code control. The "Git homepage":http://git-scm.com/ has installation instructions. There are a variety of resources on the net that will help you get familiar with Git:
+
+* "Everyday Git":http://schacon.github.com/git/everyday.html will teach you just enough about Git to get by.
+* The "PeepCode screencast":https://peepcode.com/products/git on Git ($9) is easier to follow.
+* "GitHub":http://help.github.com offers links to a variety of Git resources.
+* "Pro Git":http://git-scm.com/book is an entire book about Git with a Creative Commons license.
+
+h5. Clone the Ruby on Rails Repository
Navigate to the folder where you want the Ruby on Rails source code (it will create its own +rails+ subdirectory) and run:
@@ -58,7 +64,7 @@ $ git clone git://github.com/rails/rails.git
$ cd rails
</shell>
-h4. Set up and Run the Tests
+h5. Set up and Run the Tests
The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests.
@@ -74,7 +80,7 @@ If you are on Fedora or CentOS, you can run
$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel
</shell>
-If you have any problems with these libraries, you should install them manually compiling the source code. Just follow the instructions "here":http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos .
+If you have any problems with these libraries, you should install them manually compiling the source code. Just follow the instructions at the "Red Hat/CentOS section of the Nokogiri tutorials":http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos .
Also, SQLite3 and its development files for the +sqlite3-ruby+ gem -- in Ubuntu you're done with just
@@ -128,36 +134,17 @@ $ cd actionpack
$ bundle exec ruby -Itest test/template/form_helper_test.rb
</shell>
-h4. Warnings
-
-The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-
-As of this writing (December, 2010) they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
-
-<shell>
-$ RUBYOPT=-W0 bundle exec rake test
-</shell>
-
-h4. Testing Active Record
+h5. Active Record Setup
The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (+mysql+ and +mysql2+), and once for PostgreSQL. We are going to see now how to set up the environment for them.
WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
-h5. Database Configuration
+h6. Database Configuration
The Active Record test suite requires a custom config file: +activerecord/test/config.yml+. An example is provided in +activerecord/test/config.example.yml+ which can be copied and used as needed for your environment.
-h5. SQLite3
-
-The gem +sqlite3-ruby+ does not belong to the "db" group. Indeed, if you followed the instructions above you're ready. This is how you run the Active Record test suite only for SQLite3:
-
-<shell>
-$ cd activerecord
-$ bundle exec rake test_sqlite3
-</shell>
-
-h5. MySQL and PostgreSQL
+h6. MySQL and PostgreSQL
To be able to run the suite for MySQL and PostgreSQL we need their gems. Install first the servers, their client libraries, and their development files. In Ubuntu just run
@@ -217,6 +204,15 @@ NOTE: You'll see the following warning (or localized warning) during activating
If you’re using another database, check the file +activerecord/test/config.yml+ or +activerecord/test/config.example.yml+ for default connection information. You can edit +activerecord/test/config.yml+ to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
+h3. Testing Active Record
+
+This is how you run the Active Record test suite only for SQLite3:
+
+<shell>
+$ cd activerecord
+$ bundle exec rake test_sqlite3
+</shell>
+
You can now run the tests as you did for +sqlite3+. The tasks are respectively
<shell>
@@ -225,7 +221,7 @@ test_mysql2
test_postgresql
</shell>
-As we mentioned before
+Finally,
<shell>
$ bundle exec rake test
@@ -241,6 +237,16 @@ $ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.
You can invoke +test_jdbcmysql+, +test_jdbcsqlite3+ or +test_jdbcpostgresql+ also. See the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/travis.rb+ for the test suite run by the continuous integration server.
+h4. Warnings
+
+The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
+
+As of this writing (December, 2010) they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+
+<shell>
+$ RUBYOPT=-W0 bundle exec rake test
+</shell>
+
h4. Older Versions of Ruby on Rails
If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch:
@@ -250,7 +256,7 @@ $ git branch --track 3-0-stable origin/3-0-stable
$ git checkout 3-0-stable
</shell>
-TIP: You may want to "put your git branch name in your shell prompt":http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/ to make it easier to remember which version of the code you're working with.
+TIP: You may want to "put your Git branch name in your shell prompt":http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/ to make it easier to remember which version of the code you're working with.
h3. Helping to Resolve Existing Issues
@@ -331,7 +337,7 @@ $ cd rails
$ git checkout -b my_new_branch
</shell>
-It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails git repository.
+It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails Git repository.
h4. Write Your Code
@@ -359,6 +365,31 @@ Rails follows a simple set of coding style conventions.
The above are guidelines -- please use your best judgment in using them.
+h4. Updating the CHANGELOG
+
+The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
+
+You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, commiting a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
+
+A CHANGELOG entry should summarize what was changed and should end with author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
+
+<plain>
+* Summary of a change that briefly describes what was changed. You can use multiple
+ lines and wrap them at around 80 characters. Code examples are ok, too, if needed:
+
+ class Foo
+ def bar
+ puts 'baz'
+ end
+ end
+
+ You can continue after the code example and you can attach issue number. GH#1234
+
+ * Your Name *
+</plain>
+
+Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
+
h4. Sanity Check
You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either.
@@ -367,7 +398,7 @@ You might want also to check out the "RailsBridge BugMash":http://wiki.railsbrid
h4. Commit Your Changes
-When you're happy with the code on your computer, you need to commit the changes to git:
+When you're happy with the code on your computer, you need to commit the changes to Git:
<shell>
$ git commit -a
@@ -386,7 +417,7 @@ the commit content is obvious, it may not be obvious to others. You
should add such description also if it's already present in bug tracker,
it should not be necessary to visit a webpage to check the history.
-Description can have multiple paragraps and you can use code examples
+Description can have multiple paragraphs and you can use code examples
inside, just indent it with 4 spaces:
class PostsController
@@ -495,7 +526,7 @@ It’s entirely possible that the feedback you get will suggest changes. Don’t
h4. Backporting
-Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a rails team member before backporting your changes to avoid wasted effort.
+Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort.
For simple fixes, the easiest way to backport your changes is to "extract a diff from your changes in master and apply them to the target branch":http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git.
@@ -520,9 +551,9 @@ $ git apply ~/my_changes.patch
This works well for simple changes. However, if your changes are complicated or if the code in master has deviated significantly from your target branch, it might require more work on your part. The difficulty of a backport varies greatly from case to case, and sometimes it is simply not worth the effort.
-Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the ruby versions listed in +.travis.yml+ before submitting your pull request.
+Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the Ruby versions listed in +.travis.yml+ before submitting your pull request.
-And then ... think about your next contribution!
+And then... think about your next contribution!
h3. Rails Contributors
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
index 11c837be32..de4bbb5656 100644
--- a/guides/source/engines.textile
+++ b/guides/source/engines.textile
@@ -655,15 +655,17 @@ This tells the application that you still want to perform a +GET+ request to the
h3. Improving engine functionality
-This section looks at overriding or adding functionality to the views, controllers and models provided by an engine.
+This section explains how to add and/or override engine MVC functionality in the main Rails application.
-h4. Overriding Models
+h4. Overriding Models and Controllers
-Engine Models can be extended by (1) implementing decorators, or (2) including modules.
+Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class redefines it for use in the main applicaiton. This is usually implemented by using the decorator pattern.
-h5. Decorators
+For simple class modifications use Class#class_eval, and for complex class modifications, consider using ActiveSupport::Concern.
-Decorators extends Engine's model classes in the main application by open classing Engine models at run time execution.
+h5. Implementing Decorator Pattern Using Class#class_eval
+
+**Adding** Post#time_since_created,
<ruby>
# MyApp/app/decorators/models/blorgh/post_decorator.rb
@@ -675,15 +677,48 @@ Blorgh::Post.class_eval do
end
</ruby>
-h5. Modules
+<ruby>
+# Blorgh/app/models/post.rb
+
+class Post < ActiveRecord::Base
+ :has_many :comments
+end
+</ruby>
+
+
+**Overriding** Post#summary
-The other strategy is to create modules within the Engine holding all the models' code and include these in the respective Engine's model classes. Thus the Engine's model classes contain a mere include line referencing the respective module.
+<ruby>
+# MyApp/app/decorators/models/blorgh/post_decorator.rb
-Engine models can be overriden in the main application by creating a file with the Engine's same namespace and including the module originally referenced in the Engine's model class. ["**ActiveSupport::Concern**":http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html] helps manage the correct ordering of module dependencies at run time (it's worth it to reach the linked documentation).
+Blorgh::Post.class_eval do
+ def summary
+ "#{title} - #{truncate(text)}"
+ end
+end
+</ruby>
+
+<ruby>
+# Blorgh/app/models/post.rb
+
+class Post < ActiveRecord::Base
+ :has_many :comments
+ def summary
+ "#{title}"
+ end
+end
+</ruby>
+
+
+h5. Implementing Decorator Pattern Using ActiveSupport::Concern
+
+Using Class#class_eval is great for simple adjustments, but for more complex class modifications, you might want to consider using ActiveSupport::Concern. ["**ActiveSupport::Concern**":http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html] helps manage load order of interlinked dependencies at run time allowing you to significantly modularize your code.
+
+**Adding** Post#time_since_created<br/>
+**Overriding** Post#summary
<ruby>
# MyApp/app/models/blorgh/post.rb
-# overrides Blorgh's original Post model
class Blorgh::Post < ActiveRecord::Base
include Blorgh::Concerns::Models::Post
@@ -691,24 +726,30 @@ class Blorgh::Post < ActiveRecord::Base
def time_since_created
Time.current - created_at
end
-end
+ def summary
+ "#{title} - #{truncate(text)}"
+ end
+end
+</ruby>
+<ruby>
# Blorgh/app/models/post.rb
-# this class is overriden by the MyApp Post model
class Post < ActiveRecord::Base
include Blorgh::Concerns::Models::Post
end
+</ruby>
+<ruby>
+# Blorgh/lib/concerns/models/post
-# Blorgh/app/concerns/models/post
-
-module Blorg::Concerns::Models::Post
+module Blorgh::Concerns::Models::Post
extend ActiveSupport::Concern
- # 'included do' causes the code within to be evaluated in the conext
- # where it is included, rather be executed in the module's context.
+ # 'included do' causes the included code to be evaluated in the
+ # conext where it is included (post.rb), rather than be
+ # executed in the module's context (blorgh/concerns/models/post).
included do
attr_accessor :author_name
belongs_to :author, :class_name => "User"
@@ -722,9 +763,8 @@ module Blorg::Concerns::Models::Post
end
end
- # methods defined here will be instance methods by default
- def some_method
- 'some method string'
+ def summary
+ "#{title}"
end
module ClassMethods
diff --git a/guides/source/i18n.textile b/guides/source/i18n.textile
index c782539399..67863e590c 100644
--- a/guides/source/i18n.textile
+++ b/guides/source/i18n.textile
@@ -565,7 +565,7 @@ I18n.translate :thanks, :name => 'Jeremy'
# => 'Thanks Jeremy!'
</ruby>
-If a translation uses +:default+ or +:scope+ as an interpolation variable, an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable, but this has not been passed to +#translate+, an +I18n::MissingInterpolationArgument+ exception is raised.
+If a translation uses +:default+ or +:scope+ as an interpolation variable, an +I18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable, but this has not been passed to +#translate+, an +I18n::MissingInterpolationArgument+ exception is raised.
h4. Pluralization
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile
index 43d89eac4b..b23f31cb1a 100644
--- a/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -17,7 +17,7 @@ NOTE: Paths in this guide are relative to Rails or a Rails application unless ot
TIP: If you want to follow along while browsing the Rails "source
code":https://github.com/rails/rails, we recommend that you use the +t+
-key binding to open the file finder inside github and find files
+key binding to open the file finder inside GitHub and find files
quickly.
h3. Launch!
@@ -359,7 +359,7 @@ set earlier) is required.
h4. +config/application+
When +require APP_PATH+ is executed, +config/application.rb+ is loaded.
-This is a file exists in your app and it's free for you to change based
+This file exists in your app and it's free for you to change based
on your needs.
h4. +Rails::Server#start+
@@ -453,7 +453,7 @@ end
The interesting part for a Rails app is the last line, +server.run+. Here we encounter the +wrapped_app+ method again, which this time
we're going to explore more (even though it was executed before, and
-thus memoized by now).
+thus memorized by now).
<ruby>
@wrapped_app ||= build_app app
@@ -540,12 +540,12 @@ end
</ruby>
This is where all the Rails frameworks are loaded and thus made
-available to the application. We wont go into detail of what happens
+available to the application. We won't go into detail of what happens
inside each of those frameworks, but you're encouraged to try and
explore them on your own.
For now, just keep in mind that common functionality like Rails engines,
-I18n and Rails configuration is all bein defined here.
+I18n and Rails configuration is all being defined here.
h4. Back to +config/environment.rb+
@@ -657,10 +657,10 @@ def self.run(app, options={})
end
</ruby>
-We wont dig into the server configuration itself, but this is
+We won't dig into the server configuration itself, but this is
the last piece of our journey in the Rails initialization process.
-This high level overview will help you understand when you code is
+This high level overview will help you understand when your code is
executed and how, and overall become a better Rails developer. If you
still want to know more, the Rails source code itself is probably the
best place to go next.
diff --git a/guides/source/kindle/KINDLE.md b/guides/source/kindle/KINDLE.md
index a7d9a4e4cf..08937e053e 100644
--- a/guides/source/kindle/KINDLE.md
+++ b/guides/source/kindle/KINDLE.md
@@ -9,16 +9,16 @@
## Resources
- * [StackOverflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format)
+ * [Stack Overflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format)
* Example Periodical [.ncx](https://gist.github.com/808c971ed087b839d462) and [.opf](https://gist.github.com/d6349aa8488eca2ee6d0)
- * [Kindle Publishing guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf)
+ * [Kindle Publishing Guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf)
* [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621)
## TODO
### Post release
- * Integrate generated Kindle document in to published HTML guides
+ * Integrate generated Kindle document into published HTML guides
* Tweak heading styles (most docs use h3/h4/h5, which end up being smaller than the text under it)
* Tweak table styles (smaller text? Many of the tables are unusable on a Kindle in portrait mode)
* Have the HTML/XML TOC 'drill down' into the TOCs of the individual guides
diff --git a/guides/source/kindle/welcome.html.erb b/guides/source/kindle/welcome.html.erb
index e30704c4e6..610a71570f 100644
--- a/guides/source/kindle/welcome.html.erb
+++ b/guides/source/kindle/welcome.html.erb
@@ -2,4 +2,4 @@
<h3>Kindle Edition</h3>
-The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome, please see the "Feedback" section at the end of each guide for instructions.
+The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome. Please see the "Feedback" section at the end of each guide for instructions.
diff --git a/guides/source/performance_testing.textile b/guides/source/performance_testing.textile
index b7d4f1ef33..6c5676c346 100644
--- a/guides/source/performance_testing.textile
+++ b/guides/source/performance_testing.textile
@@ -649,6 +649,7 @@ h4. Rails Plugins and Gems
* "Palmist":http://www.flyingmachinestudios.com/programming/announcing-palmist
* "Rails Footnotes":https://github.com/josevalim/rails-footnotes/tree/master
* "Query Reviewer":https://github.com/dsboulder/query_reviewer/tree/master
+* "MiniProfiler":http://www.miniprofiler.com
h4. Generic Tools
@@ -668,4 +669,4 @@ Rails has been lucky to have a few companies dedicated to Rails-specific
performance tools. A couple of those are:
* "New Relic":http://www.newrelic.com
-* "Scout":http://scoutapp.com \ No newline at end of file
+* "Scout":http://scoutapp.com
diff --git a/guides/source/plugins.textile b/guides/source/plugins.textile
index 9001857a5f..fbd317f0c2 100644
--- a/guides/source/plugins.textile
+++ b/guides/source/plugins.textile
@@ -13,7 +13,7 @@ After reading this guide you should be familiar with:
This guide describes how to build a test-driven plugin that will:
-* Extend core ruby classes like Hash and String
+* Extend core Ruby classes like Hash and String
* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins
* Give you information about where to put generators in your plugin.
@@ -28,7 +28,7 @@ h3. Setup
_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in
Rails 3.2, and will not be available in the future.
-Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared accross
+Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared across
different rails applications using RubyGems and Bundler if desired.
h4. Generate a gemified plugin.
@@ -392,7 +392,7 @@ the creation of generators can be found in the "Generators Guide":generators.htm
h3. Publishing your Gem
Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply
-commit the code to a Git repository (like Github) and add a line to the Gemfile of the application in question:
+commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question:
<ruby>
gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
@@ -401,7 +401,7 @@ gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
After running +bundle install+, your gem functionality will be available to the application.
When the gem is ready to be shared as a formal release, it can be published to "RubyGems":http://www.rubygems.org.
-For more information about publishing gems to RubyGems, see: "http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html":http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
+For more information about publishing gems to RubyGems, see: "Creating and Publishing Your First Ruby Gem":http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
h3. RDoc Documentation
@@ -414,7 +414,7 @@ The first step is to update the README file with detailed information about how
* How to add the functionality to the app (several examples of common use cases)
* Warnings, gotchas or tips that might help users and save them time
-Once your README is solid, go through and add rdoc comments to all of the methods that developers will use. It's also customary to add '#:nodoc:' comments to those parts of the code that are not included in the public api.
+Once your README is solid, go through and add rdoc comments to all of the methods that developers will use. It's also customary to add '#:nodoc:' comments to those parts of the code that are not included in the public API.
Once your comments are good to go, navigate to your plugin directory and run:
@@ -425,6 +425,6 @@ $ rake rdoc
h4. References
* "Developing a RubyGem using Bundler":https://github.com/radar/guides/blob/master/gem-development.md
-* "Using Gemspecs As Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
+* "Using .gemspecs as Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
* "Gemspec Reference":http://docs.rubygems.org/read/chapter/20
-* "GemPlugins":http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
+* "GemPlugins: A Brief Introduction to the Future of Rails Plugins":http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index cffbf9bec4..bed7d03e06 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -273,6 +273,36 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
TIP: _Resources should never be nested more than 1 level deep._
+h4. Routing concerns
+
+Routing Concerns allows you to declare common routes that can be reused inside others resources and routes.
+
+<ruby>
+concern :commentable do
+ resources :comments
+end
+
+concern :image_attachable do
+ resources :images, only: :index
+end
+</ruby>
+
+These concerns can be used in resources to avoid code duplication and share behavior across routes.
+
+<ruby>
+resources :messages, concerns: :commentable
+
+resources :posts, concerns: [:commentable, :image_attachable]
+</ruby>
+
+Also you can use them in any place that you want inside the routes, for example in a scope or namespace call:
+
+<ruby>
+namespace :posts do
+ concerns :commentable
+end
+</ruby>
+
h4. Creating Paths and URLs From Objects
In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 5165d9401f..851f41249a 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Correctly handle SCRIPT_NAME when generating routes to engine in application
+ that's mounted at a sub-uri. With this behavior, you *should not* use
+ default_url_options[:script_name] to set proper application's mount point by
+ yourself. *Piotr Sarnacki*
+
* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
@@ -31,8 +36,6 @@
* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
-* The application generator generates `public/humans.txt` with some basic data. *Paul Campbell*
-
* Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva*
* Add Rails.queue as an interface with a default implementation that consumes jobs in a separate thread. *Yehuda Katz*
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index ee8bb55f38..2a69c26deb 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -1,9 +1,6 @@
require 'active_support/all'
-require 'active_support/test_case'
require 'action_controller'
-# work around the at_exit hook in test/unit, which kills IRB
-
module Rails
module ConsoleMethods
# reference the global "app" instance, created on demand. To recreate the
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index f469c334a7..40f35ae5a6 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -494,7 +494,11 @@ module Rails
# Define the Rack API for this engine.
def call(env)
- app.call(env.merge!(env_config))
+ env.merge!(env_config)
+ if env['SCRIPT_NAME']
+ env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
+ end
+ app.call(env)
end
# Defines additional Rack env configuration that is added on each call.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 5915d20010..b2b760ee7b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -27,9 +27,6 @@ module <%= app_const_base %>
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
- # Activate observers that should always be running.
- # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
-
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
@@ -54,11 +51,6 @@ module <%= app_const_base %>
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
# parameters by using an attr_accessible or attr_protected declaration.
<%= comment_if :skip_active_record %>config.active_record.whitelist_attributes = true
-
- # Specifies whether or not has_many or has_one association option :dependent => :restrict raises
- # an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be
- # raised. If set to false, then an error will be added on the model instead.
- <%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false
<% unless options.skip_sprockets? -%>
# Enable the asset pipeline.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 072aa8355d..2ffae66a57 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -20,9 +20,6 @@
# Generate digests for assets URLs.
config.assets.digest = true
-
- # Defaults to nil and saved in location specified by config.assets.prefix
- # config.assets.manifest = YOUR_PATH
<%- end -%>
# Specifies the header that your server uses for sending files.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
index 179c14ca52..0653957166 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
@@ -1,5 +1,23 @@
-# Sample localization file for English. Add more files in this directory for other locales.
-# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+# Files in the config/locales directory are used for internationalization
+# and are automatically loaded by Rails. If you want to use locales other
+# than English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t 'hello'
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t('hello') %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# To learn more, please read the Rails Internationalization guide
+# available at http://guides.rubyonrails.org/i18n.html.
en:
hello: "Hello world"
diff --git a/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt b/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
deleted file mode 100644
index f081e08b6c..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
+++ /dev/null
@@ -1,7 +0,0 @@
-# See more about this file at: http://humanstxt.org/
-# For format suggestions, see: http://humanstxt.org/Standard.html
-/* TEAM */
-
-/* APP */
- Name: <%= app_const_base %>
- Software: Ruby on Rails
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 0d6c10328f..0968765b4f 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -2,10 +2,16 @@ namespace :tmp do
desc "Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)"
task :clear => [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"]
+ tmp_dirs = [ 'tmp/sessions',
+ 'tmp/cache',
+ 'tmp/sockets',
+ 'tmp/pids',
+ 'tmp/cache/assets' ]
+
+ tmp_dirs.each { |d| directory d }
+
desc "Creates tmp directories for sessions, cache, sockets, and pids"
- task :create do
- FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids tmp/cache/assets ))
- end
+ task :create => tmp_dirs
namespace :sessions do
# desc "Clears all files in tmp/sessions"
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index dd3af86b99..c29fa4d95f 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -106,20 +106,28 @@ module ApplicationTests
precompile!
images_should_compile.each do |filename|
- assert File.exists?("#{app_path}/public/assets/#{filename}")
+ assert_file_exists "#{app_path}/public/assets/#{filename}"
end
- assert File.exists?("#{app_path}/public/assets/application.js")
- assert File.exists?("#{app_path}/public/assets/application.css")
+ assert_file_exists("#{app_path}/public/assets/application.js")
+ assert_file_exists("#{app_path}/public/assets/application.css")
+
+ assert_no_file_exists("#{app_path}/public/assets/someapplication.js")
+ assert_no_file_exists("#{app_path}/public/assets/someapplication.css")
- assert !File.exists?("#{app_path}/public/assets/someapplication.js")
- assert !File.exists?("#{app_path}/public/assets/someapplication.css")
+ assert_no_file_exists("#{app_path}/public/assets/something.min.js")
+ assert_no_file_exists("#{app_path}/public/assets/something.min.css")
- assert !File.exists?("#{app_path}/public/assets/something.min.js")
- assert !File.exists?("#{app_path}/public/assets/something.min.css")
+ assert_no_file_exists("#{app_path}/public/assets/something.else.js")
+ assert_no_file_exists("#{app_path}/public/assets/something.else.css")
+ end
+
+ def assert_file_exists(filename)
+ assert File.exists?(filename), "missing #{filename}"
+ end
- assert !File.exists?("#{app_path}/public/assets/something.else.js")
- assert !File.exists?("#{app_path}/public/assets/something.else.css")
+ def assert_no_file_exists(filename)
+ assert !File.exists?(filename), "#{filename} does exist"
end
test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 5534476a6d..c294bfb238 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -213,7 +213,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/database.yml"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "config/application.rb", /#\s+config\.active_record\.whitelist_attributes = true/
- assert_file "config/application.rb", /#\s+config\.active_record\.dependent_restrict_raises = false/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
@@ -367,21 +366,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /config\.active_record\.whitelist_attributes = true/
end
- def test_active_record_dependent_restrict_raises_is_present_application_config
- run_generator
- assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/
- end
-
def test_pretend_option
output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
assert_no_match(/run bundle install/, output)
end
- def test_humans_txt_file
- run_generator [File.join(destination_root, 'things-43')]
- assert_file "things-43/public/humans.txt", /Name: Things43/, /Software: Ruby on Rails/
- end
-
protected
def action(*args, &block)
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 6071cd3f39..8f04692aef 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -253,7 +253,6 @@ module TestHelpers
:activerecord] - arr
if to_remove.include? :activerecord
remove_from_config "config.active_record.whitelist_attributes = true"
- remove_from_config "config.active_record.dependent_restrict_raises = false"
end
$:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' }
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 63814f7a04..e52b3efdab 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1193,6 +1193,54 @@ YAML
last_response.body.split("\n").map(&:strip)
end
+ test "paths are properly generated when application is mounted at sub-path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ app_file "app/controllers/bar_controller.rb", <<-RUBY
+ class BarController < ApplicationController
+ def index
+ render :text => bukkits.bukkit_path
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/bar' => 'bar#index', :as => 'bar'
+ mount Bukkits::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ get '/bukkit' => 'bukkit#index'
+ end
+ RUBY
+
+
+ @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
+ class Bukkits::BukkitController < ActionController::Base
+ def index
+ render :text => main_app.bar_path
+ end
+ end
+ RUBY
+
+ boot_rails
+
+ get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bar', last_response.body
+
+ get("/bar", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bukkits/bukkit', last_response.body
+ end
+
private
def app
Rails.application
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 4c0fdee556..bd13c3aba3 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -163,24 +163,14 @@ module ApplicationTests
end
end
- def reset_script_name!
- Rails.application.routes.default_url_options = {}
- end
-
- def script_name(script_name)
- Rails.application.routes.default_url_options = {:script_name => script_name}
- end
-
test "routes generation in engine and application" do
# test generating engine's route from engine
get "/john/blog/posts"
assert_equal "/john/blog/posts/1", last_response.body
# test generating engine's route from engine with default_url_options
- script_name "/foo"
get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts/1", last_response.body
- reset_script_name!
# test generating engine's route from application
get "/engine_route"
@@ -193,14 +183,11 @@ module ApplicationTests
assert_equal "/john/blog/posts", last_response.body
# test generating engine's route from application with default_url_options
- script_name "/foo"
get "/engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/anonymous/blog/posts", last_response.body
- script_name "/foo"
get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts", last_response.body
- reset_script_name!
# test generating application's route from engine
get "/someone/blog/generate_application_route"
@@ -210,10 +197,8 @@ module ApplicationTests
assert_equal "/", last_response.body
# test generating application's route from engine with default_url_options
- script_name "/foo"
get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/foo/", last_response.body
- reset_script_name!
# test polymorphic routes
get "/polymorphic_route"