aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile6
-rw-r--r--RAILS_VERSION2
-rw-r--r--actionmailer/CHANGELOG30
-rw-r--r--actionmailer/README.rdoc2
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/lib/action_mailer/old_api.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb4
-rw-r--r--actionpack/CHANGELOG61
-rw-r--r--actionpack/README.rdoc2
-rw-r--r--actionpack/actionpack.gemspec6
-rw-r--r--actionpack/lib/abstract_controller/base.rb38
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb28
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb11
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb16
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb20
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb5
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb7
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb56
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb53
-rw-r--r--actionpack/lib/action_view/lookup_context.rb10
-rw-r--r--actionpack/lib/action_view/path_set.rb2
-rw-r--r--actionpack/lib/action_view/template/error.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb62
-rw-r--r--actionpack/lib/sprockets/railtie.rb12
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb8
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb45
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb25
-rw-r--r--actionpack/test/dispatch/request_test.rb7
-rw-r--r--actionpack/test/dispatch/response_body_is_proc_test.rb37
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb4
-rw-r--r--actionpack/test/template/capture_helper_test.rb16
-rw-r--r--actionpack/test/template/date_helper_test.rb59
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb1
-rw-r--r--actionpack/test/template/lookup_context_test.rb14
-rw-r--r--actionpack/test/template/render_test.rb2
-rw-r--r--activemodel/CHANGELOG35
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb46
-rw-r--r--activemodel/lib/active_model/observer_array.rb45
-rw-r--r--activemodel/lib/active_model/observing.rb9
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb1
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb4
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb4
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb24
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb30
-rw-r--r--activemodel/test/models/contact.rb4
-rw-r--r--activerecord/CHANGELOG141
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb71
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb5
-rw-r--r--activerecord/lib/active_record/base.rb108
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/fixtures.rb633
-rw-r--r--activerecord/lib/active_record/identity_map.rb42
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake18
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb11
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb7
-rw-r--r--activerecord/lib/active_record/session_store.rb6
-rw-r--r--activerecord/lib/active_record/test_case.rb7
-rw-r--r--activerecord/lib/active_record/validations.rb6
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb50
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb81
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb51
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb146
-rw-r--r--activerecord/test/cases/calculations_test.rb11
-rw-r--r--activerecord/test/cases/finder_test.rb21
-rw-r--r--activerecord/test/cases/fixtures_test.rb44
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/identity_map_test.rb35
-rw-r--r--activerecord/test/cases/lifecycle_test.rb25
-rw-r--r--activerecord/test/cases/locking_test.rb36
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb82
-rw-r--r--activerecord/test/cases/method_scoping_test.rb22
-rw-r--r--activerecord/test/cases/named_scope_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb4
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/session_store/session_test.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb26
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb5
-rw-r--r--activerecord/test/fixtures/all/people.yml (renamed from activerecord/test/fixtures/all/people.csv)0
-rw-r--r--activerecord/test/fixtures/mateys.yml4
-rw-r--r--activerecord/test/fixtures/memberships.yml7
-rw-r--r--activerecord/test/fixtures/parrots_pirates.yml8
-rw-r--r--activerecord/test/fixtures/string_key_objects.yml7
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/car.rb5
-rw-r--r--activerecord/test/models/member.rb2
-rw-r--r--activerecord/test/models/membership.rb6
-rw-r--r--activerecord/test/models/string_key_object.rb3
-rw-r--r--activerecord/test/schema/schema.rb7
-rw-r--r--activeresource/CHANGELOG30
-rw-r--r--activeresource/lib/active_resource.rb2
-rw-r--r--activeresource/lib/active_resource/version.rb2
-rw-r--r--activeresource/test/cases/format_test.rb2
-rw-r--r--activesupport/CHANGELOG34
-rw-r--r--activesupport/README.rdoc2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb2
-rw-r--r--activesupport/lib/active_support/configurable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb1
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb16
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb8
-rw-r--r--activesupport/lib/active_support/json/encoding.rb5
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb1
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb4
-rw-r--r--activesupport/lib/active_support/secure_random.rb205
-rw-r--r--activesupport/lib/active_support/testing/performance.rb131
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini.rb5
-rw-r--r--activesupport/test/buffered_logger_test.rb39
-rw-r--r--activesupport/test/class_cache_test.rb2
-rw-r--r--activesupport/test/configurable_test.rb7
-rw-r--r--activesupport/test/core_ext/duration_test.rb1
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb1
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb (renamed from activesupport/test/descendants_tracker_test.rb)36
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb35
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb8
-rw-r--r--activesupport/test/inflector_test_cases.rb30
-rw-r--r--activesupport/test/json/encoding_test.rb33
-rw-r--r--activesupport/test/multibyte_chars_test.rb1
-rw-r--r--activesupport/test/ordered_hash_test.rb6
-rw-r--r--activesupport/test/xml_mini_test.rb (renamed from activesupport/test/test_xml_mini.rb)20
-rw-r--r--railties/CHANGELOG48
-rw-r--r--railties/guides/source/active_support_core_extensions.textile8
-rw-r--r--railties/guides/source/security.textile10
-rw-r--r--railties/guides/source/testing.textile4
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/application/bootstrap.rb3
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/commands.rb3
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb24
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/engine.rb1
-rw-r--r--railties/lib/rails/engine/configuration.rb1
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb36
-rw-r--r--railties/lib/rails/generators/named_base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb19
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore1
-rw-r--r--railties/lib/rails/generators/rails/assets/assets_generator.rb8
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb4
-rw-r--r--railties/lib/rails/tasks/framework.rake9
-rw-r--r--railties/lib/rails/tasks/routes.rake3
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/assets_test.rb27
-rw-r--r--railties/test/application/configuration_test.rb47
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/application/rack/logger_test.rb40
-rw-r--r--railties/test/application/rake_test.rb22
-rw-r--r--railties/test/application/routing_test.rb4
-rw-r--r--railties/test/application/test_test.rb25
-rw-r--r--railties/test/generators/app_generator_test.rb24
-rw-r--r--railties/test/generators/mailer_generator_test.rb28
-rw-r--r--railties/test/generators/namespaced_generators_test.rb6
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb8
-rw-r--r--railties/test/generators/shared_generator_tests.rb56
-rw-r--r--railties/test/railties/engine_test.rb28
-rw-r--r--railties/test/railties/shared_tests.rb3
-rw-r--r--version.rb2
219 files changed, 2873 insertions, 1326 deletions
diff --git a/Gemfile b/Gemfile
index 935ecff355..7199301b90 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,7 +10,7 @@ end
gem "coffee-script"
gem "sass"
-gem "uglifier"
+gem "uglifier", :git => 'git://github.com/lautis/uglifier.git'
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
@@ -27,7 +27,6 @@ gem "memcache-client", ">= 1.8.5"
platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
- gem 'ruby-prof'
gem "json"
end
@@ -44,6 +43,9 @@ platforms :ruby do
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
+ group :test do
+ gem 'ruby-prof'
+ end
# AR
gem "sqlite3", "~> 1.3.3"
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 30afce968e..b4e716a7c1 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-3.1.0.beta
+3.1.0.beta1
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index 167c1da9c5..d4475bc951 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -2,14 +2,42 @@
* No changes
-*Rails 3.0.2 (unreleased)*
+
+*Rails 3.0.7 (April 18, 2011)*
+
+* remove AM delegating register_observer and register_interceptor to Mail [Josh Kalderimis]
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino]
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* No changes.
+
+
+*Rails 3.0.2 (November 15, 2010)*
* No changes
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") [JK]
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 9b206fbcc7..af9bf40f9e 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -157,5 +157,5 @@ API documentation is at
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
+* https://github.com/rails/rails/issues
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1e91d62e24..f00a0c8ae0 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -349,8 +349,7 @@ module ActionMailer #:nodoc:
include AbstractController::Translation
include AbstractController::AssetPaths
- cattr_reader :protected_instance_variables
- @@protected_instance_variables = []
+ self.protected_instance_variables = %w(@_action_has_layout)
helper ActionMailer::MailHelper
include ActionMailer::OldApi
diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb
index 04728cafb0..bfa9499764 100644
--- a/actionmailer/lib/action_mailer/old_api.rb
+++ b/actionmailer/lib/action_mailer/old_api.rb
@@ -8,7 +8,7 @@ module ActionMailer
included do
extend ActionMailer::AdvAttrAccessor
- self.protected_instance_variables.concat %w(@parts @mail_was_called)
+ self.protected_instance_variables.concat %w(@parts @mail_was_called @headers)
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 27eb6c2b9b..8cf3780fbc 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -3,7 +3,7 @@ module ActionMailer
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
index 370a508cad..88b074cef5 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
@@ -1,6 +1,6 @@
<% module_namespacing do -%>
class <%= class_name %> < ActionMailer::Base
- default :from => "from@example.com"
+ default <%= key_value :from, '"from@example.com"' %>
<% for action in actions -%>
# Subject can be set in your I18n file at config/locales/en.yml
@@ -11,7 +11,7 @@ class <%= class_name %> < ActionMailer::Base
def <%= action %>
@greeting = "Hi"
- mail :to => "to@example.org"
+ mail <%= key_value :to, '"to@example.org"' %>
end
<% end -%>
end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index ba01c4749f..68076b794e 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,11 @@
*Rails 3.1.0 (unreleased)*
+* Warn if we cannot verify CSRF token authenticity [José Valim]
+
+* Allow AM/PM format in datetime selectors [Aditya Sanghi]
+
+* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH]
+
* auto_link has been removed with no replacement. If you still use auto_link
please install the rails_autolink gem:
http://github.com/tenderlove/rails_autolink
@@ -112,8 +118,6 @@ tested.
Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis]
-* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim]
-
* Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim]
* :rhtml and :rxml were finally removed as template handlers. [José Valim]
@@ -129,7 +133,58 @@ tested.
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
-*Rails 3.0.2 (unreleased)*
+*Rails 3.0.7 (April 18, 2011)*
+
+*No changes.
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* Fixed XSS vulnerability in `auto_link`. `auto_link` no longer marks input as
+ html safe. Please make sure that calls to auto_link() are wrapped in a
+ sanitize(), or a raw() depending on the type of input passed to auto_link().
+ For example:
+
+ <%= sanitize(auto_link(some_user_input)) %>
+
+ Thanks to Torben Schulz for reporting this. The fix can be found here:
+ 61ee3449674c591747db95f9b3472c5c3bd9e84d
+
+* Fixes the output of `rake routes` to be correctly match to the behavior of the application, as the regular expression used to match the path is greedy and won't capture the format part by default [Prem Sichanugrist]
+
+* Fixes an issue with number_to_human when converting values which are less than 1 but greater than -1 [Josh Kalderimis]
+
+* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn]
+
+* URL parameters which return nil for to_param are now removed from the query string [Andrew White]
+
+* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino]
+
+* Make TranslationHelper#translate use the :rescue_format option in I18n 0.5.0 [Sven Fuchs]
+
+* Fix regression: javascript_include_tag shouldn't raise if you register an expansion key with nil or [] value [Santiago Pastorino]
+
+* Fix Action caching bug where an action that has a non-cacheable response always renders a nil response body. It now correctly renders the response body. [Cheah Chu Yeow]
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* When ActiveRecord::Base objects are sent to predicate methods, the id of the object should be sent to ARel, not the ActiveRecord::Base object.
+
+* :constraints routing should only do sanity checks against regular expressions. String arguments are OK.
+
+
+*Rails 3.0.2 (November 15, 2010)*
* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson]
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 1a56c44db5..5db4cff66b 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -338,4 +338,4 @@ API documentation is at
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
+* https://github.com/rails/rails/issues
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 4aa2c894f9..606abac83a 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -19,13 +19,13 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.0.0')
+ s.add_dependency('rack-cache', '~> 1.0.1')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6.0beta1')
s.add_dependency('rack', '~> 1.3.0.beta')
s.add_dependency('rack-test', '~> 0.6.0')
- s.add_dependency('rack-mount', '~> 0.7.2')
+ s.add_dependency('rack-mount', '~> 0.8.0')
s.add_dependency('sprockets', '~> 2.0.0.beta.2')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('tzinfo', '~> 0.3.27')
s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 0951267fea..f67d0e558e 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -130,27 +130,39 @@ module AbstractController
self.class.action_methods
end
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
+ # Returns true if a method for the action is available and
+ # can be dispatched, false otherwise.
#
- # For instance, this is overriden by ActionController to add
- # the implicit rendering feature.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def action_method?(name)
- self.class.action_methods.include?(name)
+ # Notice that <tt>action_methods.include?("foo")</tt> may return
+ # false and <tt>available_action?("foo")</tt> returns true because
+ # available action consider actions that are also available
+ # through other means, for example, implicit render ones.
+ def available_action?(action_name)
+ method_for_action(action_name).present?
end
private
+ # Returns true if the name can be considered an action because
+ # it has a method defined in the controller.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ #
+ # :api: private
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
+
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
+ #
+ # Notice that the first argument is the method to be dispatched
+ # which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index f7b2b7ff53..e8426bc52b 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -13,8 +13,8 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
- def process_action(method_name, *args)
- run_callbacks(:process_action, method_name) do
+ def process_action(*args)
+ run_callbacks(:process_action, action_name) do
super
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index d1b87b67ee..8f73e244d7 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -292,15 +292,15 @@ module AbstractController
end
end
- attr_writer :action_has_layout
+ attr_internal_writer :action_has_layout
def initialize(*)
- @action_has_layout = true
+ @_action_has_layout = true
super
end
def action_has_layout?
- @action_has_layout
+ @_action_has_layout
end
private
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index f78365afdb..ab2c532859 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -32,9 +32,13 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
-
include AbstractController::ViewPaths
+ included do
+ config_accessor :protected_instance_variables, :instance_reader => false
+ self.protected_instance_variables = []
+ end
+
# Overwrite process to setup I18n proxy.
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
@@ -53,14 +57,20 @@ module AbstractController
end
end
- attr_writer :view_context_class
+ attr_internal_writer :view_context_class
+
+ # Explicitly define protected_instance_variables so it can be
+ # inherited and overwritten by other modules if needed.
+ def protected_instance_variables
+ config.protected_instance_variables
+ end
def view_context_class
- @view_context_class || self.class.view_context_class
+ @_view_context_class || self.class.view_context_class
end
def initialize(*)
- @view_context_class = nil
+ @_view_context_class = nil
super
end
@@ -79,7 +89,7 @@ module AbstractController
# Returns an object that is able to render templates.
def view_renderer
- @view_renderer ||= ActionView::Renderer.new(lookup_context)
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
# Normalize arguments, options and then delegates render_to_body and
@@ -112,13 +122,19 @@ module AbstractController
private
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
+ @_action_name @_response_body @_formats @_prefixes @_config
+ @_view_context_class @_view_renderer @_lookup_context
+ )
+
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
hash = {}
variables = instance_variable_names
- variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
+ variables -= protected_instance_variables
+ variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
hash
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 0893459e24..6b7aae8c74 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -39,7 +39,7 @@ module AbstractController
# templates, i.e. view paths and details. Check ActionView::LookupContext for more
# information.
def lookup_context
- @lookup_context ||=
+ @_lookup_context ||=
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3fae697cc3..8d813a8e38 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -7,8 +7,10 @@ module ActionController
def start_processing(event)
payload = event.payload
params = payload[:params].except(*INTERNAL_PARAMS)
+ format = payload[:format]
+ format = format.to_s.upcase if format.is_a?(Symbol)
- info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
+ info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index 006b9fd456..05dca445a4 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -18,13 +18,10 @@ module ActionController
delegate :default_charset=, :to => "ActionDispatch::Response"
end
- # TODO: Update protected instance variables list
- config_accessor :protected_instance_variables
- self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render
- @variables_added @request_origin @url
- @parent_controller @action_name
- @before_filter_chain_aborted @_headers @_params
- @_response)
+ self.protected_instance_variables = %w(
+ @_status @_headers @_params @_env @_response @_request
+ @_view_runtime @_stream @_url_options @_action_has_layout
+ )
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 3ec0c4c6a4..e8e465d3ba 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,21 +1,19 @@
module ActionController
module ImplicitRender
def send_action(method, *args)
- if respond_to?(method, true)
- ret = super
- default_render unless response_body
- ret
- else
- default_render
- end
+ ret = super
+ default_render unless response_body
+ ret
end
def default_render(*args)
render(*args)
end
- def action_method?(action_name)
- super || template_exists?(action_name.to_s, _prefixes)
+ def method_for_action(action_name)
+ super || if template_exists?(action_name.to_s, _prefixes)
+ "default_render"
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 4e54c2ad88..16cbbce2fb 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,7 +19,7 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :formats => request.formats.map(&:to_sym),
+ :format => request.format.ref,
:method => request.method,
:path => (request.fullpath rescue "unknown")
}
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 21bbe17dc3..9b27bb8b91 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -125,7 +125,7 @@ module ActionController
# module is inherited.
def inherited(klass)
if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options)
+ klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
end
super
end
@@ -136,15 +136,23 @@ module ActionController
# this could be done by trying to find the defined model that has the
# same singularize name as the controller. For example, +UsersController+
# will try to find if the +User+ model exists.
- def _default_wrap_model
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
model_name = self.name.sub(/Controller$/, '').singularize
begin
model_klass = model_name.constantize
- rescue NameError => e
- unscoped_model_name = model_name.split("::", 2).last
- break if unscoped_model_name == model_name
- model_name = unscoped_model_name
+ rescue NameError, ArgumentError => e
+ if e.message =~ /is not missing constant|uninitialized constant #{model_name}/
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ else
+ raise
+ end
end until model_klass
model_klass
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 1cd93a188c..13044a7450 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -73,7 +73,10 @@ module ActionController #:nodoc:
protected
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
- verified_request? || handle_unverified_request
+ unless verified_request?
+ logger.debug "WARNING: Can't verify CSRF token authenticity" if logger
+ handle_unverified_request
+ end
end
def handle_unverified_request
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 59a3621f72..ebadb29ea7 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -68,7 +68,7 @@ module ActionController #:nodoc:
# respond_with(@project, @task)
# end
#
- # Giving an array of resources, you ensure that the responder will redirect to
+ # Giving several resources ensures that the responder will redirect to
# <code>project_task_url</code> instead of <code>task_url</code>.
#
# Namespaced and singleton resources require a symbol to be given, as in
@@ -77,6 +77,11 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
+ # Note that if you give an array, it will be treated as a collection,
+ # so the following is not equivalent:
+ #
+ # respond_with [@project, :manager, @task]
+ #
# === Custom options
#
# <code>respond_with</code> also allow you to pass options that are forwarded
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index 09dd08898c..91a97c02ff 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -33,7 +33,7 @@ module HTML
result = super
# strip any comments, and if they have a newline at the end (ie. line with
# only a comment) strip that too
- result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result
+ result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m)
# Recurse - handle all dirty nested tags
result == text ? result : sanitize(result, options)
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 4f4cb96a74..aaed0d750f 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -42,20 +42,6 @@ module ActionDispatch
attr_reader :cache_control, :etag
alias :etag? :etag
- def initialize(*)
- super
-
- @cache_control = {}
- @etag = self["ETag"]
-
- if cache_control = self["Cache-Control"]
- cache_control.split(/,\s*/).each do |segment|
- first, last = segment.split("=")
- @cache_control[first.to_sym] = last || true
- end
- end
- end
-
def last_modified
if last = headers['Last-Modified']
Time.httpdate(last)
@@ -77,6 +63,18 @@ module ActionDispatch
private
+ def prepare_cache_control!
+ @cache_control = {}
+ @etag = self["ETag"]
+
+ if cache_control = self["Cache-Control"]
+ cache_control.split(/,\s*/).each do |segment|
+ first, last = segment.split("=")
+ @cache_control[first.to_sym] = last || true
+ end
+ end
+ end
+
def handle_conditional_get!
if etag? || last_modified? || !@cache_control.empty?
set_conditional_cache_control!
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index f07ac44f7a..ccb866f4f7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,16 +17,17 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
- LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
-
- %w[ AUTH_TYPE GATEWAY_INTERFACE
+ LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
+ ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env|
+ HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
+
+ ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase}
@env["#{env}"]
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 1f4f3ac0da..3a6b1da4fd 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -56,26 +56,25 @@ module ActionDispatch # :nodoc:
cattr_accessor(:default_charset) { "utf-8" }
- module Setup
- def initialize(status = 200, header = {}, body = [])
- self.body, self.header, self.status = body, header, status
+ include Rack::Response::Helpers
+ include ActionDispatch::Http::Cache::Response
- @sending_file = false
- @blank = false
+ def initialize(status = 200, header = {}, body = [])
+ self.body, self.header, self.status = body, header, status
- if content_type = self["Content-Type"]
- type, charset = content_type.split(/;\s*charset=/)
- @content_type = Mime::Type.lookup(type)
- @charset = charset || "UTF-8"
- end
+ @sending_file = false
+ @blank = false
- yield self if block_given?
+ if content_type = self["Content-Type"]
+ type, charset = content_type.split(/;\s*charset=/)
+ @content_type = Mime::Type.lookup(type)
+ @charset = charset || "UTF-8"
end
- end
- include Rack::Response::Helpers
- include Setup
- include ActionDispatch::Http::Cache::Response
+ prepare_cache_control!
+
+ yield self if block_given?
+ end
def status=(status)
@status = Rack::Utils.status_code(status)
@@ -116,9 +115,32 @@ module ActionDispatch # :nodoc:
EMPTY = " "
+ class BodyBuster #:nodoc:
+ def initialize(response)
+ @response = response
+ @body = ""
+ end
+
+ def bust(body)
+ body.call(@response, self)
+ body.close if body.respond_to?(:close)
+ @body
+ end
+
+ def write(string)
+ @body << string.to_s
+ end
+ end
+
def body=(body)
@blank = true if body == EMPTY
+ if body.respond_to?(:call)
+ ActiveSupport::Deprecation.warn "Setting a Proc or an object that responds to call " \
+ "in response_body is no longer supported", caller
+ body = BodyBuster.new(self).bust(body)
+ end
+
# Explicitly check for strings. This is *wrong* theoretically
# but if we don't check this, the performance on string bodies
# is bad on Ruby 1.8 (because strings responds to each then).
@@ -150,6 +172,10 @@ module ActionDispatch # :nodoc:
headers['Location'] = url
end
+ def close
+ @body.close if @body.respond_to?(:close)
+ end
+
def to_a
assign_default_content_type_and_charset!
handle_conditional_get!
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 97f7cf0bbe..0c5bafa666 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -24,7 +24,7 @@
<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
-<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div>
+<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
<h2 style="margin-top: 30px">Response</h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 2099fd069a..4b9d3141d5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,7 +1,7 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.message %></pre>
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 97e8ccc9a5..5097f6732d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -224,6 +224,7 @@ module ActionDispatch
self.valid_conditions.push(:controller, :action)
@append = []
+ @prepend = []
@disable_clear_and_finalize = false
clear!
end
@@ -232,7 +233,6 @@ module ActionDispatch
clear! unless @disable_clear_and_finalize
eval_block(block)
finalize! unless @disable_clear_and_finalize
-
nil
end
@@ -240,6 +240,10 @@ module ActionDispatch
@append << block
end
+ def prepend(&block)
+ @prepend << block
+ end
+
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
@@ -262,8 +266,6 @@ module ActionDispatch
end
def clear!
- # Clear the controller cache so we may discover new ones
- @controller_constraints = nil
@finalized = false
routes.clear
named_routes.clear
@@ -271,6 +273,7 @@ module ActionDispatch
:parameters_key => PARAMETERS_KEY,
:request_class => request_class
)
+ @prepend.each { |blk| eval_block(blk) }
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index d430691429..397bda41d5 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -29,7 +29,7 @@ module ActionDispatch
@response.redirect_url
end
- # Shortcut for <tt>ARack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
+ # Shortcut for <tt>Rack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
#
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 170ceb299a..584e5c3791 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -3,7 +3,7 @@ module ActionPack
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 92b6f7c770..a67b61c1ef 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -50,6 +50,7 @@ module ActionView
autoload :Resolver
autoload :PathResolver
autoload :FileSystemResolver
+ autoload :OptimizedFileSystemResolver
autoload :FallbackFileSystemResolver
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index ead7feb091..3b5f4e694f 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -135,8 +135,12 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- result = @view_flow.append(name, content) if content
- result unless content
+ if content
+ @view_flow.append(name, content)
+ nil
+ else
+ @view_flow.get(name)
+ end
end
# The same as +content_for+ but when used with streaming flushes
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 326ad40c8d..853ab061d2 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -94,9 +94,20 @@ module ActionView
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- distance_in_years = distance_in_minutes / 525600
- minute_offset_for_leap_year = (distance_in_years / 4) * 1440
- remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ remainder = (minutes_with_offset % 525600)
+ distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
@@ -207,7 +218,8 @@ module ActionView
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
- # +object+). You can include the seconds with <tt>:include_seconds</tt>.
+ # +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format
+ # with <tt>:ampm</tt> option.
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a
@@ -231,6 +243,9 @@ module ActionView
# time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
# time_select("post", "written_on", :prompt => true) # generic prompts for all
#
+ # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
+ # time_select 'game', 'game_time', {:ampm => true}
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -258,6 +273,9 @@ module ActionView
# # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
+ # # Generate a datetime select with hours in the AM/PM format
+ # datetime_select("post", "written_on", :ampm => true)
+ #
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
# # as the written_on attribute.
# datetime_select("post", "written_on", :discard_type => true)
@@ -307,6 +325,9 @@ module ActionView
# # my_date_time (four days after today)
# select_datetime(my_date_time, :discard_type => true)
#
+ # # Generate a datetime field with hours in the AM/PM format
+ # select_datetime(my_date_time, :ampm => true)
+ #
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
@@ -388,7 +409,10 @@ module ActionView
# # separated by ':' and includes an input for seconds.
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
- # # Generates a time select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
+ # # Generate a time select field with hours in the AM/PM format
+ # select_time(my_time, :ampm => true)
+ #
+ # # 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
# select_time(my_time, :prompt => true) # generic prompts for all
@@ -469,6 +493,9 @@ module ActionView
# # generic prompt.
# select_hour(13, :prompt => 'Choose hour')
#
+ # # Generate a select field for hours in the AM/PM format
+ # select_hour(my_time, :ampm => true)
+ #
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
@@ -602,6 +629,15 @@ module ActionView
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
}.freeze
+ AMPM_TRANSLATION = Hash[
+ [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
+ [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
+ [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
+ [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
+ [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
+ [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
+ ].freeze
+
def initialize(datetime, options = {}, html_options = {})
@options = options.dup
@html_options = html_options.dup
@@ -693,7 +729,7 @@ module ActionView
if @options[:use_hidden] || @options[:discard_hour]
build_hidden(:hour, hour)
else
- build_options_and_select(:hour, hour, :end => 23)
+ build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm])
end
end
@@ -817,7 +853,7 @@ module ActionView
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
- options.reverse_merge!({:leading_zeros => true})
+ options.reverse_merge!({:leading_zeros => true, :ampm => false})
leading_zeros = options.delete(:leading_zeros)
select_options = []
@@ -825,7 +861,8 @@ module ActionView
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
- select_options << content_tag(:option, value, tag_options)
+ text = options[:ampm] ? AMPM_TRANSLATION[i] : value
+ select_options << content_tag(:option, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 06975ffa2f..f0ed3425de 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -167,12 +167,12 @@ module ActionView
@frozen_formats = true
end
- # Overload formats= to reject ["*/*"] values.
+ # Overload formats= to expand ["*/*"] values and automatically
+ # add :html as fallback to :js.
def formats=(values)
- if values && values.size == 1
- value = values.first
- values = nil if value == "*/*"
- values << :html if value == :js
+ if values
+ values.concat(_formats_defaults) if values.delete "*/*"
+ values << :html if values == [:js]
end
super(values)
end
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index 8b840a6463..e0cb5d6a70 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -35,7 +35,7 @@ module ActionView #:nodoc:
each_with_index do |path, i|
path = path.to_s if path.is_a?(Pathname)
next unless path.is_a?(String)
- self[i] = FileSystemResolver.new(path)
+ self[i] = OptimizedFileSystemResolver.new(path)
end
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index e246646963..d4448a7b33 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -1,3 +1,4 @@
+require "active_support/core_ext/array/wrap"
require "active_support/core_ext/enumerable"
module ActionView
@@ -29,6 +30,7 @@ module ActionView
def initialize(paths, path, prefixes, partial, details, *)
@path = path
+ prefixes = Array.wrap(prefixes)
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
"partial"
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 870897958a..2b9427ace5 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -10,17 +10,16 @@ module ActionView
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
- def initialize(name, prefix, partial)
- @name, @prefix, @partial = name, prefix, partial
- rebuild(@name, @prefix, @partial)
+ def self.build(name, prefix, partial)
+ virtual = ""
+ virtual << "#{prefix}/" unless prefix.empty?
+ virtual << (partial ? "_#{name}" : name)
+ new name, prefix, partial, virtual
end
- def rebuild(name, prefix, partial)
- @virtual = ""
- @virtual << "#{prefix}/" unless prefix.empty?
- @virtual << (partial ? "_#{name}" : name)
-
- self.replace(@virtual)
+ def initialize(name, prefix, partial, virtual)
+ @name, @prefix, @partial = name, prefix, partial
+ super(virtual)
end
end
@@ -60,7 +59,7 @@ module ActionView
# Helpers that builds a path. Useful for building virtual paths.
def build_path(name, prefix, partial)
- Path.new(name, prefix, partial)
+ Path.build(name, prefix, partial)
end
# Handles templates caching. If a key is given and caching is on
@@ -112,7 +111,8 @@ module ActionView
end
end
- class PathResolver < Resolver
+ # An abstract class that implements a Resolver with path semantics.
+ class PathResolver < Resolver #:nodoc:
EXTENSIONS = [:locale, :formats, :handlers]
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
@@ -124,13 +124,12 @@ module ActionView
private
def find_templates(name, prefix, partial, details)
- path = build_path(name, prefix, partial)
- extensions = Hash[EXTENSIONS.map { |ext| [ext, details[ext]] }.flatten(0)]
- query(path, extensions, details[:formats])
+ path = Path.build(name, prefix, partial)
+ query(path, details, details[:formats])
end
- def query(path, exts, formats)
- query = build_query(path, exts)
+ def query(path, details, formats)
+ query = build_query(path, details)
templates = []
sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
@@ -138,7 +137,7 @@ module ActionView
next if File.directory?(p) || !sanitizer[p].include?(p)
handler, format = extract_handler_and_format(p, formats)
- contents = File.open(p, "rb") {|io| io.read }
+ contents = File.open(p, "rb") { |io| io.read }
templates << Template.new(contents, File.expand_path(p), handler,
:virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
@@ -147,18 +146,15 @@ module ActionView
templates
end
- # Helper for building query glob string based on resolver's pattern.
- def build_query(path, exts)
+ # Helper for building query glob string based on resolver's pattern.
+ def build_query(path, details)
query = @pattern.dup
query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
- exts.each { |ext, variants|
+ details.each do |ext, variants|
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
- }
-
- query.gsub!('.{html,', '.{html,text.html,')
- query.gsub!('.{text,', '.{text,text.plain,')
+ end
File.expand_path(query, @path)
end
@@ -235,9 +231,25 @@ module ActionView
alias :== :eql?
end
+ # An Optimized resolver for Rails' most common case.
+ class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
+ def build_query(path, details)
+ exts = EXTENSIONS.map { |ext| details[ext] }
+ query = File.join(@path, path)
+
+ exts.each do |ext|
+ query << "{"
+ ext.compact.each { |e| query << ".#{e}," }
+ query << "}"
+ end
+
+ query
+ end
+ end
+
# The same as FileSystemResolver but does not allow templates to store
# a virtual path since it is invalid for such resolvers.
- class FallbackFileSystemResolver < FileSystemResolver
+ class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
def self.instances
[new(""), new("/")]
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index 9c10decd60..c75b7d4de0 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -34,12 +34,20 @@ module Sprockets
app.assets = asset_environment(app)
ActiveSupport.on_load(:action_view) do
- app.assets.context.instance_eval do
+ if app.assets.respond_to?(:context_class)
+ context = app.assets.context_class
+
+ # TODO: Remove this condition when Sprockets 2.0.beta.3 is released
+ else
+ context = app.assets.context
+ end
+
+ context.instance_eval do
include ::ActionView::Helpers::SprocketsHelper
end
end
- app.routes.append do
+ app.routes.prepend do
mount app.assets => assets.prefix
end
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index 99f09286ff..97be5a5bb0 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -36,7 +36,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
def render_with_record_collection
- @developers = Developer.find(:all)
+ @developers = Developer.all
render :partial => @developers
end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 3bb3016fdb..1e2191d417 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -33,10 +33,10 @@ module RenderImplicitAction
assert_status 200
end
- test "action_method? returns true for implicit actions" do
- assert SimpleController.new.action_method?(:hello_world)
- assert SimpleController.new.action_method?(:"hyphen-ated")
- assert SimpleController.new.action_method?(:not_implemented)
+ test "available_action? returns true for implicit actions" do
+ assert SimpleController.new.available_action?(:hello_world)
+ assert SimpleController.new.available_action?(:"hyphen-ated")
+ assert SimpleController.new.available_action?(:not_implemented)
end
end
end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 548cd02dc0..85464fc780 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -170,28 +170,36 @@ end
class NamespacedParamsWrapperTest < ActionController::TestCase
module Admin
- class UsersController < ActionController::Base
- class << self
- attr_accessor :last_parameters
- end
-
- def parse
- self.class.last_parameters = request.params.except(:controller, :action)
- head :ok
+ module Users
+ class UsersController < ActionController::Base;
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
end
end
end
- class Sample
+ class SampleOne
def self.column_names
["username"]
end
end
- tests Admin::UsersController
+ class SampleTwo
+ def self.column_names
+ ["title"]
+ end
+ end
+
+ tests Admin::Users::UsersController
def teardown
- Admin::UsersController.last_parameters = nil
+ Admin::Users::UsersController.last_parameters = nil
end
def test_derived_name_from_controller
@@ -203,7 +211,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
end
def test_namespace_lookup_from_model
- Admin.const_set(:User, Class.new(Sample))
+ Admin.const_set(:User, Class.new(SampleOne))
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -216,20 +224,15 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
end
def test_hierarchy_namespace_lookup_from_model
- # Make sure that we cleanup ::Admin::User
- admin_user_constant = ::Admin::User
- ::Admin.send :remove_const, :User
-
- Object.const_set(:User, Class.new(Sample))
+ Object.const_set(:User, Class.new(SampleTwo))
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
- assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }})
end
ensure
Object.send :remove_const, :User
- ::Admin.const_set(:User, admin_user_constant)
end
end
@@ -241,6 +244,6 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
end
def assert_parameters(expected)
- assert_equal expected, Admin::UsersController.last_parameters
+ assert_equal expected, Admin::Users::UsersController.last_parameters
end
-end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 31f4bf3a76..dea80ed887 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -81,22 +81,25 @@ module RequestForgeryProtectionTests
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
- ActionController::Base.request_forgery_protection_token = :authenticity_token
+ ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
end
+ def teardown
+ ActionController::Base.request_forgery_protection_token = nil
+ end
def test_should_render_form_with_token_tag
assert_not_blocked do
get :index
end
- assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_button_to_with_token_tag
assert_not_blocked do
get :show_button
end
- assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_allow_get
@@ -128,15 +131,15 @@ module RequestForgeryProtectionTests
end
def test_should_allow_post_with_token
- assert_not_blocked { post :index, :authenticity_token => @token }
+ assert_not_blocked { post :index, :custom_authenticity_token => @token }
end
def test_should_allow_put_with_token
- assert_not_blocked { put :index, :authenticity_token => @token }
+ assert_not_blocked { put :index, :custom_authenticity_token => @token }
end
def test_should_allow_delete_with_token
- assert_not_blocked { delete :index, :authenticity_token => @token }
+ assert_not_blocked { delete :index, :custom_authenticity_token => @token }
end
def test_should_allow_post_with_token_in_header
@@ -172,10 +175,18 @@ end
class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
+ setup do
+ ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
+ end
+
+ teardown do
+ ActionController::Base.request_forgery_protection_token = nil
+ end
+
test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
- assert_select 'meta[name=?][content=?]', 'csrf-param', 'authenticity_token'
+ assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token'
assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index d128006404..25b1b4f745 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -358,6 +358,13 @@ class RequestTest < ActiveSupport::TestCase
assert request.head?
end
+ test "post masquerading as put" do
+ request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
+ assert_equal "POST", request.method
+ assert_equal "PUT", request.request_method
+ assert request.put?
+ end
+
test "xml format" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => 'xml' })
diff --git a/actionpack/test/dispatch/response_body_is_proc_test.rb b/actionpack/test/dispatch/response_body_is_proc_test.rb
new file mode 100644
index 0000000000..fd94832624
--- /dev/null
+++ b/actionpack/test/dispatch/response_body_is_proc_test.rb
@@ -0,0 +1,37 @@
+require 'abstract_unit'
+
+class ResponseBodyIsProcTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def test
+ self.response_body = proc { |response, output|
+ output.write 'Hello'
+ }
+ end
+ end
+
+ def test_simple_get
+ with_test_route_set do
+ assert_deprecated do
+ get '/test'
+ end
+ assert_response :success
+ assert_equal 'Hello', response.body
+ end
+ end
+
+ private
+
+ def with_test_route_set(options = {})
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => ::ResponseBodyIsProcTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index cc57a6cba0..42f6c7f79f 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -131,11 +131,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
'action_dispatch.request.parameters' => {
'action' => 'show',
'id' => 'unknown',
- 'controller' => 'featured_tiles'
+ 'controller' => 'featured_tile'
}
})
assert_response 500
- assert_match(/RuntimeError\n in FeaturedTilesController/, body)
+ assert_match(/RuntimeError\n in FeaturedTileController/, body)
end
test "sets the HTTP charset parameter" do
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 592c7da060..a9157e711c 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -38,7 +38,15 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
end
- def test_content_for
+ def test_capture_used_for_read
+ content_for :foo, "foo"
+ assert_equal "foo", content_for(:foo)
+
+ content_for(:bar){ "bar" }
+ assert_equal "bar", content_for(:bar)
+ end
+
+ def test_content_for_question_mark
assert ! content_for?(:title)
content_for :title, 'title'
assert content_for?(:title)
@@ -49,14 +57,14 @@ class CaptureHelperTest < ActionView::TestCase
assert !content_for?(:title)
provide :title, "hi"
assert content_for?(:title)
- assert_equal "hi", @view_flow.get(:title)
+ assert_equal "hi", content_for(:title)
provide :title, "<p>title</p>"
- assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @view_flow.get(:title)
+ assert_equal "hi&lt;p&gt;title&lt;/p&gt;", content_for(:title)
@view_flow = ActionView::OutputFlow.new
provide :title, "hi"
provide :title, "<p>title</p>".html_safe
- assert_equal "hi<p>title</p>", @view_flow.get(:title)
+ assert_equal "hi<p>title</p>", content_for(:title)
end
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 12d2410f49..09c53a36f0 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -121,6 +121,10 @@ class DateHelperTest < ActionView::TestCase
start_date = Date.new 1975, 1, 31
end_date = Date.new 1977, 1, 31
assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date))
+
+ start_date = Date.new 1982, 12, 3
+ end_date = Date.new 2010, 11, 30
+ assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
end
def test_distance_in_words_with_integers
@@ -416,6 +420,14 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18))
end
+ def test_select_hour_with_ampm
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :ampm => true)
+ end
+
def test_select_hour_with_disabled
expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n)
expected << %(<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">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<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)
@@ -937,6 +949,35 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
end
+ def test_select_datetime_with_ampm
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<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="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="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="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<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, :prefix => "date[first]", :ampm => true)
+ end
+
+
def test_select_datetime_with_separators
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1136,6 +1177,24 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false)
end
+ def test_select_time_with_ampm
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
+ expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
+
+ expected << %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<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_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false, :ampm => true)
+ end
+
def test_select_time_with_separator
expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index fcc3782f04..678cb9eeeb 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -20,6 +20,7 @@ class SanitizerTest < ActionController::TestCase
assert_equal "This has a here.", sanitizer.sanitize("This has a <![CDATA[<section>]]> here.")
assert_equal "This has an unclosed ", sanitizer.sanitize("This has an unclosed <![CDATA[<section>]] here...")
[nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
+ assert_nothing_raised { sanitizer.sanitize("This is a frozen string with no tags".freeze) }
end
def test_strip_links
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 5fb1fdc044..47b70f05ab 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -51,6 +51,11 @@ class LookupContextTest < ActiveSupport::TestCase
assert_equal Mime::SET, @lookup_context.formats
end
+ test "handles explicitly defined */* formats fallback to :js" do
+ @lookup_context.formats = [:js, Mime::ALL]
+ assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats
+ end
+
test "adds :html fallback to :js formats" do
@lookup_context.formats = [:js]
assert_equal [:js, :html], @lookup_context.formats
@@ -251,4 +256,13 @@ class TestMissingTemplate < ActiveSupport::TestCase
end
assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
+
+ test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
+ e = assert_raise ActionView::MissingTemplate do
+ details = {:handlers=>[], :formats=>[], :locale=>[]}
+ @lookup_context.view_paths.find("foo", "parent", true, details)
+ end
+ assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ end
+
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index d4e912c410..86d08a43a5 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -325,7 +325,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
# Ensure view path cache is primed
def setup
view_paths = ActionController::Base.view_paths
- assert_equal ActionView::FileSystemResolver, view_paths.first.class
+ assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
setup_view(view_paths)
end
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 03287fac7a..be4de2e53c 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
+
* Add support for proc or lambda as an option for InclusionValidator,
ExclusionValidator, and FormatValidator [Prem Sichanugrist]
@@ -13,8 +15,39 @@
* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
+* Add support for selectively enabling/disabling observers [Myron Marston]
+
+
+*Rails 3.0.7 (April 18, 2011)*
+
+*No changes.
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 [Robert Pankowecki, Santiago Pastorino]
+
+* Fix length validation for fixnums #6556 [Andriy Tyurnikov]
+
+* Fix i18n key collision with namespaced models #6448 [yves.senn]
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* No changes.
+
-*Rails 3.0.2 (unreleased)*
+*Rails 3.0.2 (November 15, 2010)*
* No changes
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 01eef762fd..483b577681 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -35,17 +35,17 @@ module ActiveModel
# protected
#
# def account_params
- # scope = admin ? :admin : :default
- # sanitize_for_mass_assignment(params[:account], scope)
+ # role = admin ? :admin : :default
+ # sanitize_for_mass_assignment(params[:account], role)
# end
#
# end
#
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
- # whenever attributes are sanitized before assignment. A scope for the
- # attributes is optional, if no scope is provided then :default is used.
- # A scope can be defined by using the :as option.
+ # whenever attributes are sanitized before assignment. A role for the
+ # attributes is optional, if no role is provided then :default is used.
+ # A role can be defined by using the :as option.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -67,7 +67,7 @@ module ActiveModel
# end
# end
#
- # When using a :default scope :
+ # When using the :default role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -78,7 +78,7 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin scope :
+ # And using the :admin role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
@@ -93,10 +93,10 @@ module ActiveModel
# to sanitize attributes won't provide sufficient protection.
def attr_protected(*args)
options = args.extract_options!
- scope = options[:as] || :default
+ role = options[:as] || :default
self._protected_attributes = protected_attributes_configs.dup
- self._protected_attributes[scope] = self.protected_attributes(scope) + args
+ self._protected_attributes[role] = self.protected_attributes(role) + args
self._active_authorizer = self._protected_attributes
end
@@ -104,8 +104,8 @@ module ActiveModel
# Specifies a white list of model attributes that can be set via
# mass-assignment.
#
- # Like +attr_protected+, a scope for the attributes is optional,
- # if no scope is provided then :default is used. A scope can be defined by
+ # Like +attr_protected+, a role for the attributes is optional,
+ # if no role is provided then :default is used. A role can be defined by
# using the :as option.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
@@ -131,7 +131,7 @@ module ActiveModel
# end
# end
#
- # When using a :default scope :
+ # When using the :default role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -141,7 +141,7 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin scope :
+ # And using the :admin role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
@@ -152,20 +152,20 @@ module ActiveModel
# to sanitize attributes won't provide sufficient protection.
def attr_accessible(*args)
options = args.extract_options!
- scope = options[:as] || :default
+ role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
- self._accessible_attributes[scope] = self.accessible_attributes(scope) + args
+ self._accessible_attributes[role] = self.accessible_attributes(role) + args
self._active_authorizer = self._accessible_attributes
end
- def protected_attributes(scope = :default)
- protected_attributes_configs[scope]
+ def protected_attributes(role = :default)
+ protected_attributes_configs[role]
end
- def accessible_attributes(scope = :default)
- accessible_attributes_configs[scope]
+ def accessible_attributes(role = :default)
+ accessible_attributes_configs[role]
end
def active_authorizers
@@ -198,12 +198,12 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, scope = :default)
- mass_assignment_authorizer(scope).sanitize(attributes)
+ def sanitize_for_mass_assignment(attributes, role = :default)
+ mass_assignment_authorizer(role).sanitize(attributes)
end
- def mass_assignment_authorizer(scope = :default)
- self.class.active_authorizer[scope]
+ def mass_assignment_authorizer(role = :default)
+ self.class.active_authorizer[role]
end
end
end
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index ab7f86007f..5fb73f1c78 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -2,7 +2,7 @@ require 'set'
module ActiveModel
# Stores the enabled/disabled state of individual observers for
- # a particular model classes.
+ # a particular model class.
class ObserverArray < Array
attr_reader :model_class
def initialize(model_class, *args)
@@ -10,14 +10,57 @@ module ActiveModel
super(*args)
end
+ # Returns true if the given observer is disabled for the model class.
def disabled_for?(observer)
disabled_observers.include?(observer.class)
end
+ # Disables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.disable :user_observer
+ # # => disables the UserObserver
+ #
+ # User.observers.disable AuditTrail
+ # # => disables the AuditTrail observer for User notifications.
+ # # Other models will still notify the AuditTrail observer.
+ #
+ # ORM.observers.disable :observer_1, :observer_2
+ # # => disables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.disable :all
+ # # => disables all observers for all models.
+ #
+ # User.observers.disable :all do
+ # # all user observers are disabled for
+ # # just the duration of the block
+ # end
def disable(*observers, &block)
set_enablement(false, observers, &block)
end
+ # Enables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.enable :user_observer
+ # # => enables the UserObserver
+ #
+ # User.observers.enable AuditTrail
+ # # => enables the AuditTrail observer for User notifications.
+ # # Other models will not be affected (i.e. they will not
+ # # trigger notifications to AuditTrail if previously disabled)
+ #
+ # ORM.observers.enable :observer_1, :observer_2
+ # # => enables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.enable :all
+ # # => enables all observers for all models.
+ #
+ # User.observers.enable :all do
+ # # all user observers are enabled for
+ # # just the duration of the block
+ # end
+ #
+ # Note: all observers are enabled by default. This method is only
+ # useful when you have previously disabled one or more observers.
def enable(*observers, &block)
set_enablement(true, observers, &block)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index cfe71074a5..4682ae07ef 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -40,7 +40,11 @@ module ActiveModel
observers.replace(values.flatten)
end
- # Gets the current observers.
+ # Gets an array of observers observing this model.
+ # The array also provides +enable+ and +disable+ methods
+ # that allow you to selectively enable and disable observers.
+ # (see <tt>ActiveModel::ObserverArray.enable</tt> and
+ # <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
def observers
@observers ||= ObserverArray.new(self)
end
@@ -222,7 +226,8 @@ module ActiveModel
self.class.observed_classes
end
- # Send observed_method(object) if the method exists.
+ # Send observed_method(object) if the method exists and
+ # the observer is enabled for the given object's class.
def update(observed_method, object) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index d4295e6afe..19639b1363 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -33,6 +33,7 @@ module ActiveModel
protected
def compute_type
+ return if value.nil?
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
type ||= :string if value.respond_to?(:to_str)
type ||= :yaml
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 23ba42bf35..68c138da84 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index b22ce874ea..43a12eed61 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -10,7 +10,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
- def test_only_moderator_scope_attribute_accessible
+ def test_only_moderator_role_attribute_accessible
user = SpecialUser.new
expected = { "name" => "John Smith", "email" => "john@smith.com" }
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator)
@@ -27,7 +27,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
- def test_admin_scoped_attributes_accessible
+ def test_attributes_accessible_with_admin_role
user = Person.new
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index b6a2f88667..8f5c196850 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -92,6 +92,10 @@ class XmlSerializationTest < ActiveModel::TestCase
test "should serialize string" do
assert_match %r{<name>aaron stack</name>}, @contact.to_xml
end
+
+ test "should serialize nil" do
+ assert_match %r{<pseudonyms nil=\"true\"></pseudonyms>}, @contact.to_xml(:methods => :pseudonyms)
+ end
test "should serialize integer" do
assert_match %r{<age type="integer">25</age>}, @contact.to_xml
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 3cb95b4a00..e06b04af19 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -11,7 +11,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -20,7 +20,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -28,7 +28,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true_but_its_not )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true_but_its_not )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -36,7 +36,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true_but_its_not )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true_but_its_not )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -45,7 +45,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "a = 1; a == 1" )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "a = 1; a == 1" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -54,7 +54,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "a = 1; a == 1" )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "a = 1; a == 1" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -62,7 +62,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "false")
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "false")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -70,7 +70,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "false")
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "false")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -79,7 +79,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
:if => Proc.new { |r| r.content.size > 4 } )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
@@ -89,7 +89,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
:unless => Proc.new { |r| r.content.size > 4 } )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
@@ -98,7 +98,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
:if => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
@@ -107,7 +107,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
:unless => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 73647efea5..2ce714fef0 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_allow_blank
- Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
+ Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank => true)
assert Topic.new("title" => "Shouldn't be valid").invalid?
assert Topic.new("title" => "").valid?
assert Topic.new("title" => nil).valid?
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 92c473de0e..413da92de4 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -42,7 +42,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_allow_nil
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil => true )
assert Topic.new("title" => "a!", "content" => "abc").invalid?
assert Topic.new("title" => "", "content" => "abc").invalid?
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index f02514639b..44048a9c1d 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -11,7 +11,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_allow_nil
- Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
+ Topic.validates_length_of( :title, :is => 5, :allow_nil => true )
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").invalid?
@@ -20,7 +20,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_allow_blank
- Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
+ Topic.validates_length_of( :title, :is => 5, :allow_blank => true )
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").valid?
@@ -176,16 +176,16 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_nasty_params
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>-6) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") }
end
def test_validates_length_of_custom_errors_for_minimum_with_message
- Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %{count}" )
+ Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" )
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -193,7 +193,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
- Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %{count}" )
+ Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" )
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -201,7 +201,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_message
- Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %{count}" )
+ Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -222,7 +222,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}" )
+ Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -244,7 +244,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_message
- Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" )
+ Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -252,7 +252,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
- Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %{count}" )
+ Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -331,7 +331,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_block
- Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %{count} words.",
+ Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.",
:tokenizer => lambda {|str| str.scan(/\w+/) }
t = Topic.new(:content => "this content should be long enough")
assert t.valid?
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index f4f3078473..7bfc542afb 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -16,6 +16,10 @@ class Contact
options.each { |name, value| send("#{name}=", value) }
end
+ def pseudonyms
+ nil
+ end
+
def persisted?
id
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9ff29f1155..32bcf02139 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,24 @@
*Rails 3.1.0 (unreleased)*
+* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0
+
+* AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes! all accept a second hash as option that allows you
+ to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
+ new mass assignment capabilities:
+
+ class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+ end
+
+ Post.new(params[:post], :as => :admin)
+
+ assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
+
+ Please note that this changes the method signatures for AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes!. If you have overwritten these methods you should update them accordingly.
+
+ [Josh Kalderimis]
+
* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
evaluation:
@@ -22,25 +41,7 @@
[Jon Leighton]
-* Calling 'default_scope' multiple times in a class (including when a superclass calls
- 'default_scope') is deprecated. The current behavior is that this will merge the default
- scopes together:
-
- class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
- end
-
- In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
- class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
- end
-
- If you wish to merge default scopes in special ways, it is recommended to define your default
+* If you wish to merge default scopes in special ways, it is recommended to define your default
scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
etc.):
@@ -293,6 +294,84 @@ IrreversibleMigration exception will be raised when going down.
[Aaron Patterson]
+*Rails 3.0.7 (April 18, 2011)*
+
+* Destroying records via nested attributes works independent of reject_if LH #6006 [Durran Jordan]
+
+* Delegate any? and many? to Model.scoped for consistency [Andrew White]
+
+* Quote the ORDER BY clause in batched finds - fixes #6620 [Andrew White]
+
+* Change exists? so records are not instantiated - fixes #6127. This prevents after_find
+ and after_initialize callbacks being triggered when checking for record existence.
+ [Andrew White]
+
+* Fix performance bug with attribute accessors which only occurred on Ruby 1.8.7, and ensure we
+ cache type-casted values when the column returned from the db contains non-standard chars.
+ [Jon Leighton]
+
+* Fix a performance regression introduced here 86acbf1cc050c8fa8c74a10c735e467fb6fd7df8
+ related to read_attribute method [Stian Grytøyr]
+
+
+*Rails 3.0.6 (April 5, 2011)*
+
+* Un-deprecate reorder method [Sebastian Martinez]
+
+* Extensions are applied when calling +except+ or +only+ on relations.
+ Thanks to Iain Hecker.
+
+* Schemas set in set_table_name are respected by the mysql adapter. LH #5322
+
+* Fixed a bug when empty? was called on a grouped Relation that wasn't loaded.
+ LH #5829
+
+* Reapply extensions when using except and only. Thanks Iain Hecker.
+
+* Binary data is escaped when being inserted to SQLite3 Databases. Thanks
+ Naruse!
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* Model.where(:column => 1).where(:column => 2) will always produce an AND
+query.
+
+ [Aaron Patterson]
+
+* Deprecated support for interpolated association conditions in the form of :conditions => 'foo = #{bar}'.
+
+ Instead, you should use a proc, like so:
+
+ Before:
+
+ has_many :things, :conditions => 'foo = #{bar}'
+
+ After:
+
+ has_many :things, :conditions => proc { "foo = #{bar}" }
+
+ Inside the proc, 'self' is the object which is the owner of the association, unless you are
+ eager loading the association, in which case 'self' is the class which the association is within.
+
+ You can have any "normal" conditions inside the proc, so the following will work too:
+
+ has_many :things, :conditions => proc { ["foo = ?", bar] }
+
+ Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
+ 'record' to get the record being inserted or deleted. This is now passed as an argument to
+ the proc.
+
+ [Jon Leighton]
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* Added deprecation warning for has_and_belongs_to_many associations where the join table has
+ additional attributes other than the keys. Access to these attributes is removed in 3.1.
+ Please use has_many :through instead. [Jon Leighton]
+
+
*Rails 3.0.3 (November 16, 2010)*
* Support find by class like this: Post.where(:name => Post)
@@ -329,10 +408,12 @@ IrreversibleMigration exception will be raised when going down.
[Aaron Patterson]
+
*Rails 3.0.1 (October 15, 2010)*
* Introduce a fix for CVE-2010-3993
+
*Rails 3.0.0 (August 29, 2010)*
* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
@@ -532,12 +613,12 @@ IrreversibleMigration exception will be raised when going down.
* Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran]
- class Book < ActiveRecord::Base
- has_one :author
- has_many :pages
+ class Book < ActiveRecord::Base
+ has_one :author
+ has_many :pages
- accepts_nested_attributes_for :author, :pages
- end
+ accepts_nested_attributes_for :author, :pages
+ end
* Make after_save callbacks fire only if the record was successfully saved. #1735 [Michael Lovitt]
@@ -957,7 +1038,7 @@ so newlines etc are escaped #10385 [Norbert Crombach]
"foo.bar" => "`foo`.`bar`"
* Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett]
- http://errtheblog.com/post/2381
+ http://errtheblog.com/post/2381
* Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 [Jack Danger Canty]
@@ -1073,7 +1154,7 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea
* Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd]
- For more information see: http://dev.rubyonrails.org/ticket/8049
+ For more information see: http://dev.rubyonrails.org/ticket/8049
* Don't clobber includes passed to has_many.count [Jack Danger Canty]
@@ -1583,8 +1664,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
@@ -2040,8 +2121,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index a27640eac9..3a89446a83 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -219,4 +219,4 @@ API documentation is at
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
+* https://github.com/rails/rails/issues
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 9d4cbbc150..3a5035305b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -23,5 +23,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 2.1.0')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('tzinfo', '~> 0.3.27')
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 06a414b874..62d48d3a2c 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -13,6 +13,19 @@ module ActiveRecord::Associations::Builder
private
+ def define_readers
+ super
+ name = self.name
+
+ model.redefine_method("#{name}_loaded?") do
+ ActiveSupport::Deprecation.warn(
+ "Calling obj.#{name}_loaded? is deprecated. Please use " \
+ "obj.association(:#{name}).loaded? instead."
+ )
+ association(name).loaded?
+ end
+ end
+
def define_constructors
name = self.name
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 6cdec8c487..4429c655a2 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module Associations
# = Active Record Association Collection
#
- # AssociationCollection is an abstract class that provides common stuff to
+ # CollectionAssociation is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in AssociationProxy.
#
@@ -94,7 +94,13 @@ module ActiveRecord
end
def build(attributes = {}, options = {}, &block)
- build_or_create(:build, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| build(attr, options, &block) }
+ else
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ end
+ end
end
def create(attributes = {}, options = {}, &block)
@@ -102,7 +108,16 @@ module ActiveRecord
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(:create, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, options, &block) }
+ else
+ transaction do
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ insert_record(record)
+ end
+ end
+ end
end
def create!(attrs = {}, options = {}, &block)
@@ -321,15 +336,7 @@ module ActiveRecord
def load_target
if find_target?
- targets = []
-
- begin
- targets = find_target
- rescue ActiveRecord::RecordNotFound
- reset
- end
-
- @target = merge_target_lists(targets, target)
+ @target = merge_target_lists(find_target, target)
end
loaded!
@@ -337,20 +344,18 @@ module ActiveRecord
end
def add_to_target(record)
- transaction do
- callback(:before_add, record)
- yield(record) if block_given?
+ callback(:before_add, record)
+ yield(record) if block_given?
- if options[:uniq] && index = @target.index(record)
- @target[index] = record
- else
- @target << record
- end
-
- callback(:after_add, record)
- set_inverse_instance(record)
+ if options[:uniq] && index = @target.index(record)
+ @target[index] = record
+ else
+ @target << record
end
+ callback(:after_add, record)
+ set_inverse_instance(record)
+
record
end
@@ -374,7 +379,7 @@ module ActiveRecord
if options[:finder_sql]
reflection.klass.find_by_sql(custom_finder_sql)
else
- find(:all)
+ scoped.all
end
records = options[:uniq] ? uniq(records) : records
@@ -403,26 +408,16 @@ module ActiveRecord
end + existing
end
- def build_or_create(method, attributes, options)
- records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs, options)
-
- add_to_target(record) do
- yield(record) if block_given?
- insert_record(record) if method == :create
- end
- end
-
- attributes.is_a?(Array) ? records : records.first
- end
-
# Do the relevant stuff to insert the given record into the association collection.
def insert_record(record, validate = true)
raise NotImplementedError
end
def build_record(attributes, options)
- reflection.build_association(scoped.scope_for_create.merge(attributes), options)
+ record = reflection.build_association
+ record.assign_attributes(scoped.scope_for_create, :without_protection => true)
+ record.assign_attributes(attributes, options)
+ record
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 388173c1fb..adfc71d435 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -64,9 +64,12 @@ module ActiveRecord
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
- if match && match.creator?
- attributes = match.attribute_names
- return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
+ if match && match.instantiator?
+ record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
+ @association.send :set_owner_attributes, r
+ @association.send :add_to_target, r
+ yield(r) if block_given?
+ end
end
if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 7134dc85c8..2f3a6e71f1 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -10,7 +10,7 @@ module ActiveRecord
reflection.klass.transaction do
if target && target != record
- remove_target!(options[:dependent])
+ remove_target!(options[:dependent]) unless target.destroyed?
end
if record
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0a666598ed..c32753782f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -91,12 +91,12 @@ module ActiveRecord
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- relation.from(join(table, constraint))
-
unless conditions[i].empty?
- relation.where(sanitize(conditions[i], table))
+ constraint = constraint.and(sanitize(conditions[i], table))
end
+ relation.from(join(table, constraint))
+
# The current table in this iteration becomes the foreign table in the next
foreign_table = table
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index ea4d73d414..877ddf3ee1 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -18,7 +18,7 @@ module ActiveRecord
end
def create(attributes = {}, options = {})
- new_record(:create, attributes, options)
+ build(attributes, options).tap { |record| record.save }
end
def create!(attributes = {}, options = {})
@@ -26,7 +26,14 @@ module ActiveRecord
end
def build(attributes = {}, options = {})
- new_record(:build, attributes, options)
+ record = reflection.build_association
+ record.assign_attributes(
+ scoped.scope_for_create.except(klass.primary_key),
+ :without_protection => true
+ )
+ record.assign_attributes(attributes, options)
+ set_new_record(record)
+ record
end
private
@@ -43,13 +50,6 @@ module ActiveRecord
def set_new_record(record)
replace(record)
end
-
- def new_record(method, attributes, options)
- attributes = scoped.scope_for_create.merge(attributes || {})
- record = reflection.send("#{method}_association", attributes, options)
- set_new_record(record)
- record
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index e6ab628719..53c5c3cedf 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,10 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(reflection.klass.scoped)
+ scope = scope.merge(
+ reflection.klass.scoped.with_default_scope.
+ except(:select, :create_with)
+ )
end
scope
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 58a056bce9..e1bf2ccc8a 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -482,7 +482,7 @@ module ActiveRecord #:nodoc:
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
- # # Create a single new object using the :admin mass-assignment security scope
+ # # Create a single new object using the :admin mass-assignment security role
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Create a single new object bypassing mass-assignment security
@@ -830,6 +830,10 @@ module ActiveRecord #:nodoc:
@symbolized_base_class ||= base_class.to_s.to_sym
end
+ def symbolized_sti_name
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
+ end
+
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -891,7 +895,7 @@ module ActiveRecord #:nodoc:
# not use the default_scope:
#
# Post.unscoped {
- # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
@@ -1482,7 +1486,7 @@ MSG
# # Instantiates a single new object
# User.new(:first_name => 'Jamie')
#
- # # Instantiates a single new object using the :admin mass-assignment security scope
+ # # Instantiates a single new object using the :admin mass-assignment security role
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Instantiates a single new object bypassing mass-assignment security
@@ -1648,17 +1652,16 @@ MSG
return unless new_attributes.is_a?(Hash)
- guard_protected_attributes ||= true
- if guard_protected_attributes
- assign_attributes(new_attributes)
- else
+ if guard_protected_attributes == false
assign_attributes(new_attributes, :without_protection => true)
+ else
+ assign_attributes(new_attributes)
end
end
# Allows you to set all the attributes for a particular mass-assignment
- # security scope by passing in a hash of attributes with keys matching
- # the attribute names (which again matches the column names) and the scope
+ # security role by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the role
# name using the :as option.
#
# To bypass mass-assignment security you can use the :without_protection => true
@@ -1684,13 +1687,15 @@ MSG
# user.name # => "Josh"
# user.is_admin? # => true
def assign_attributes(new_attributes, options = {})
+ return unless new_attributes
+
attributes = new_attributes.stringify_keys
- scope = options[:as] || :default
+ role = options[:as] || :default
multi_parameter_attributes = []
unless options[:without_protection]
- attributes = sanitize_for_mass_assignment(attributes, scope)
+ attributes = sanitize_for_mass_assignment(attributes, role)
end
attributes.each do |k, v|
@@ -1943,32 +1948,9 @@ MSG
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- # in order to allow a date to be set without a year, we must keep the empty values.
- # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
- values = values_with_empty_parameters.reject { |v| v.nil? }
-
- if values.empty?
- send(name + "=", nil)
- else
-
- value = if Time == klass
- instantiate_time_object(name, values)
- elsif Date == klass
- begin
- values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
- Date.new(*values)
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- else
- klass.new(*values)
- end
-
- send(name + "=", value)
- end
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -1976,19 +1958,65 @@ MSG
end
end
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Date bits were provided but blank, then default to 1
+ # If Time bits are not there, then default to 0
+ [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
for pair in pairs
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ attributes
end
def type_cast_attribute_value(multiparameter_name, value)
@@ -1996,7 +2024,7 @@ MSG
end
def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 3045e30407..b3eb23bbb3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -6,15 +6,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(sql, name = nil, binds = [])
- if supports_statement_cache?
- select(sql, name, binds)
- else
- return select(sql, name) if binds.empty?
- binds = binds.dup
- select sql.gsub('?') {
- quote(*binds.shift.reverse)
- }, name
- end
+ select(sql, name, binds)
end
# Returns a record hash with the column names as keys and column values
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8af22fe9f5..ac2da73a84 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -184,6 +184,10 @@ module ActiveRecord
QUOTED_FALSE
end
+ def substitute_at(column, index)
+ Arel.sql "\0"
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity(&block) #:nodoc:
@@ -292,14 +296,14 @@ module ActiveRecord
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
end
def exec_delete(sql, name, binds)
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
@connection.affected_rows
end
alias :exec_update :exec_delete
@@ -646,7 +650,8 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ binds = binds.dup
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
end
def exec_query(sql, name = 'SQL', binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 37db2be7a9..f4beeceb61 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -39,6 +39,16 @@ module ActiveRecord
# :stopdoc:
class << self
attr_accessor :money_precision
+ def string_to_time(string)
+ return string unless String === string
+
+ case string
+ when 'infinity' then 1.0 / 0.0
+ when '-infinity' then -1.0 / 0.0
+ else
+ super
+ end
+ end
end
# :startdoc:
@@ -349,6 +359,9 @@ module ActiveRecord
return super unless column
case value
+ when Float
+ return super unless value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 96fea741e0..4aa6389a04 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -13,6 +13,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/logger'
require 'active_support/ordered_hash'
+require 'active_support/core_ext/module/deprecation'
if defined? ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
@@ -28,11 +29,9 @@ class FixturesFileNotFound < StandardError; end
#
# = Fixture formats
#
-# Fixtures come in 3 flavors:
+# Fixtures come in 1 flavor:
#
# 1. YAML fixtures
-# 2. CSV fixtures
-# 3. Single-file fixtures
#
# == YAML fixtures
#
@@ -74,56 +73,6 @@ class FixturesFileNotFound < StandardError; end
# parent_id: 1
# title: Child
#
-# == CSV fixtures
-#
-# Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored
-# in a single file, but instead end with the <tt>.csv</tt> file extension
-# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
-#
-# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
-# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the
-# file is then comprised
-# of the actual data (1 per line). Here's an example:
-#
-# id, name, url
-# 1, Ruby On Rails, http://www.rubyonrails.org
-# 2, Google, http://www.google.com
-#
-# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
-# need to use a double quote character, you must escape it with another double quote.
-#
-# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
-# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
-# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
-# "web_site_2".
-#
-# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
-# have existing data somewhere already.
-#
-# == Single-file fixtures
-#
-# This type of fixture was the original format for Active Record that has since been deprecated in
-# favor of the YAML and CSV formats.
-# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model)
-# to the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
-# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
-#
-# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
-# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension.
-# Here's what the above example might look like:
-#
-# web_sites/google
-# web_sites/yahoo.txt
-# web_sites/ruby-on-rails
-#
-# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
-# of "name => value". Here's an example of the ruby-on-rails fixture above:
-#
-# id => 1
-# name => Ruby on Rails
-# url => http://www.rubyonrails.org
-#
# = Using fixtures in testcases
#
# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
@@ -176,7 +125,7 @@ class FixturesFileNotFound < StandardError; end
# = Dynamic fixtures with ERB
#
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
-# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+# mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like:
#
# <% for i in 1..1000 %>
# fix_<%= i %>:
@@ -423,8 +372,8 @@ class FixturesFileNotFound < StandardError; end
# to the rescue:
#
# george_reginald:
-# monkey_id: <%= Fixtures.identify(:reginald) %>
-# pirate_id: <%= Fixtures.identify(:george) %>
+# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
+# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
#
# == Support for YAML defaults
#
@@ -444,369 +393,375 @@ class FixturesFileNotFound < StandardError; end
#
# Any fixture labeled "DEFAULTS" is safely ignored.
-class Fixtures
- MAX_ID = 2 ** 30 - 1
+Fixture = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixture', 'ActiveRecord::Fixture')
+Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixtures', 'ActiveRecord::Fixtures')
- @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+module ActiveRecord
+ class Fixtures
+ MAX_ID = 2 ** 30 - 1
- def self.find_table_name(table_name) # :nodoc:
- ActiveRecord::Base.pluralize_table_names ?
- table_name.to_s.singularize.camelize :
- table_name.to_s.camelize
- end
+ @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.reset_cache
- @@all_cached_fixtures.clear
- end
+ def self.find_table_name(table_name) # :nodoc:
+ ActiveRecord::Base.pluralize_table_names ?
+ table_name.to_s.singularize.camelize :
+ table_name.to_s.camelize
+ end
- def self.cache_for_connection(connection)
- @@all_cached_fixtures[connection]
- end
+ def self.reset_cache
+ @@all_cached_fixtures.clear
+ end
- def self.fixture_is_cached?(connection, table_name)
- cache_for_connection(connection)[table_name]
- end
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
- def self.cached_fixtures(connection, keys_to_fetch = nil)
- if keys_to_fetch
- cache_for_connection(connection).values_at(*keys_to_fetch)
- else
- cache_for_connection(connection).values
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
end
- end
- def self.cache_fixtures(connection, fixtures_map)
- cache_for_connection(connection).update(fixtures_map)
- end
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ cache_for_connection(connection).values
+ end
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
- if load_instances
- fixtures.each do |name, fixture|
- begin
- object.instance_variable_set "@#{name}", fixture.find
- rescue FixtureClassNotFound
- nil
+ def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ if load_instances
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
end
end
end
- end
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
end
- end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ table_names.each { |n|
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
+ }
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
- }
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
- unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
+ unless files_to_read.empty?
+ connection.disable_referential_integrity do
+ fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
+ fixture_files = files_to_read.map do |path|
+ table_name = path.tr '/', '_'
- fixtures_map[path] = Fixtures.new(
- connection,
- table_name,
- class_names[table_name.to_sym] || table_name.classify,
- File.join(fixtures_directory, path))
- end
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
+ connection,
+ table_name,
+ class_names[table_name.to_sym] || table_name.classify,
+ File.join(fixtures_directory, path))
+ end
- all_loaded_fixtures.update(fixtures_map)
+ all_loaded_fixtures.update(fixtures_map)
- connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ connection.transaction(:requires_new => true) do
+ fixture_files.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
- table_rows.keys.each do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
- end
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
- table_rows.each do |table_name,rows|
- rows.each do |row|
- conn.insert_fixture(row, table_name)
+ table_rows.each do |table_name,rows|
+ rows.each do |row|
+ conn.insert_fixture(row, table_name)
+ end
end
end
- end
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ end
end
end
- end
- cache_fixtures(connection, fixtures_map)
+ cache_fixtures(connection, fixtures_map)
+ end
end
+ cached_fixtures(connection, table_names)
end
- cached_fixtures(connection, table_names)
- end
-
- # Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
- end
- attr_reader :table_name, :name, :fixtures, :model_class
-
- def initialize(connection, table_name, class_name, fixture_path)
- @connection = connection
- @table_name = table_name
- @fixture_path = fixture_path
- @name = table_name # preserve fixture base name
- @class_name = class_name
-
- @fixtures = ActiveSupport::OrderedHash.new
- @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
-
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
- @table_name = class_name.table_name
- @connection = class_name.connection
- @model_class = class_name
- else
- @model_class = class_name.constantize rescue nil
+ # Returns a consistent, platform-independent identifier for +label+.
+ # Identifiers are positive integers less than 2^32.
+ def self.identify(label)
+ Zlib.crc32(label.to_s) % MAX_ID
end
- read_fixture_files
- end
+ attr_reader :table_name, :name, :fixtures, :model_class
- def [](x)
- fixtures[x]
- end
+ def initialize(connection, table_name, class_name, fixture_path)
+ @connection = connection
+ @table_name = table_name
+ @fixture_path = fixture_path
+ @name = table_name # preserve fixture base name
+ @class_name = class_name
- def []=(k,v)
- fixtures[k] = v
- end
+ @fixtures = ActiveSupport::OrderedHash.new
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
- def each(&block)
- fixtures.each(&block)
- end
+ # Should be an AR::Base type class
+ if class_name.is_a?(Class)
+ @table_name = class_name.table_name
+ @connection = class_name.connection
+ @model_class = class_name
+ else
+ @model_class = class_name.constantize rescue nil
+ end
- def size
- fixtures.size
- end
+ read_fixture_files
+ end
- # Return a hash of rows to be inserted. The key is the table, the value is
- # a list of rows to insert to that table.
- def table_rows
- now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
- now = now.to_s(:db)
+ def [](x)
+ fixtures[x]
+ end
- # allow a standard key to be used for doing defaults in YAML
- fixtures.delete('DEFAULTS')
+ def []=(k,v)
+ fixtures[k] = v
+ end
- # track any join tables we need to insert later
- rows = Hash.new { |h,table| h[table] = [] }
+ def each(&block)
+ fixtures.each(&block)
+ end
- rows[table_name] = fixtures.map do |label, fixture|
- row = fixture.to_hash
+ def size
+ fixtures.size
+ end
- if model_class && model_class < ActiveRecord::Base
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
- if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
- end
- end
+ # Return a hash of rows to be inserted. The key is the table, the value is
+ # a list of rows to insert to that table.
+ def table_rows
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
- # interpolate the fixture label
- row.each do |key, value|
- row[key] = label if value == "$LABEL"
- end
+ # allow a standard key to be used for doing defaults in YAML
+ fixtures.delete('DEFAULTS')
- # generate a primary key if necessary
- if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = Fixtures.identify(label)
- end
+ # track any join tables we need to insert later
+ rows = Hash.new { |h,table| h[table] = [] }
+
+ rows[table_name] = fixtures.map do |label, fixture|
+ row = fixture.to_hash
- # If STI is used, find the correct subclass for association reflection
- reflection_class =
- if row.include?(inheritance_column_name)
- row[inheritance_column_name].constantize rescue model_class
- else
- model_class
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
end
- reflection_class.reflect_on_all_associations.each do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
+ end
- row[fk_name] = Fixtures.identify(value)
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
end
- when :has_and_belongs_to_many
- if (targets = row.delete(association.name.to_s))
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
- rows[table_name].concat targets.map { |target|
- { association.foreign_key => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }
- }
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
+ end
+
+ row[fk_name] = ActiveRecord::Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ table_name = association.options[:join_table]
+ rows[table_name].concat targets.map { |target|
+ { association.foreign_key => row[primary_key_name],
+ association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
+ }
+ end
end
end
end
- end
-
- row
- end
- rows
- end
- private
- def primary_key_name
- @primary_key_name ||= model_class && model_class.primary_key
+ row
+ end
+ rows
end
- def has_primary_key_column?
- @has_primary_key_column ||= primary_key_name &&
- model_class.columns.any? { |c| c.name == primary_key_name }
- end
+ private
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
- def timestamp_column_names
- @timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
- end
+ def has_primary_key_column?
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
+ end
- def inheritance_column_name
- @inheritance_column_name ||= model_class && model_class.inheritance_column
- end
+ def timestamp_column_names
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
+ end
- def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
- end
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
- def read_fixture_files
- if File.file?(yaml_file_path)
- read_yaml_fixture_files
- elsif File.file?(csv_file_path)
- read_csv_fixture_files
- else
- raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
end
- end
- def read_yaml_fixture_files
- yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
- File.file?(f)
- } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
-
- if yaml = parse_yaml_string(yaml_string)
- # If the file is an ordered map, extract its children.
- yaml_value =
- if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
- yaml.value
- else
- [yaml]
- end
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ else
+ raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ end
+ end
- yaml_value.each do |fixture|
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
- unless data
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ def read_yaml_fixture_files
+ yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ File.file?(f)
+ } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
end
- fixtures[name] = Fixture.new(data, model_class)
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ fixtures[name] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
end
end
- end
- def read_csv_fixture_files
- reader = CSV.parse(erb_render(IO.read(csv_file_path)))
- header = reader.shift
- i = 0
- reader.each do |row|
- data = {}
- row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
- end
+ deprecate :read_csv_fixture_files
- def yaml_file_path
- "#{@fixture_path}.yml"
- end
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
- def csv_file_path
- @fixture_path + ".csv"
- end
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
- def yaml_fixtures_key(path)
- File.basename(@fixture_path).split(".").first
- end
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
- def parse_yaml_string(fixture_content)
- YAML::load(erb_render(fixture_content))
- rescue => error
- raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
- end
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
+ end
- def erb_render(fixture_content)
- ERB.new(fixture_content).result
- end
-end
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+ end
-class Fixture #:nodoc:
- include Enumerable
+ class Fixture #:nodoc:
+ include Enumerable
- class FixtureError < StandardError #:nodoc:
- end
+ class FixtureError < StandardError #:nodoc:
+ end
- class FormatError < FixtureError #:nodoc:
- end
+ class FormatError < FixtureError #:nodoc:
+ end
- attr_reader :model_class, :fixture
+ attr_reader :model_class, :fixture
- def initialize(fixture, model_class)
- @fixture = fixture
- @model_class = model_class
- end
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class
+ end
- def class_name
- model_class.name if model_class
- end
+ def class_name
+ model_class.name if model_class
+ end
- def each
- fixture.each { |item| yield item }
- end
+ def each
+ fixture.each { |item| yield item }
+ end
- def [](key)
- fixture[key]
- end
+ def [](key)
+ fixture[key]
+ end
- alias :to_hash :fixture
+ alias :to_hash :fixture
- def find
- if model_class
- model_class.find(fixture[model_class.primary_key])
- else
- raise FixtureClassNotFound, "No class attached to find."
+ def find
+ if model_class
+ model_class.find(fixture[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
end
end
end
@@ -832,7 +787,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.fixture_class_names = Hash.new do |h, table_name|
- h[table_name] = Fixtures.find_table_name(table_name)
+ h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
end
end
@@ -944,7 +899,7 @@ module ActiveRecord
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
@@already_loaded_fixtures[self.class] = nil
@loaded_fixtures = load_fixtures
end
@@ -957,7 +912,7 @@ module ActiveRecord
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
unless run_in_transaction?
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
end
# Rollback changes if a transaction is active.
@@ -970,7 +925,7 @@ module ActiveRecord
private
def load_fixtures
- fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
Hash[fixtures.map { |f| [f.name, f] }]
end
@@ -979,16 +934,16 @@ module ActiveRecord
def instantiate_fixtures
if pre_loaded_fixtures
- raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
unless @@required_fixture_classes
- self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
@@required_fixture_classes = true
end
- Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
@loaded_fixtures.each do |fixture_name, fixtures|
- Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 9eb47ad99f..b15b5a8133 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -12,10 +12,36 @@ module ActiveRecord
# In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
# in your <tt>config/application.rb</tt> file.
#
- # IdentityMap is disabled by default.
+ # IdentityMap is disabled by default and still in development (i.e. use it with care).
+ #
+ # == Associations
+ #
+ # Active Record Identity Map does not track associations yet. For example:
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # @post.comments.include?(comment) #=> true
+ #
+ # Ideally, the example above would return false, removing the comment object from the
+ # post association when the association is nullified. This may cause side effects, as
+ # in the situation below, if Identity Map is enabled:
+ #
+ # Post.has_many :comments, :dependent => :destroy
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # comment.save
+ # Post.destroy(@post.id)
+ #
+ # Without using Identity Map, the code above will destroy the @post object leaving
+ # the comment object intact. However, once we enable Identity Map, the post loaded
+ # by Post.destroy is exactly the same object as the object @post. As the object @post
+ # still has the comment object in @post.comments, once Identity Map is enabled, the
+ # comment object will be accidently removed.
+ #
+ # This inconsistency is meant to be fixed in future Rails releases.
#
module IdentityMap
- extend ActiveSupport::Concern
class << self
def enabled=(flag)
@@ -49,11 +75,11 @@ module ActiveRecord
end
def get(klass, primary_key)
- record = repository[klass.symbolized_base_class][primary_key]
+ record = repository[klass.symbolized_sti_name][primary_key]
if record.is_a?(klass)
ActiveSupport::Notifications.instrument("identity.active_record",
- :line => "From Identity Map (id: #{primary_key})",
+ :line => "From Identity Map (id: #{primary_key})",
:name => "#{klass} Loaded",
:connection_id => object_id)
@@ -64,15 +90,15 @@ module ActiveRecord
end
def add(record)
- repository[record.class.symbolized_base_class][record.id] = record
+ repository[record.class.symbolized_sti_name][record.id] = record
end
def remove(record)
- repository[record.class.symbolized_base_class].delete(record.id)
+ repository[record.class.symbolized_sti_name].delete(record.id)
end
- def remove_by_id(symbolized_base_class, id)
- repository[symbolized_base_class].delete(id)
+ def remove_by_id(symbolized_sti_name, id)
+ repository[symbolized_sti_name].delete(id)
end
def clear
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 9a31675782..cdedcde0eb 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -94,7 +94,7 @@ module ActiveRecord
relation = self.class.unscoped
stmt = relation.where(
- relation.table[self.class.primary_key].eq(quoted_id).and(
+ relation.table[self.class.primary_key].eq(id).and(
relation.table[lock_col].eq(quote_value(previous_lock_value))
)
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0893d7e337..c723436330 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -110,8 +110,8 @@ module ActiveRecord
next unless respond_to?(callback)
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do
- observer.send(callback, self)
+ klass.send(:define_method, callback_meth) do |&block|
+ observer.send(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b4531ed35f..b9041f44d8 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -146,7 +146,7 @@ module ActiveRecord
# will fail and false will be returned.
#
# When updating model attributes, mass-assignment security protection is respected.
- # If no +:as+ option is supplied then the +:default+ scope will be used.
+ # If no +:as+ option is supplied then the +:default+ role will be used.
# If you want to bypass the protection given by +attr_protected+ and
# +attr_accessible+ then you can do so using the +:without_protection+ option.
def update_attributes(attributes, options = {})
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 929998eb85..4e61671473 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -40,6 +40,7 @@ module ActiveRecord
def close
@target.close if @target.respond_to?(:close)
ensure
+ ActiveRecord::Base.connection.clear_query_cache
unless @original_cache_value
ActiveRecord::Base.connection.disable_query_cache!
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5703fac033..85ad43b35f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -203,18 +203,18 @@ db_namespace = namespace :db do
# only files matching "20091231235959_some_name.rb" pattern
if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2]]
+ file_list << [status, match_data[1], match_data[2].humanize]
end
end
+ db_list.map! do |version|
+ ['up', version, '********** NO FILE **********']
+ end
# output
puts "\ndatabase: #{config['database']}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- file_list.each do |file|
- puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
- end
- db_list.each do |version|
- puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration|
+ puts "#{migration[0].center(8)} #{migration[1].ljust(14)} #{migration[2]}"
end
puts
end
@@ -305,7 +305,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
- Fixtures.create_fixtures(fixtures_dir, fixture_file)
+ ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -316,13 +316,13 @@ db_namespace = namespace :db do
label, id = ENV['LABEL'], ENV['ID']
raise 'LABEL or ID required' if label.blank? && id.blank?
- puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
+ puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
- key_id = Fixtures.identify(key)
+ key_id = ActiveRecord::Fixtures.identify(key)
if key == label || key_id == id.to_i
puts "#{file}: #{key} (#{key_id})"
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 869eebfa34..0fcae92d51 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -146,7 +146,7 @@ module ActiveRecord
if options.except(:distinct).present?
apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
else
- if eager_loading? || includes_values.present?
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
perform_calculation(operation, column_name, options)
@@ -161,21 +161,20 @@ module ActiveRecord
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
- distinct = nil
+ distinct = options[:distinct]
if operation == "count"
column_name ||= (select_for_count || :all)
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
- column_name = primary_key if column_name == :all
end
+ column_name = primary_key if column_name == :all && distinct
+
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
- distinct = options[:distinct] || distinct
-
if @group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
@@ -197,7 +196,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = except(:order)
+ relation = reorder(nil)
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 57c9921ea8..32d1cff6c3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -375,7 +375,12 @@ module ActiveRecord
if loaded?
@records.last
else
- @last ||= reverse_order.limit(1).to_a[0]
+ @last ||=
+ if offset_value || limit_value
+ to_a.last
+ else
+ reverse_order.limit(1).to_a[0]
+ end
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 7e77aefb21..c3e976002e 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# You must implement these methods:
#
# self.find_by_session_id(session_id)
- # initialize(hash_of_session_id_and_data)
+ # initialize(hash_of_session_id_and_data, options_hash = {})
# attr_reader :session_id
# attr_accessor :data
# save
@@ -83,6 +83,8 @@ module ActiveRecord
cattr_accessor :data_column_name
self.data_column_name = 'data'
+ attr_accessible :session_id, :data, :marshaled_data
+
before_save :marshal_data!
before_save :raise_on_session_data_overflow!
@@ -123,7 +125,7 @@ module ActiveRecord
end
end
- def initialize(attributes = nil)
+ def initialize(attributes = nil, options = {})
@data = nil
super
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 29efbbcb8c..0d47eb3338 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -13,6 +13,13 @@ module ActiveRecord
ActiveRecord::IdentityMap.clear
end
+ # Backport skip to Ruby 1.8. test/unit doesn't support it, so just
+ # make it a noop.
+ unless instance_methods.map(&:to_s).include?("skip")
+ def skip(message)
+ end
+ end
+
def assert_date_from_db(expected, actual, message = nil)
# SybaseAdapter doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index de36dd20b3..59b6876135 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -32,11 +32,11 @@ module ActiveRecord
module ClassMethods
# Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
# so an exception is raised if the record is invalid.
- def create!(attributes = nil, &block)
+ def create!(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| create!(attr, &block) }
+ attributes.collect { |attr| create!(attr, options, &block) }
else
- object = new(attributes)
+ object = new(attributes, options)
yield(object) if block_given?
object.save!
object
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 0667be7d23..2c20dd997f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 43015098c9..292c7efebb 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
new file mode 100644
index 0000000000..cd9c1041dc
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -0,0 +1,50 @@
+require "cases/helper"
+require 'models/topic'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2Adapter
+ class BindParameterTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_update_question_marks
+ str = "foo?bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_question_marks
+ str = "foo?bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_update_null_bytes
+ str = "foo\0bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_null_bytes
+ str = "foo\0bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1efa7deaeb..752b864818 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 39e8a7960a..49d8722aff 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -51,7 +51,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors])
assert_nothing_raised do
- assert_equal 3, categories.count
+ assert_equal 4, categories.count
+ assert_equal 4, categories.all.count
+ assert_equal 3, categories.count(:distinct => true)
assert_equal 3, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
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 007f11b535..b149f5912f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -66,6 +66,63 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'exotic', bulb.name
end
+ def test_create_from_association_with_nil_values_should_work
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.build(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.create(nil)
+ assert_equal 'defaulty', bulb.name
+ end
+
+ def test_association_keys_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.new :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.build
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.build :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.create
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.create :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+ end
+
+ def test_association_conditions_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.frickinawesome_bulbs.new
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.new(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.build
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.build(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.create
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.create(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
@@ -605,6 +662,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
end
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index f3c96ccbe6..356a4a7a09 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -4,6 +4,7 @@ require 'models/project'
require 'models/company'
require 'models/ship'
require 'models/pirate'
+require 'models/car'
require 'models/bulb'
class HasOneAssociationsTest < ActiveRecord::TestCase
@@ -95,6 +96,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nil Account.find(old_account_id).firm_id
end
+ def test_natural_assignment_to_nil_after_destroy
+ firm = companies(:rails_core)
+ old_account_id = firm.account.id
+ firm.account.destroy
+ firm.account = nil
+ assert_nil companies(:rails_core).account
+ assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
+ end
+
def test_association_change_calls_delete
companies(:first_firm).deletable_account = Account.new(:credit_limit => 5)
assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
@@ -359,4 +369,45 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
assert_nil new_ship.pirate_id
end
+
+ def test_deprecated_association_loaded
+ firm = companies(:first_firm)
+ firm.association(:account).stubs(:loaded?).returns(stub)
+
+ assert_deprecated do
+ assert_equal firm.association(:account).loaded?, firm.account_loaded?
+ end
+ end
+
+ def test_association_keys_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_bulb
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.build_bulb :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.create_bulb
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.create_bulb :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+ end
+
+ def test_association_conditions_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_frickinawesome_bulb
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.build_frickinawesome_bulb(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.create_frickinawesome_bulb
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.create_frickinawesome_bulb(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 968025ade8..2503349c08 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -310,4 +310,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal dashboard, minivan.dashboard
assert_equal dashboard, minivan.speedometer.dashboard
end
+
+ def test_has_one_through_with_custom_select_on_join_model_default_scope
+ assert_equal clubs(:boring_club), members(:groucho).selected_club
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index e2228228a3..55d9a328a7 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'models/post'
require 'models/comment'
require 'models/author'
+require 'models/essay'
require 'models/category'
require 'models/categorization'
require 'models/person'
@@ -34,6 +35,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_no_match(/JOIN/i, sql)
end
+ def test_join_conditions_added_to_join_clause
+ sql = Author.joins(:essays).to_sql
+ assert_match(/writer_type.*?=.*?Author/i, sql)
+ assert_no_match(/WHERE/i, sql)
+ end
+
+ def test_join_conditions_allow_nil_associations
+ authors = Author.includes(:essays).where(:essays => {:id => nil})
+ assert_equal 2, authors.count
+ end
+
def test_find_with_implicit_inner_joins_honors_readonly_without_select
authors = Author.joins(:posts).to_a
assert !authors.empty?, "expected authors to be non-empty"
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 04f628a398..49d82ba2df 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -85,7 +85,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_should_construct_new_finder_sql_after_create
person = Person.new :first_name => 'clark'
- assert_equal [], person.readers.find(:all)
+ assert_equal [], person.readers.all
person.save!
reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar")
assert person.readers.find(reader.id)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5ee3b2d776..9bc04ed29c 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -45,6 +45,10 @@ class ReadonlyTitlePost < Post
attr_readonly :title
end
+class ProtectedTitlePost < Post
+ attr_protected :title
+end
+
class Weird < ActiveRecord::Base; end
class Boolean < ActiveRecord::Base; end
@@ -105,7 +109,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_select_symbol
topic_ids = Topic.select(:id).map(&:id).sort
- assert_equal Topic.find(:all).map(&:id).sort, topic_ids
+ assert_equal Topic.all.map(&:id).sort, topic_ids
end
def test_table_exists
@@ -491,8 +495,9 @@ class BasicsTest < ActiveRecord::TestCase
def test_attributes_guard_protected_attributes_is_deprecated
attributes = { "title" => "An amazing title" }
- topic = Topic.new
- assert_deprecated { topic.send(:attributes=, attributes, false) }
+ post = ProtectedTitlePost.new
+ assert_deprecated { post.send(:attributes=, attributes, false) }
+ assert_equal "An amazing title", post.title
end
def test_multiparameter_attributes_on_date
@@ -575,6 +580,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
+ def test_multiparameter_attributes_on_time_with_no_date
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
def test_multiparameter_attributes_on_time_with_old_date
attributes = {
"written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
@@ -586,6 +614,82 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
end
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 0, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 2, topic.written_on.sec
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 24, topic.written_on.min
+ assert_equal 0, topic.written_on.sec
+ end
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 02, topic.written_on.sec
+ end
+
def test_multiparameter_attributes_on_time_with_utc
ActiveRecord::Base.default_timezone = :utc
attributes = {
@@ -692,6 +796,42 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal address, customer.address
end
+ def test_multiparameter_assignment_of_aggregation_out_of_order
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_large_index
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 654c4c9010..56f6d795b6 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -319,6 +319,17 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
end
+ def test_should_not_perform_joined_include_by_default
+ assert_equal Account.count, Account.includes(:firm).count
+ queries = assert_sql { Account.includes(:firm).count }
+ assert_no_match(/join/i, queries.last)
+ end
+
+ def test_should_perform_joined_include_when_referencing_included_tables
+ joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count
+ assert_equal 1, joined_count
+ end
+
def test_should_count_scoped_select
Account.update_all("credit_limit = NULL")
assert_equal 0, Account.scoped(:select => "credit_limit").count
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index be4ba18555..4e75eafe3d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -683,6 +683,27 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
end
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
def test_find_all_by_one_attribute
topics = Topic.find_all_by_content("Have a nice day")
assert_equal 2, topics.size
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 3e20155210..b0bd9c5763 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase
FIXTURES.each do |name|
fixtures = nil
assert_nothing_raised { fixtures = create_fixtures(name).first }
- assert_kind_of(Fixtures, fixtures)
+ assert_kind_of(ActiveRecord::Fixtures, fixtures)
fixtures.each { |_name, fixture|
fixture.each { |key, value|
assert_match(MATCH_ATTRIBUTE_NAME, key)
@@ -46,7 +46,7 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_create_fixtures
- Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
assert Parrot.find_by_name('Curious George'), 'George is in the database'
end
@@ -54,7 +54,7 @@ class FixturesTest < ActiveRecord::TestCase
fixtures_array = nil
assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
assert_kind_of(Array, fixtures_array)
- fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
+ fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) }
end
def test_attributes
@@ -75,7 +75,7 @@ class FixturesTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_migrations?
def test_inserts_with_pre_and_suffix
# Reset cache to make finds on the new table work
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
t.column :title, :string
@@ -150,11 +150,11 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
@@ -164,23 +164,25 @@ class FixturesTest < ActiveRecord::TestCase
assert Dir[nonexistent_fixture_path+"*"].empty?
assert_raise(FixturesFileNotFound) do
- Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
+ ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
- assert_raise(Fixture::FormatError) do
- Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
+ assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
end
end
def test_empty_csv_fixtures
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ assert_deprecated do
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ end
end
def test_omap_fixtures
assert_nothing_raised do
- fixtures = Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
+ fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
i = 0
fixtures.each do |name, fixture|
@@ -220,7 +222,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def setup
@instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
- Fixtures.reset_cache # make sure tables get reinitialized
+ ActiveRecord::Fixtures.reset_cache # make sure tables get reinitialized
end
def test_resets_to_min_pk_with_specified_pk_and_sequence
@@ -524,13 +526,13 @@ class FasterFixturesTest < ActiveRecord::TestCase
def load_extra_fixture(name)
fixture = create_fixtures(name).first
- assert fixture.is_a?(Fixtures)
+ assert fixture.is_a?(ActiveRecord::Fixtures)
@loaded_fixtures[fixture.table_name] = fixture
end
def test_cache
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
assert_no_queries do
create_fixtures('categories')
@@ -538,7 +540,7 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
load_extra_fixture('posts')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
self.class.setup_fixture_accessors('posts')
assert_equal 'Welcome to the weblog', posts(:welcome).title
end
@@ -548,17 +550,17 @@ class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
def test_identifies_strings
- assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo"))
- assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO"))
+ assert_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("foo"))
+ assert_not_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("FOO"))
end
def test_identifies_symbols
- assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo))
+ assert_equal(ActiveRecord::Fixtures.identify(:foo), ActiveRecord::Fixtures.identify(:foo))
end
def test_identifies_consistently
- assert_equal 207281424, Fixtures.identify(:ruby)
- assert_equal 1066363776, Fixtures.identify(:sapphire_2)
+ assert_equal 207281424, ActiveRecord::Fixtures.identify(:ruby)
+ assert_equal 1066363776, ActiveRecord::Fixtures.identify(:sapphire_2)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index fd20f1b120..fbb4ee6f7b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -104,7 +104,7 @@ class ActiveSupport::TestCase
self.use_transactional_fixtures = true
def create_fixtures(*table_names, &block)
- Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
+ ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
end
end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 649715fbb5..a0e16400d2 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -129,6 +129,41 @@ class IdentityMapTest < ActiveRecord::TestCase
end
##############################################################################
+ # Tests checking if IM is functioning properly on classes with multiple #
+ # types of inheritance #
+ ##############################################################################
+
+ def test_inherited_without_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+ end
+
+ def test_inherited_with_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1.class, c2.class)
+ end
+ end
+
+ def test_inherited_without_type_attribute
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+
+ def test_inherited_with_type_attribute
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1, c2)
+ end
+
+ ##############################################################################
# Tests checking dirty attribute behaviour with IM #
##############################################################################
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 6cd8494c9e..643e949087 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -107,6 +107,23 @@ class ValidatedCommentObserver < ActiveRecord::Observer
end
end
+
+class AroundTopic < Topic
+end
+
+class AroundTopicObserver < ActiveRecord::Observer
+ observe :around_topic
+ def topic_ids
+ @topic_ids ||= []
+ end
+
+ def around_save(topic)
+ topic_ids << topic.id
+ yield(topic)
+ topic_ids << topic.id
+ end
+end
+
class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
@@ -206,6 +223,14 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer, SalaryChecker.instance.last_saved
end
+ test "around filter from observer should accept block" do
+ observer = AroundTopicObserver.instance
+ topic = AroundTopic.new
+ topic.save
+ assert_nil observer.topic_ids.first
+ assert_not_nil observer.topic_ids.last
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 636a709924..61baa55027 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -5,6 +5,7 @@ require 'models/job'
require 'models/reader'
require 'models/legacy_thing'
require 'models/reference'
+require 'models/string_key_object'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -18,7 +19,40 @@ class ReadonlyFirstNamePerson < Person
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references
+ fixtures :people, :legacy_things, :references, :string_key_objects
+
+ def test_non_integer_lock_existing
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s2.name = 'doubly updated record'
+ assert_raise(ActiveRecord::StaleObjectError) { s2.save! }
+ end
+
+ def test_non_integer_lock_destroy
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+ assert_raise(ActiveRecord::StaleObjectError) { s2.destroy }
+
+ assert s1.destroy
+ assert s1.frozen?
+ assert s1.destroyed?
+ assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") }
+ end
def test_lock_existing
p1 = Person.find(1)
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index fbbae99e8b..765033852d 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -87,7 +87,11 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
- def test_assign_attributes_uses_default_scope_when_no_scope_is_provided
+ def test_mass_assigning_does_not_choke_on_nil
+ Firm.new.assign_attributes(nil)
+ end
+
+ def test_assign_attributes_uses_default_role_when_no_role_is_provided
p = LoosePerson.new
p.assign_attributes(attributes_hash)
@@ -101,28 +105,28 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_all_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_default_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_admin_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_default_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_admin_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :admin)
@@ -153,30 +157,42 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_default_attributes(p, true)
end
- def test_new_with_admin_scope_with_attr_accessible_attributes
+ def test_new_with_admin_role_with_attr_accessible_attributes
p = TightPerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_new_with_admin_scope_with_attr_protected_attributes
+ def test_new_with_admin_role_with_attr_protected_attributes
p = LoosePerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_create_with_admin_scope_with_attr_accessible_attributes
+ def test_create_with_admin_role_with_attr_accessible_attributes
p = TightPerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
end
- def test_create_with_admin_scope_with_attr_protected_attributes
+ def test_create_with_admin_role_with_attr_protected_attributes
p = LoosePerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
end
+ def test_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ p = TightPerson.create!(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_create_with_bang_with_admin_role_with_attr_protected_attributes
+ p = LoosePerson.create!(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
def test_new_with_without_protection_with_attr_accessible_attributes
p = TightPerson.new(attributes_hash, :without_protection => true)
@@ -201,6 +217,18 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_all_attributes(p)
end
+ def test_create_with_bang_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.create!(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_bang_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.create!(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
def test_protection_against_class_attribute_writers
[:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
:default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
@@ -230,12 +258,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -257,12 +285,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -284,12 +312,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -318,12 +346,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -345,12 +373,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -372,12 +400,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -406,12 +434,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -433,12 +461,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -460,12 +488,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 7f0f007a70..a0cb5dbdc5 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -68,7 +68,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_scoped_find_all
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal [developers(:david)], Developer.find(:all)
+ assert_equal [developers(:david)], Developer.all
end
end
@@ -235,23 +235,23 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_immutable_scope
options = { :conditions => "name = 'David'" }
Developer.send(:with_scope, :find => options) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
options[:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
scope = { :find => { :conditions => "name = 'David'" }}
Developer.send(:with_scope, scope) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
scope[:find][:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
def test_scoped_with_duck_typing
scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
Developer.send(:with_scope, scoping) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
@@ -432,7 +432,7 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merged_scoped_find_combines_and_sanitizes_conditions
Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
end
@@ -487,9 +487,9 @@ class NestedScopingTest < ActiveRecord::TestCase
options2 = { :conditions => "name = 'David'" }
Developer.send(:with_scope, :find => options1) do
Developer.send(:with_exclusive_scope, :find => options2) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
end
@@ -499,9 +499,9 @@ class NestedScopingTest < ActiveRecord::TestCase
options2 = { :conditions => "salary > 10000" }
Developer.send(:with_scope, :find => options1) do
Developer.send(:with_scope, :find => options2) do
- assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(Jamis), Developer.all.map(&:name)
options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(Jamis), Developer.all.map(&:name)
end
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 8fd1fc2577..34188e4915 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -462,7 +462,7 @@ class NamedScopeTest < ActiveRecord::TestCase
[:destroy_all, :reset, :delete_all].each do |method|
before = post.comments.containing_the_letter_e
post.association(:comments).send(method)
- assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache"
+ assert before.object_id != post.comments.containing_the_letter_e.object_id, "CollectionAssociation##{method} should reset the named scopes cache"
end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b066575af8..57d1441128 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -336,6 +336,10 @@ class PersistencesTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
+ def test_update_attribute_does_not_choke_on_nil
+ assert Topic.find(1).update_attributes(nil)
+ end
+
def test_update_attribute_for_readonly_attribute
minivan = Minivan.find('m1')
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index b2e40c6b22..a61180cfaf 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -65,6 +65,17 @@ class QueryCacheTest < ActiveRecord::TestCase
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
end
+ def test_cache_clear_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Post.find(:first)
+ }
+ body = mw.call({}).last
+
+ assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
+ body.close
+ assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
+ end
+
def test_find_queries
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 864b3d4846..c215602567 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -462,4 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
+
+ def test_default_scope_order_ignored_by_aggregations
+ assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count
+ end
end
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
index cee5ddd003..669c0b7b4d 100644
--- a/activerecord/test/cases/session_store/session_test.rb
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -21,6 +21,12 @@ module ActiveRecord
assert_equal 'sessions', Session.table_name
end
+ def test_accessible_attributes
+ assert Session.accessible_attributes.include?(:session_id)
+ assert Session.accessible_attributes.include?(:data)
+ assert Session.accessible_attributes.include?(:marshaled_data)
+ end
+
def test_create_table!
assert !Session.table_exists?
Session.create_table!
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index ceb1452afd..22d4cac422 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -14,6 +14,32 @@ class TimestampTest < ActiveRecord::TestCase
@previously_updated_at = @developer.updated_at
end
+ def test_load_infinity_and_beyond
+ unless current_adapter?(:PostgreSQLAdapter)
+ return skip("only tested on postgresql")
+ end
+
+ d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at")
+ assert d.first.updated_at.infinite?, 'timestamp should be infinite'
+
+ d = Developer.find_by_sql("select '-infinity'::timestamp as updated_at")
+ time = d.first.updated_at
+ assert time.infinite?, 'timestamp should be infinite'
+ assert_operator time, :<, 0
+ end
+
+ def test_save_infinity_and_beyond
+ unless current_adapter?(:PostgreSQLAdapter)
+ return skip("only tested on postgresql")
+ end
+
+ d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0)
+ assert_equal(1.0 / 0.0, d.updated_at)
+
+ d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0)
+ assert_equal(-1.0 / 0.0, d.updated_at)
+ end
+
def test_saving_a_changed_record_updates_its_timestamp
@developer.name = "Jack Bauer"
@developer.save!
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index a6074b23e7..756c8a32eb 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -143,10 +143,7 @@ class NilXmlSerializationTest < ActiveRecord::TestCase
end
def test_should_serialize_yaml
- assert %r{<preferences(.*)></preferences>}.match(@xml)
- attributes = $1
- assert_match %r{type="yaml"}, attributes
- assert_match %r{nil="true"}, attributes
+ assert_match %r{<preferences nil=\"true\"></preferences>}, @xml
end
end
diff --git a/activerecord/test/fixtures/all/people.csv b/activerecord/test/fixtures/all/people.yml
index e69de29bb2..e69de29bb2 100644
--- a/activerecord/test/fixtures/all/people.csv
+++ b/activerecord/test/fixtures/all/people.yml
diff --git a/activerecord/test/fixtures/mateys.yml b/activerecord/test/fixtures/mateys.yml
index 9ecdd4ecd5..d3690955fc 100644
--- a/activerecord/test/fixtures/mateys.yml
+++ b/activerecord/test/fixtures/mateys.yml
@@ -1,4 +1,4 @@
blackbeard_to_redbeard:
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
- target_id: <%= Fixtures.identify(:redbeard) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
+ target_id: <%= ActiveRecord::Fixtures.identify(:redbeard) %>
weight: 10
diff --git a/activerecord/test/fixtures/memberships.yml b/activerecord/test/fixtures/memberships.yml
index 60eb641054..a5d52bd438 100644
--- a/activerecord/test/fixtures/memberships.yml
+++ b/activerecord/test/fixtures/memberships.yml
@@ -25,3 +25,10 @@ blarpy_winkup_crazy_club:
member_id: 3
favourite: false
type: CurrentMembership
+
+selected_membership_of_boring_club:
+ joined_on: <%= 3.weeks.ago.to_s(:db) %>
+ club: boring_club
+ member_id: 1
+ favourite: false
+ type: SelectedMembership
diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml
index 6b17a37d68..66472243c7 100644
--- a/activerecord/test/fixtures/parrots_pirates.yml
+++ b/activerecord/test/fixtures/parrots_pirates.yml
@@ -1,7 +1,7 @@
george_blackbeard:
- parrot_id: <%= Fixtures.identify(:george) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:george) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
louis_blackbeard:
- parrot_id: <%= Fixtures.identify(:louis) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:louis) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
diff --git a/activerecord/test/fixtures/string_key_objects.yml b/activerecord/test/fixtures/string_key_objects.yml
new file mode 100644
index 0000000000..fa1299915b
--- /dev/null
+++ b/activerecord/test/fixtures/string_key_objects.yml
@@ -0,0 +1,7 @@
+first:
+ id: record1
+ name: first record
+
+second:
+ id: record2
+ name: second record
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index c68d008c26..643dcefed3 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -2,6 +2,8 @@ class Bulb < ActiveRecord::Base
default_scope where(:name => 'defaulty')
belongs_to :car
+ attr_protected :car_id, :frickinawesome
+
attr_reader :scope_after_initialize
after_initialize :record_scope_after_initialize
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index b036f0f5c9..76f20b1061 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -2,6 +2,11 @@ class Car < ActiveRecord::Base
has_many :bulbs
has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' }
+ has_many :frickinawesome_bulbs, :class_name => "Bulb", :conditions => { :frickinawesome => true }
+
+ has_one :bulb
+ has_one :frickinawesome_bulb, :class_name => "Bulb", :conditions => { :frickinawesome => true }
+
has_many :tyres
has_many :engines
has_many :wheels, :as => :wheelable
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 991e0e051f..11a0f4ff63 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -1,8 +1,10 @@
class Member < ActiveRecord::Base
has_one :current_membership
+ has_one :selected_membership
has_one :membership
has_many :fellow_members, :through => :club, :source => :members
has_one :club, :through => :current_membership
+ has_one :selected_club, :through => :selected_membership, :source => :club
has_one :favourite_club, :through => :membership, :conditions => ["memberships.favourite = ?", true], :source => :club
has_one :hairy_club, :through => :membership, :conditions => {:clubs => {:name => "Moustache and Eyebrow Fancier Club"}}, :source => :club
has_one :sponsor, :as => :sponsorable
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index 905f948c37..bcbb7e42c5 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -7,3 +7,9 @@ class CurrentMembership < Membership
belongs_to :member
belongs_to :club
end
+
+class SelectedMembership < Membership
+ def self.default_scope
+ select("'1' as foo")
+ end
+end
diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb
new file mode 100644
index 0000000000..f8d4c6e0e4
--- /dev/null
+++ b/activerecord/test/models/string_key_object.rb
@@ -0,0 +1,3 @@
+class StringKeyObject < ActiveRecord::Base
+ set_primary_key :id
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9479242e4f..c8a98f121d 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -89,6 +89,7 @@ ActiveRecord::Schema.define do
create_table :bulbs, :force => true do |t|
t.integer :car_id
t.string :name
+ t.boolean :frickinawesome
end
create_table "CamelCase", :force => true do |t|
@@ -543,6 +544,12 @@ ActiveRecord::Schema.define do
t.string :sponsorable_type
end
+ create_table :string_key_objects, :id => false, :primary_key => :id, :force => true do |t|
+ t.string :id
+ t.string :name
+ t.integer :lock_version, :null => false, :default => 0
+ end
+
create_table :students, :force => true do |t|
t.string :name
end
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 386bafd7de..a4e79f3d77 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -2,14 +2,42 @@
* No changes
-*Rails 3.0.2 (unreleased)*
+
+*Rails 3.0.7 (April 18, 2011)*
+
+*No changes.
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* No changes.
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* No changes.
+
+
+*Rails 3.0.2 (November 15, 2010)*
* No changes
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. [Santiago Pastorino]
diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb
index 186865f811..0794a1a800 100644
--- a/activeresource/lib/active_resource.rb
+++ b/activeresource/lib/active_resource.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2006 David Heinemeier Hansson
+# Copyright (c) 2006-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index 82dcb5d575..f26e2312b9 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -3,7 +3,7 @@ module ActiveResource
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activeresource/test/cases/format_test.rb b/activeresource/test/cases/format_test.rb
index fc1a7b8c6f..f8d33f99fa 100644
--- a/activeresource/test/cases/format_test.rb
+++ b/activeresource/test/cases/format_test.rb
@@ -86,7 +86,7 @@ class FormatTest < Test::Unit::TestCase
def test_serialization_of_nested_resource
address = { :street => '12345 Street' }
- person = { :name=> 'Rus', :address => address}
+ person = { :name => 'Rus', :address => address}
[:json, :xml].each do |format|
encoded_person = ActiveResource::Formats[format].encode(person)
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 69e9cbfd42..23b0df1d5c 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* New reporting method Kernel#quietly. [fxn]
+
* Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH]
* Add Object#in? to test if an object is included in another object [Prem Sichanugrist, Brian Morearty, John Reitano]
@@ -19,14 +21,44 @@ advantage of the new ClassCache.
* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
-*Rails 3.0.2 (unreleased)*
+* JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. [Josh Kalderimis]
+
+
+*Rails 3.0.7 (April 18, 2011)*
+
+* Hash.from_xml no longer loses attributes on tags containing only whitespace [André Arko]
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* No changes.
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* No changes.
+
+
+*Rails 3.0.2 (November 15, 2010)*
* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* Implemented String#strip_heredoc. [fxn]
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index 13ca4b3bf1..8bb15e849a 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -30,4 +30,4 @@ API documentation is at
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
+* https://github.com/rails/rails/issues
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 6b87774978..a846f81c12 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005 David Heinemeier Hansson
+# Copyright (c) 2005-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 88b50fc506..a14f008be5 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -49,10 +49,12 @@ module ActiveSupport
@log = log
elsif File.exist?(log)
@log = open(log, (File::WRONLY | File::APPEND))
+ @log.binmode
@log.sync = true
else
FileUtils.mkdir_p(File.dirname(log))
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
+ @log.binmode
@log.sync = true
end
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 8c56a21ef7..a94446acde 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -2,6 +2,7 @@ require 'active_support/concern'
require 'active_support/ordered_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
@@ -51,14 +52,16 @@ module ActiveSupport
# user.allowed_access # => true
#
def config_accessor(*names)
+ options = names.extract_options!
+
names.each do |name|
- code, line = <<-RUBY, __LINE__ + 1
- def #{name}; config.#{name}; end
- def #{name}=(value); config.#{name} = value; end
- RUBY
+ reader, line = "def #{name}; config.#{name}; end", __LINE__
+ writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
- singleton_class.class_eval code, __FILE__, line
- class_eval code, __FILE__, line
+ singleton_class.class_eval reader, __FILE__, line
+ singleton_class.class_eval writer, __FILE__, line
+ class_eval reader, __FILE__, line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, line unless options[:instance_writer] == false
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 724e076407..236055d77a 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -206,7 +206,7 @@ class Date
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
- self.acts_like?(:time) ? change(:day => 1,:hour => 0, :min => 0, :sec => 0) : change(:day => 1)
+ self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
alias :at_beginning_of_month :beginning_of_month
@@ -231,13 +231,13 @@ class Date
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
def beginning_of_year
- self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0) : change(:month => 1, :day => 1)
+ self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
def end_of_year
- self.acts_like?(:time) ? change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
+ self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
end
alias :at_end_of_year :end_of_year
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 769ead9544..338104fd05 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,6 +1,7 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
@@ -13,10 +14,10 @@ class Date
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
- remove_method :to_time if method_defined?(:to_time)
+ remove_possible_method :to_time
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
- remove_method :xmlschema if method_defined?(:xmlschema)
+ remove_possible_method :xmlschema
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 61a1d88b0e..102378a029 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -108,7 +108,8 @@ class Hash
raise "can't typecast #{entries.inspect}"
end
end
- elsif value['type'] == 'file' || value["__content__"].present?
+ elsif value['type'] == 'file' ||
+ (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
content = value["__content__"]
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index aad4b61e16..c2a6476604 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -9,4 +9,16 @@ class Hash
def with_indifferent_access
ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
end
+
+ # Called when object is nested under an object that receives
+ # #with_indifferent_access. This method with be called on the current object
+ # by the enclosing object and is aliased to #with_indifferent_access by
+ # default. Subclasses of Hash may overwrite this method to return +self+ if
+ # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
+ # desirable.
+ #
+ # b = {:b => 1}
+ # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
+ #
+ alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 37a827123a..c6920098a8 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -62,7 +62,7 @@ module Kernel
# Captures the given stream and returns it:
#
- # stream = capture(:stdout){ puts "Cool" }
+ # stream = capture(:stdout) { puts "Cool" }
# stream # => "Cool\n"
#
def capture(stream)
@@ -78,4 +78,16 @@ module Kernel
result
end
alias :silence :capture
+
+ # Silences both STDOUT and STDERR, even for subprocesses.
+ #
+ # quietly { system 'bundle install' }
+ #
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 9c169a2598..5a5b4e3f80 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,5 +1,3 @@
-require 'active_support/deprecation'
-
class Module
# Declare that a method has been deprecated.
# deprecate :foo
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 7e134db118..dcac17536a 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -187,7 +187,7 @@ class Time
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
#(self - seconds_since_midnight).change(:usec => 0)
- change(:hour => 0, :min => 0, :sec => 0, :usec => 0)
+ change(:hour => 0)
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
@@ -201,7 +201,7 @@ class Time
# Returns a new Time representing the start of the month (1st of the month, 0:00)
def beginning_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
- change(:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
+ change(:day => 1, :hour => 0)
end
alias :at_beginning_of_month :beginning_of_month
@@ -227,7 +227,7 @@ class Time
# Returns a new Time representing the start of the year (1st of january, 0:00)
def beginning_of_year
- change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0, :usec => 0)
+ change(:month => 1, :day => 1, :hour => 0)
end
alias :at_beginning_of_year :beginning_of_year
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 1a4d918ce7..457d3f5b62 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
time.instance_eval do
if zone = defined?(@_zone) && remove_instance_variable('@_zone')
ary = to_a
+ ary[0] += subsec if ary[0] == sec
ary[-1] = zone
utc? ? Time.utc(*ary) : Time.local(*ary)
else
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 4d1cfacc95..e2a8b4d4e3 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -1,5 +1,3 @@
-require 'active_support/dependencies'
-
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
@@ -18,12 +16,16 @@ module ActiveSupport
end
def self.clear
- @@direct_descendants.each do |klass, descendants|
- if ActiveSupport::Dependencies.autoloaded?(klass)
- @@direct_descendants.delete(klass)
- else
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ if defined? ActiveSupport::Dependencies
+ @@direct_descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@direct_descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
end
+ else
+ @@direct_descendants.clear
end
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 79a0de7940..8ec4f6e09a 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -140,8 +140,8 @@ module ActiveSupport
end
def convert_value(value)
- if value.class == Hash
- self.class.new_from_hash_copying_default(value)
+ if value.is_a? Hash
+ value.nested_under_indifferent_access
elsif value.is_a?(Array)
value.dup.replace(value.map { |e| convert_value(e) })
else
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index de49750083..a2c4f7bfda 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -135,11 +135,13 @@ module ActiveSupport
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
# ordinalize(1003) # => "1003rd"
+ # ordinalize(-11) # => "-11th"
+ # ordinalize(-1021) # => "-1021st"
def ordinalize(number)
- if (11..13).include?(number.to_i % 100)
+ if (11..13).include?(number.to_i.abs % 100)
"#{number}th"
else
- case number.to_i % 10
+ case number.to_i.abs % 10
when 1; "#{number}st"
when 2; "#{number}nd"
when 3; "#{number}rd"
@@ -148,4 +150,4 @@ module ActiveSupport
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 82b8a7e148..d22fe14b33 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
require 'active_support/deprecation'
require 'active_support/json/variable'
+require 'active_support/ordered_hash'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -205,7 +206,9 @@ class Regexp
end
module Enumerable
- def as_json(options = nil) to_a end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ to_a.as_json(options)
+ end
end
class Array
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 392e33edbc..3e54134c5c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/log_subscriber'
require 'active_support/buffered_logger'
+require 'active_support/notifications'
module ActiveSupport
class LogSubscriber
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index fbc40d1b69..762a64a881 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -43,6 +43,10 @@ module ActiveSupport
end
end
+ def nested_under_indifferent_access
+ self
+ end
+
# Hash is ordered in Ruby 1.9!
if RUBY_VERSION < '1.9'
diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb
index 368954907f..52f8c72b77 100644
--- a/activesupport/lib/active_support/secure_random.rb
+++ b/activesupport/lib/active_support/secure_random.rb
@@ -1,205 +1,6 @@
-begin
- require 'securerandom'
-rescue LoadError
-end
+require 'securerandom'
module ActiveSupport
- if defined?(::SecureRandom)
- # Use Ruby's SecureRandom library if available.
- SecureRandom = ::SecureRandom # :nodoc:
- else
- # = Secure random number generator interface.
- #
- # This library is an interface for secure random number generator which is
- # suitable for generating session key in HTTP cookies, etc.
- #
- # It supports following secure random number generators.
- #
- # * openssl
- # * /dev/urandom
- # * Win32
- #
- # *Note*: This module is based on the SecureRandom library from Ruby 1.9,
- # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's
- # SecureRandom library.
- #
- # == Example
- #
- # # random hexadecimal string.
- # p SecureRandom.hex(10) # => "52750b30ffbc7de3b362"
- # p SecureRandom.hex(10) # => "92b15d6c8dc4beb5f559"
- # p SecureRandom.hex(11) # => "6aca1b5c58e4863e6b81b8"
- # p SecureRandom.hex(12) # => "94b2fff3e7fd9b9c391a2306"
- # p SecureRandom.hex(13) # => "39b290146bea6ce975c37cfc23"
- # ...
- #
- # # random base64 string.
- # p SecureRandom.base64(10) # => "EcmTPZwWRAozdA=="
- # p SecureRandom.base64(10) # => "9b0nsevdwNuM/w=="
- # p SecureRandom.base64(10) # => "KO1nIU+p9DKxGg=="
- # p SecureRandom.base64(11) # => "l7XEiFja+8EKEtY="
- # p SecureRandom.base64(12) # => "7kJSM/MzBJI+75j8"
- # p SecureRandom.base64(13) # => "vKLJ0tXBHqQOuIcSIg=="
- # ...
- #
- # # random binary string.
- # p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301"
- # p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337"
- # ...
- #
- module SecureRandom
-
- # Generates a random binary string.
- #
- # The argument n specifies the length of the result string.
- #
- # If n is not specified, 16 is assumed.
- # It may be larger in future.
- #
- # If secure random number generator is not available,
- # NotImplementedError is raised.
- #
- def self.random_bytes(n=nil)
- n ||= 16
-
- unless defined? OpenSSL
- begin
- require 'openssl'
- rescue LoadError
- end
- end
-
- if defined? OpenSSL::Random
- return OpenSSL::Random.random_bytes(n)
- end
-
- if !defined?(@has_urandom) || @has_urandom
- flags = File::RDONLY
- flags |= File::NONBLOCK if defined? File::NONBLOCK
- flags |= File::NOCTTY if defined? File::NOCTTY
- flags |= File::NOFOLLOW if defined? File::NOFOLLOW
- begin
- File.open("/dev/urandom", flags) {|f|
- unless f.stat.chardev?
- raise Errno::ENOENT
- end
- @has_urandom = true
- ret = f.readpartial(n)
- if ret.length != n
- raise NotImplementedError, "Unexpected partial read from random device"
- end
- return ret
- }
- rescue Errno::ENOENT
- @has_urandom = false
- end
- end
-
- unless defined?(@has_win32)
- begin
- require 'Win32API'
-
- crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
- @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
-
- hProvStr = " " * 4
- prov_rsa_full = 1
- crypt_verifycontext = 0xF0000000
-
- if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
- raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
- end
- @hProv, = hProvStr.unpack('L')
-
- @has_win32 = true
- rescue LoadError
- @has_win32 = false
- end
- end
- if @has_win32
- bytes = " " * n
- if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
- raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
- end
- return bytes
- end
-
- raise NotImplementedError, "No random device"
- end
-
- # Generates a random hex string.
- #
- # The argument n specifies the length of the random length.
- # The length of the result string is twice of n.
- #
- # If n is not specified, 16 is assumed.
- # It may be larger in future.
- #
- # If secure random number generator is not available,
- # NotImplementedError is raised.
- #
- def self.hex(n=nil)
- random_bytes(n).unpack("H*")[0]
- end
-
- # Generates a random base64 string.
- #
- # The argument n specifies the length of the random length.
- # The length of the result string is about 4/3 of n.
- #
- # If n is not specified, 16 is assumed.
- # It may be larger in future.
- #
- # If secure random number generator is not available,
- # NotImplementedError is raised.
- #
- def self.base64(n=nil)
- [random_bytes(n)].pack("m*").delete("\n")
- end
-
- # Generates a random number.
- #
- # If an positive integer is given as n,
- # SecureRandom.random_number returns an integer:
- # 0 <= SecureRandom.random_number(n) < n.
- #
- # If 0 is given or an argument is not given,
- # SecureRandom.random_number returns an float:
- # 0.0 <= SecureRandom.random_number() < 1.0.
- #
- def self.random_number(n=0)
- if 0 < n
- hex = n.to_s(16)
- hex = '0' + hex if (hex.length & 1) == 1
- bin = [hex].pack("H*")
- mask = bin[0]
- mask |= mask >> 1
- mask |= mask >> 2
- mask |= mask >> 4
- begin
- rnd = SecureRandom.random_bytes(bin.length)
- rnd[0] = rnd[0] & mask
- end until rnd < bin
- rnd.unpack("H*")[0].hex
- else
- # assumption: Float::MANT_DIG <= 64
- i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
- Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
- end
- end
-
- # Following code is based on David Garamond's GUID library for Ruby.
- def self.lastWin32ErrorMessage # :nodoc:
- get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
- format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
- format_message_ignore_inserts = 0x00000200
- format_message_from_system = 0x00001000
-
- code = get_last_error.call
- msg = "\0" * 1024
- len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
- msg[0, len].tr("\r", '').chomp
- end
- end
- end
+ # Use Ruby's SecureRandom library.
+ SecureRandom = ::SecureRandom # :nodoc:
end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 8c91a061fb..7cd9bfa947 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -3,12 +3,100 @@ begin
require 'fileutils'
require 'rails/version'
+ require 'active_support/concern'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/string/inflections'
module ActiveSupport
module Testing
module Performance
+ extend ActiveSupport::Concern
+
+ included do
+ superclass_delegating_accessor :profile_options
+ self.profile_options = DEFAULTS
+
+ if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
+ include ForMiniTest
+ else
+ include ForClassicTestUnit
+ end
+ end
+
+ module ForMiniTest
+ def run(runner)
+ @runner = runner
+
+ run_warmup
+ if profile_options && metrics = profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
+ end
+ end
+ end
+ end
+
+ def run_test(metric, mode)
+ result = '.'
+ begin
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ method_name }
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ ensure
+ begin
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ end
+ end
+ result
+ end
+ end
+
+ module ForClassicTestUnit
+ def run(result)
+ return if method_name =~ /^default_test$/
+
+ yield(self.class::STARTED, name)
+ @_result = result
+
+ run_warmup
+ if profile_options && metrics = profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
+ result.add_run
+ end
+ end
+ end
+
+ yield(self.class::FINISHED, name)
+ end
+
+ def run_test(metric, mode)
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ @method_name }
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError => e
+ add_error(e)
+ ensure
+ begin
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError => e
+ add_error(e)
+ end
+ end
+ end
+
DEFAULTS =
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
{ :benchmark => true,
@@ -24,53 +112,10 @@ begin
:output => 'tmp/performance' }
end.freeze
- def self.included(base)
- base.superclass_delegating_accessor :profile_options
- base.profile_options = DEFAULTS
- end
-
def full_test_name
"#{self.class.name}##{method_name}"
end
- def run(result)
- return if method_name =~ /^default_test$/
-
- yield(self.class::STARTED, name)
- @_result = result
-
- run_warmup
- if profile_options && metrics = profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- result.add_run
- end
- end
- end
-
- yield(self.class::FINISHED, name)
- end
-
- def run_test(metric, mode)
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ @method_name }
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- end
- end
-
protected
def run_warmup
GC.start
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 690fc7f0fc..c2cf39e391 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -3,7 +3,7 @@ module ActiveSupport
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index cddfcddb57..6e12404ad4 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/string/inflections'
module ActiveSupport
# = XmlMini
@@ -138,7 +139,9 @@ module ActiveSupport
protected
def _dasherize(key)
- key.gsub(/(?!^[_]*)_(?![_]*$)/, '-')
+ # $2 must be a non-greedy regex for this to work
+ left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3]
+ "#{left}#{middle.tr('_ ', '--')}#{right}"
end
# TODO: Add support for other encodings
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb
index 8d1b1c02c6..21049d685b 100644
--- a/activesupport/test/buffered_logger_test.rb
+++ b/activesupport/test/buffered_logger_test.rb
@@ -2,6 +2,7 @@ require 'abstract_unit'
require 'multibyte_test_helpers'
require 'stringio'
require 'fileutils'
+require 'tempfile'
require 'active_support/buffered_logger'
class BufferedLoggerTest < Test::Unit::TestCase
@@ -16,6 +17,44 @@ class BufferedLoggerTest < Test::Unit::TestCase
@logger = Logger.new(@output)
end
+ def test_write_binary_data_to_existing_file
+ t = Tempfile.new ['development', 'log']
+ t.binmode
+ t.write 'hi mom!'
+ t.close
+
+ logger = Logger.new t.path
+ logger.level = Logger::DEBUG
+
+ str = "\x80"
+ if str.respond_to?(:force_encoding)
+ str.force_encoding("ASCII-8BIT")
+ end
+
+ logger.add Logger::DEBUG, str
+ logger.flush
+ ensure
+ logger.close
+ t.close true
+ end
+
+ def test_write_binary_data_create_file
+ fname = File.join Dir.tmpdir, 'lol', 'rofl.log'
+ logger = Logger.new fname
+ logger.level = Logger::DEBUG
+
+ str = "\x80"
+ if str.respond_to?(:force_encoding)
+ str.force_encoding("ASCII-8BIT")
+ end
+
+ logger.add Logger::DEBUG, str
+ logger.flush
+ ensure
+ logger.close
+ File.unlink fname
+ end
+
def test_should_log_debugging_message_when_debugging
@logger.level = Logger::DEBUG
@logger.add(Logger::DEBUG, @message)
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 8445af8d25..fc2d54515d 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -58,7 +58,7 @@ module ActiveSupport
assert @cache.key?(ClassCacheTest.name)
end
- def test_new_rejects_strings
+ def test_new_rejects_strings_when_called_on_a_new_string
assert_deprecated do
@cache.new ClassCacheTest.name
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 2b28e61815..c6d8191298 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -5,6 +5,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
class Parent
include ActiveSupport::Configurable
config_accessor :foo
+ config_accessor :bar, :instance_reader => false, :instance_writer => false
end
class Child < Parent
@@ -36,6 +37,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
assert_equal :bar, Parent.config.foo
end
+ test "configuration accessors is not available on instance" do
+ instance = Parent.new
+ assert !instance.respond_to?(:bar)
+ assert !instance.respond_to?(:bar=)
+ end
+
test "configuration hash is available on instance" do
instance = Parent.new
assert_equal :bar, instance.config.foo
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index c0b529d9f8..c8312aa653 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/inflector'
require 'active_support/time'
require 'active_support/json'
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 012b956d7f..4557a10688 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -16,6 +16,12 @@ class HashExtTest < Test::Unit::TestCase
class SubclassingHash < Hash
end
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
def setup
@strings = { 'a' => 1, 'b' => 2 }
@symbols = { :a => 1, :b => 2 }
@@ -109,9 +115,12 @@ class HashExtTest < Test::Unit::TestCase
assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
end
- def test_hash_subclass
- flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of SubclassingHash, flash["foo"]
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
end
def test_indifferent_assorted
@@ -897,7 +906,13 @@ class HashToXmlTest < Test::Unit::TestCase
hash = Hash.from_xml(xml)
assert_equal "bacon is the best", hash['blog']['name']
end
-
+
+ def test_empty_cdata_from_xml
+ xml = "<data><![CDATA[]]></data>"
+
+ assert_equal "", Hash.from_xml(xml)["data"]
+ end
+
def test_xsd_like_types_from_xml
bacon_xml = <<-EOT
<bacon>
@@ -940,7 +955,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
end
-
+
def test_should_use_default_value_for_unknown_key
hash_wia = HashWithIndifferentAccess.new(3)
assert_equal 3, hash_wia[:new_key]
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index f0c289a418..32675c884a 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -3,6 +3,7 @@ require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
+require 'active_support/inflector'
require 'active_support/core_ext/string'
require 'active_support/time'
require 'active_support/core_ext/kernel/reporting'
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 53d497013a..44e02109b1 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -808,4 +808,11 @@ class TimeExtMarshalingTest < Test::Unit::TestCase
assert_equal t.zone, unmarshaled.zone
assert_equal t, unmarshaled
end
+
+ def test_marshalling_preserves_fractional_seconds
+ t = Time.parse('00:00:00.500')
+ unmarshaled = Marshal.load(Marshal.dump(t))
+ assert_equal t.to_f, unmarshaled.to_f
+ assert_equal t, unmarshaled
+ end
end
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 79fb893592..066ec8549b 100644
--- a/activesupport/test/descendants_tracker_test.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -1,9 +1,4 @@
-require 'abstract_unit'
-require 'test/unit'
-require 'active_support'
-require 'active_support/core_ext/hash/slice'
-
-class DescendantsTrackerTest < Test::Unit::TestCase
+module DescendantsTrackerTestCases
class Parent
extend ActiveSupport::DescendantsTracker
end
@@ -34,7 +29,7 @@ class DescendantsTrackerTest < Test::Unit::TestCase
assert_equal [], Child2.direct_descendants
end
- def test_clear_with_autoloaded_parent_children_and_granchildren
+ def test_clear
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
@@ -43,35 +38,22 @@ class DescendantsTrackerTest < Test::Unit::TestCase
end
end
- def test_clear_with_autoloaded_children_and_granchildren
- mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
- ActiveSupport::DescendantsTracker.clear
- assert_equal [Child2], Parent.descendants
- assert_equal [], Child2.descendants
- end
- end
-
- def test_clear_with_autoloaded_granchildren
- mark_as_autoloaded Grandchild1, Grandchild2 do
- ActiveSupport::DescendantsTracker.clear
- assert_equal [Child1, Child2], Parent.descendants
- assert_equal [], Child1.descendants
- assert_equal [], Child2.descendants
- end
- end
-
protected
def mark_as_autoloaded(*klasses)
- old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
- ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+ # If ActiveSupport::Dependencies is not loaded, forget about autoloading.
+ # This allows using AS::DescendantsTracker without AS::Dependencies.
+ if defined? ActiveSupport::Dependencies
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+ end
old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup
old_descendants.each { |k, v| old_descendants[k] = v.dup }
yield
ensure
- ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded
+ ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies
ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants)
end
end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
new file mode 100644
index 0000000000..ae18a56f44
--- /dev/null
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -0,0 +1,35 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support/descendants_tracker'
+require 'active_support/dependencies'
+require 'descendants_tracker_test_cases'
+
+class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase
+ include DescendantsTrackerTestCases
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded(*ALL) do
+ ActiveSupport::DescendantsTracker.clear
+ ALL.each do |k|
+ assert ActiveSupport::DescendantsTracker.descendants(k).empty?
+ end
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
new file mode 100644
index 0000000000..1f0c32dc3f
--- /dev/null
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -0,0 +1,8 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support/descendants_tracker'
+require 'descendants_tracker_test_cases'
+
+class DescendantsTrackerWithoutAutoloadingTest < Test::Unit::TestCase
+ include DescendantsTrackerTestCases
+end \ No newline at end of file
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 2b144e5931..ec9d92794c 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -215,6 +215,36 @@ module InflectorTestCases
}
OrdinalNumbers = {
+ "-1" => "-1st",
+ "-2" => "-2nd",
+ "-3" => "-3rd",
+ "-4" => "-4th",
+ "-5" => "-5th",
+ "-6" => "-6th",
+ "-7" => "-7th",
+ "-8" => "-8th",
+ "-9" => "-9th",
+ "-10" => "-10th",
+ "-11" => "-11th",
+ "-12" => "-12th",
+ "-13" => "-13th",
+ "-14" => "-14th",
+ "-20" => "-20th",
+ "-21" => "-21st",
+ "-22" => "-22nd",
+ "-23" => "-23rd",
+ "-24" => "-24th",
+ "-100" => "-100th",
+ "-101" => "-101st",
+ "-102" => "-102nd",
+ "-103" => "-103rd",
+ "-104" => "-104th",
+ "-110" => "-110th",
+ "-111" => "-111th",
+ "-112" => "-112th",
+ "-113" => "-113th",
+ "-1000" => "-1000th",
+ "-1001" => "-1001st",
"0" => "0th",
"1" => "1st",
"2" => "2nd",
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index d5fcbf15b7..8cf1a54a99 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -215,6 +215,30 @@ class TestJSONEncoding < Test::Unit::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
+ people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ json = people.each.as_json :only => [:address, :city]
+ expected = [
+ { 'address' => { 'city' => 'London' }},
+ { 'address' => { 'city' => 'Paris' }}
+ ]
+
+ assert_equal(expected, json)
+ end
+
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
+ people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ json = people.each.to_json :only => [:address, :city]
+
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
+ end
+
def test_struct_encoding
Struct.new('UserNameAndEmail', :name, :email)
Struct.new('UserNameAndDate', :name, :date)
@@ -259,12 +283,3 @@ class TestJSONEncoding < Test::Unit::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
-
-class JsonOptionsTests < Test::Unit::TestCase
- def test_enumerable_should_passthrough_options_to_elements
- value, options = Object.new, Object.new
- def value.as_json(options) options end
- def options.encode_json(encoder) self end
- assert_equal options, ActiveSupport::JSON.encode(value, options)
- end
-end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 6ebbfdf334..bfff10fff2 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
+require 'active_support/core_ext/string/multibyte'
class String
def __method_for_multibyte_testing_with_integer_result; 1; end
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 50168fa78f..f3dcd7b068 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/json'
require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/hash/indifferent_access'
class OrderedHashTest < Test::Unit::TestCase
def setup
@@ -243,6 +244,11 @@ class OrderedHashTest < Test::Unit::TestCase
assert_equal @other_ordered_hash.keys, @ordered_hash.keys
end
+ def test_nested_under_indifferent_access
+ flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access
+ assert_kind_of ActiveSupport::OrderedHash, flash[:a]
+ end
+
def test_each_after_yaml_serialization
values = []
@deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/xml_mini_test.rb
index 6dbcd1f40b..e2b90ae16e 100644
--- a/activesupport/test/test_xml_mini.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -16,14 +16,6 @@ module XmlMiniTest
assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
end
- def test_rename_key_camelizes_with_camelize_false
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false)
- end
-
- def test_rename_key_camelizes_with_camelize_nil
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil)
- end
-
def test_rename_key_camelizes_with_camelize_true
assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
end
@@ -56,7 +48,7 @@ module XmlMiniTest
assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id")
end
- def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ def test_rename_key_does_not_dasherize_multiple_trailing_underscores
assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
end
end
@@ -95,6 +87,16 @@ module XmlMiniTest
@xml.to_tag(:b, "Howdy", @options)
assert_xml "<b>Howdy</b>"
end
+
+ test "#to_tag should dasherize the space when passed a string with spaces as a key" do
+ @xml.to_tag("New York", 33, @options)
+ assert_xml "<New---York type=\"integer\">33</New---York>"
+ end
+
+ test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do
+ @xml.to_tag(:"New York", 33, @options)
+ assert_xml "<New---York type=\"integer\">33</New---York>"
+ end
# TODO: test the remaining functions hidden in #to_tag.
end
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 0def5bcf6c..c465b08594 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,19 @@
*Rails 3.1.0 (unreleased)*
+* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn]
+
+* Fixed database tasks for jdbc* adapters #jruby
+
+ [Rashmi Yadav]
+
+* Template generation for jdbcpostgresql #jruby
+
+ [Vishnu Atrai]
+
+* Template generation for jdbcmysql and jdbcsqlite3 #jruby
+
+ [Arun Agrawal]
+
* The -j option of the application generator accepts an arbitrary string. If passed "foo",
the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest
requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails"
@@ -16,7 +30,7 @@ by the prototype-rails gem. [fxn]
* jQuery is the new default JavaScript library. [fxn]
-* Changed scaffold and app generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist]
+* Changed scaffold, application, and mailer generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist]
So instead of creating something like:
@@ -62,10 +76,42 @@ by the prototype-rails gem. [fxn]
* Include all helpers from plugins and shared engines in application [Piotr Sarnacki]
+
+*Rails 3.0.7 (April 18, 2011)*
+
+*No changes.
+
+
+*Rails 3.0.6 (April 5, 2011)
+
+* No changes.
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* No changes.
+
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* No changes.
+
+
+*Rails 3.0.2 (November 15, 2010)*
+
+* No changes.
+
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit and --skip-active-record respectively. [fxn]
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 8f98c71aa0..66869b4eeb 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -417,6 +417,14 @@ silence_stream(STDOUT) do
end
</ruby>
+The +quietly+ method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses:
+
+<ruby>
+quietly { system 'bundle install' }
+</ruby>
+
+For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status.
+
Silencing exceptions is also possible with +suppress+. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is +kind_of?+ any of the arguments, +suppress+ captures it and returns silently. Otherwise the exception is reraised:
<ruby>
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 8c408ec06b..e0ccc7a6e6 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -372,7 +372,7 @@ def signup
end
</ruby>
-Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
+Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the +new+ method, or +assign_attributes=+ a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
<pre>
"name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
@@ -386,7 +386,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true}
So if you create a new user using mass-assignment, it may be too easy to become an administrator.
-Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
+Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
<ruby>
class Person < ActiveRecord::Base
@@ -410,7 +410,7 @@ To avoid this, Rails provides two class methods in your Active Record class to c
attr_protected :admin
</ruby>
-+attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group.
++attr_protected+ also optionally takes a role option using :as which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the :default role.
<ruby>
attr_protected :last_login, :as => :admin
@@ -433,7 +433,7 @@ params[:user] # => {:name => "ow3ned", :admin => true}
@user.admin # => true
</ruby>
-When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
+When assigning attributes in Active Record using +attributes=+ the :default role will be used. To assign attributes using different roles you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default role will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
<ruby>
@user = User.new
@@ -451,7 +451,7 @@ When assigning attributes in Active Record using +attributes=+, or +update_attri
@user.is_admin # => true
</ruby>
-In a similar way, +new+, +create+ and <tt>create!</tt> methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example:
+In a similar way, +new+, +create+, <tt>create!</tt>, +update_attributes+, and +update_attributes!+ methods all respect mass-assignment security and accept either +:as+ or +:without_protection+ options. For example:
<ruby>
@user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin)
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index 01badebd41..7a93c3a1e6 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -54,7 +54,7 @@ For good tests, you'll need to give some thought to setting up test data. In Rai
h5. What are Fixtures?
-_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide, we will use *YAML*, which is the preferred format.
+_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume a single format: *YAML*.
You'll find fixtures under your +test/fixtures+ directory. When you run +rails generate model+ to create a new model, fixture stubs will be automatically created and placed in this directory.
@@ -81,7 +81,7 @@ Each fixture is given a name followed by an indented list of colon-separated key
h5. ERB'in It Up
-ERB allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data.
+ERB allows you to embed ruby code within templates. YAML fixture format is pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data.
<erb>
<% earth_size = 20 %>
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index dd01bbab1d..1e4d25f18c 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -157,7 +157,8 @@ module Rails
middleware.use ::Rack::Lock unless config.allow_concurrency
middleware.use ::Rack::Runtime
- middleware.use ::Rails::Rack::Logger
+ middleware.use ::Rack::MethodOverride
+ middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
@@ -171,7 +172,6 @@ module Rails
end
middleware.use ::ActionDispatch::ParamsParser
- middleware.use ::Rack::MethodOverride
middleware.use ::ActionDispatch::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
@@ -199,4 +199,4 @@ module Rails
require "rails/console/helpers"
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9c9d85eed6..9f21d273e6 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/dependencies"
require "active_support/descendants_tracker"
module Rails
@@ -9,7 +10,6 @@ module Rails
initializer :load_environment_hook do end
initializer :load_active_support do
- require 'active_support/dependencies'
require "active_support/all" unless config.active_support.bare
end
@@ -37,6 +37,7 @@ module Rails
)
logger
end
+ at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) }
end
# Initialize cache early in the stack so railties can make use of it.
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 29b9c27a13..3b74de690a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -34,7 +34,7 @@ module Rails
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
@assets.paths = []
- @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ]
+ @assets.precompile = [ /\w+\.(?!js|css).+/, "application.js", "application.css" ]
@assets.prefix = "/assets"
@assets.js_compressor = nil
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 4182757346..4a082aedb8 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -6,7 +6,8 @@ aliases = {
"g" => "generate",
"c" => "console",
"s" => "server",
- "db" => "dbconsole"
+ "db" => "dbconsole",
+ "r" => "runner"
}
command = ARGV.shift
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index f0d6ea1687..b0ba76217a 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -23,7 +23,7 @@ module Rails
include_password = false
options = {}
OptionParser.new do |opt|
- opt.banner = "Usage: dbconsole [options] [environment]"
+ opt.banner = "Usage: dbconsole [environment] [options]"
opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
include_password = true
end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 1a91d477ec..f8b00e7249 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -25,7 +25,7 @@ ARGV.clone.options do |opts|
opts.separator "-------------------------------------------------------------"
opts.separator "#!/usr/bin/env #{File.expand_path($0)} runner"
opts.separator ""
- opts.separator "Product.find(:all).each { |p| p.price *= 2 ; p.save! }"
+ opts.separator "Product.all.each { |p| p.price *= 2 ; p.save! }"
opts.separator "-------------------------------------------------------------"
end
@@ -39,18 +39,12 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
-begin
- if code_or_file.nil?
- $stderr.puts "Run '#{$0} -h' for help."
- exit 1
- elsif File.exist?(code_or_file)
- $0 = code_or_file
- eval(File.read(code_or_file), nil, code_or_file)
- else
- eval(code_or_file)
- end
-ensure
- if defined? Rails
- Rails.logger.flush if Rails.logger.respond_to?(:flush)
- end
+if code_or_file.nil?
+ $stderr.puts "Run '#{$0} -h' for help."
+ exit 1
+elsif File.exist?(code_or_file)
+ $0 = code_or_file
+ eval(File.read(code_or_file), nil, code_or_file)
+else
+ eval(code_or_file)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e447209242..505a4ca2bd 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -55,8 +55,9 @@ module Rails
end
def start
+ url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Call with -d to detach" unless options[:daemonize]
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 2015a944f0..6a125685d0 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -523,6 +523,7 @@ module Rails
initializer :append_assets_path do |app|
app.config.assets.paths.unshift *paths["vendor/assets"].existent
+ app.config.assets.paths.unshift *paths["lib/assets"].existent
app.config.assets.paths.unshift *paths["app/assets"].existent
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 241db4b0a9..f424492bb4 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -47,6 +47,7 @@ module Rails
paths.add "app/mailers", :eager_load => true
paths.add "app/views"
paths.add "lib", :load_path => true
+ paths.add "lib/assets", :glob => "*"
paths.add "lib/tasks", :glob => "**/*.rake"
paths.add "config"
paths.add "config/environments", :glob => "#{Rails.env}.rb"
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 9be395e989..85c67af19a 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -52,13 +52,13 @@ module Rails
:integration_tool => nil,
:javascripts => true,
:javascript_engine => nil,
- :orm => nil,
+ :orm => false,
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
:stylesheet_engine => nil,
- :test_framework => nil,
+ :test_framework => false,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 998957f313..a5743762e5 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -28,6 +28,9 @@ module Rails
class_option :skip_gemfile, :type => :boolean, :default => false,
:desc => "Don't create a Gemfile"
+ class_option :skip_bundle, :type => :boolean, :default => false,
+ :desc => "Don't run bundle install"
+
class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false,
:desc => "Skip Git ignores and keeps"
@@ -117,15 +120,15 @@ module Rails
end
def database_gemfile_entry
- entry = options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
- if options[:database] == 'mysql'
- if options.dev? || options.edge?
- entry += ", :git => 'git://github.com/brianmario/mysql2.git'"
- else
- entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'"
- end
- end
- entry + "\n"
+ options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n"
+ end
+
+ def include_all_railties?
+ !options[:skip_active_record] && !options[:skip_test_unit]
+ end
+
+ def comment_if(value)
+ options[value] ? '#' : ''
end
def rails_gemfile_entry
@@ -184,13 +187,18 @@ module Rails
"gem '#{options[:javascript]}-rails'" unless options[:skip_javascript]
end
- def bundle_if_dev_or_edge
- bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
- run "#{bundle_command} install" if dev_or_edge?
+ def bundle_command(command)
+ require 'bundler'
+ require 'bundler/cli'
+
+ say_status :run, "bundle #{command}"
+ Bundler::CLI.new.send(command)
+ rescue
+ say_status :failure, "bundler raised an exception, are you offline?", :red
end
- def dev_or_edge?
- options.dev? || options.edge?
+ def run_bundle
+ bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle]
end
def empty_directory_with_gitkeep(destination, config = {})
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 36bc9e055c..cf317eb21f 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/introspection'
require 'rails/generators/base'
require 'rails/generators/generated_attribute'
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 6562667782..5f9fb9685c 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -9,11 +9,20 @@ module Rails
@options = generator.options
end
- private
+ private
+ %w(template copy_file directory empty_directory inside
+ empty_directory_with_gitkeep create_file chmod shebang).each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ @generator.send(:#{method}, *args, &block)
+ end
+ RUBY
+ end
- def method_missing(meth, *args, &block)
- @generator.send(meth, *args, &block)
- end
+ # TODO: Remove once this is fully in place
+ def method_missing(meth, *args, &block)
+ @generator.send(meth, *args, &block)
+ end
end
# The application builder allows you to override elements of the application
@@ -215,7 +224,7 @@ module Rails
build(:leftovers)
end
- public_task :apply_rails_template, :bundle_if_dev_or_edge
+ public_task :apply_rails_template, :run_bundle
protected
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b046edd5b7..20bd9db624 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -4,11 +4,12 @@ source 'http://rubygems.org'
<%= database_gemfile_entry -%>
+<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6" -%>
# Asset template engines
<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
gem 'sass'
gem 'coffee-script'
-gem 'uglifier'
+# gem 'uglifier'
<%= gem_for_javascript %>
@@ -21,4 +22,4 @@ gem 'uglifier'
# To use debugger
# <%= gem_for_ruby_debugger %>
-<%= gem_for_turn -%> \ No newline at end of file
+<%= gem_for_turn -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index 9f0f1d0e38..7c36f2356e 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -156,6 +156,10 @@ PostgreSQL and SQLite 3.
The default directory structure of a generated Ruby on Rails application:
|-- app
+ | |-- assets
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
| |-- controllers
| |-- helpers
| |-- mailers
@@ -172,9 +176,6 @@ The default directory structure of a generated Ruby on Rails application:
| `-- tasks
|-- log
|-- public
- | |-- images
- | |-- javascripts
- | `-- stylesheets
|-- script
|-- test
| |-- fixtures
@@ -188,11 +189,16 @@ The default directory structure of a generated Ruby on Rails application:
| |-- sessions
| `-- sockets
`-- vendor
+ |-- assets
+ `-- stylesheets
`-- plugins
app
Holds all the code that's specific to this particular application.
+app/assets
+ Contains subdirectories for images, stylesheets, and JavaScript files.
+
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from
@@ -237,8 +243,7 @@ lib
the load path.
public
- The directory available for the web server. Contains subdirectories for
- images, stylesheets, and javascripts. Also contains the dispatchers and the
+ The directory available for the web server. Also contains the dispatchers and the
default HTML files. This should be set as the DOCUMENT_ROOT of your web
server.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index 612c614f2e..19294b3478 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -1,5 +1,8 @@
-// FIXME: Tell people that this is a manifest file, real code should go into discrete files
-// FIXME: Tell people how Sprockets and CoffeeScript works
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
//
<% unless options[:skip_javascript] -%>
//= require <%= options[:javascript] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index f4b082ccc0..fc25b5723f 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -1,5 +1,7 @@
/*
- * FIXME: Introduce SCSS & Sprockets
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require_self
*= require_tree .
*/ \ No newline at end of file
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 3723addf2b..8ff80c6fd3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,14 +1,14 @@
require File.expand_path('../boot', __FILE__)
-<% unless options[:skip_active_record] -%>
+<% if include_all_railties? -%>
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
-# require "active_record/railtie"
+<%= comment_if :skip_active_record %> require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
-require "rails/test_unit/railtie"
+<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie"
<% end -%>
# If you have a Gemfile, require the gems listed there, including any gems
@@ -50,21 +50,12 @@ module <%= app_const_base %>
# config.action_view.javascript_expansions[:defaults] = %w(prototype prototype_ujs)
<% end -%>
-<% if options[:skip_test_unit] -%>
- config.generators.test_framework = false
-<% end -%>
-
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
-<% unless options[:skip_active_record] -%>
- # Enable IdentityMap for Active Record, to disable set to false or remove the line below.
- config.active_record.identity_map = true
-<% end -%>
-
# Enable the asset pipeline
config.assets.enabled = true
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 41b2374eda..066aa54862 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -22,4 +22,3 @@
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
end
-
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index 32ffbee7a1..e56195da80 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -4,7 +4,7 @@
# which will be enabled by default in the upcoming version of Ruby on Rails.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
-ActionController::Base.wrap_parameters :format => [:json]
+ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %>
# Disable root element in JSON by default.
if defined?(ActiveRecord)
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index f0fa30c536..923b697662 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -2,3 +2,4 @@
db/*.sqlite3
log/*.log
tmp/
+.sass-cache/
diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb
index 80beb7abfe..2d52da77eb 100644
--- a/railties/lib/rails/generators/rails/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb
@@ -1,11 +1,11 @@
module Rails
module Generators
class AssetsGenerator < NamedBase
- class_option :javascripts, :type => :boolean, :desc => "Generate javascripts"
- class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
+ class_option :javascripts, :type => :boolean, :desc => "Generate JavaScripts"
+ class_option :stylesheets, :type => :boolean, :desc => "Generate Stylesheets"
- class_option :javascript_engine, :desc => "Engine for javascripts"
- class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+ class_option :javascript_engine, :desc => "Engine for JavaScripts"
+ class_option :stylesheet_engine, :desc => "Engine for Stylesheets"
def create_javascript_files
return unless options.javascripts?
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 6201595308..939c0cd727 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -209,7 +209,7 @@ task :default => :test
build(:leftovers)
end
- public_task :apply_rails_template, :bundle_if_dev_or_edge
+ public_task :apply_rails_template, :run_bundle
protected
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 6eef0dbe5b..aa9b45c5a5 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -6,8 +6,8 @@ module Rails
remove_hook_for :resource_controller
remove_class_option :actions
- class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
- class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+ class_option :stylesheets, :type => :boolean, :desc => "Generate Stylesheets"
+ class_option :stylesheet_engine, :desc => "Engine for Stylesheets"
hook_for :scaffold_controller, :required => true
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 5222b7bf50..77a5c4dc6c 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -1,6 +1,6 @@
namespace :rails do
- desc "Update both configs and public/javascripts from Rails (or use just update:javascripts or update:configs)"
- task :update => [ "update:configs", "update:javascripts", "update:scripts", "update:application_controller" ]
+ desc "Update configs and some other initially generated files (or use just update:configs, update:scripts, or update:application_controller)"
+ task :update => [ "update:configs", "update:scripts", "update:application_controller" ]
desc "Applies the template supplied by LOCATION=/path/to/template"
task :template do
@@ -58,11 +58,6 @@ namespace :rails do
invoke_from_app_generator :create_config_files
end
- # desc "Update Prototype javascripts from your current rails install"
- task :javascripts do
- invoke_from_app_generator :create_javascript_files
- end
-
# desc "Adds new scripts to the application script/ directory"
task :scripts do
invoke_from_app_generator :create_script_files
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 02e22361e0..a0c953967c 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -16,7 +16,8 @@ task :routes => :environment do
{:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
end
- routes.reject! { |r| r[:path] =~ %r{/rails/info/properties} } # Skip the route if it's internal info route
+ # Skip the route if it's internal info route
+ routes.reject! { |r| r[:path] =~ %r{/rails/info/properties|^/assets} }
name_width = routes.map{ |r| r[:name].length }.max
verb_width = routes.map{ |r| r[:verb].length }.max
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index b076881c21..fc6c0a0204 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index cd0646b8ed..4404838670 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '~> 0.14.4')
+ s.add_dependency('thor', '~> 0.14.6')
s.add_dependency('rack-ssl', '~> 1.3.2')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
new file mode 100644
index 0000000000..b03dc3132b
--- /dev/null
+++ b/railties/test/application/assets_test.rb
@@ -0,0 +1,27 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class RoutingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ test "assets routes have higher priority" do
+ app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();"
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
+ end
+ RUBY
+
+ get "/assets/demo.js"
+ assert_match "alert()", last_response.body
+ end
+ end
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index b1f7076776..43876c0a72 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -12,7 +12,6 @@ end
class ::MyOtherMailObserver < ::MyMailObserver; end
-
module ApplicationTests
class ConfigurationTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -226,8 +225,6 @@ module ApplicationTests
make_basic_app
class ::OmgController < ActionController::Base
- protect_from_forgery
-
def index
render :inline => "<%= csrf_meta_tags %>"
end
@@ -237,6 +234,21 @@ module ApplicationTests
assert last_response.body =~ /csrf\-param/
end
+ test "request forgery token param can be changed" do
+ make_basic_app do
+ app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
+ end
+
+ class ::OmgController < ActionController::Base
+ def index
+ render :inline => "<%= csrf_meta_tags %>"
+ end
+ end
+
+ get "/"
+ assert last_response.body =~ /_xsrf_token_here/
+ end
+
test "config.action_controller.perform_caching = true" do
make_basic_app do |app|
app.config.action_controller.perform_caching = true
@@ -437,10 +449,35 @@ module ApplicationTests
app_file 'config/initializers/wrap_parameters.rb', <<-RUBY
ActionController::Base.wrap_parameters :format => [:json]
RUBY
+
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ def self.column_names
+ %w(title)
+ end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render :text => params[:post].inspect
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.append do
+ resources :posts
+ end
+ RUBY
+
require "#{app_path}/config/environment"
- require 'action_controller/base'
+ require "rack/test"
+ extend Rack::Test::Methods
- assert_equal [:json], ActionController::Base._wrapper_options[:format]
+ post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json"
+ assert_equal '{"title"=>"foo"}', last_response.body
end
test "config.action_dispatch.ignore_accept_header" do
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 19311a7fa0..196d121c14 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -166,7 +166,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- expects = [ActiveRecord::IdentityMap::Middleware, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
middleware = Rails.application.config.middleware.map { |m| m.klass }
assert_equal expects, middleware & expects
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 01e6c49d9c..8bfde00af5 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -23,20 +23,19 @@ module ApplicationTests
"Rack::Lock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
- "Rails::Rack::Logger",
+ "Rack::MethodOverride",
+ "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
"ActionDispatch::ShowExceptions",
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
- "ActiveRecord::IdentityMap::Middleware",
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
"ActionDispatch::ParamsParser",
- "Rack::MethodOverride",
"ActionDispatch::Head",
"Rack::ConditionalGet",
"Rack::ETag",
@@ -121,6 +120,7 @@ module ApplicationTests
end
test "identity map is inserted" do
+ add_to_config "config.active_record.identity_map = true"
boot!
assert middleware.include?("ActiveRecord::IdentityMap::Middleware")
end
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
new file mode 100644
index 0000000000..a29244357c
--- /dev/null
+++ b/railties/test/application/rack/logger_test.rb
@@ -0,0 +1,40 @@
+require "isolation/abstract_unit"
+require "active_support/log_subscriber/test_helper"
+require "rack/test"
+
+module ApplicationTests
+ module RackTests
+ class LoggerTest < Test::Unit::TestCase
+ include ActiveSupport::LogSubscriber::TestHelper
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ require "#{app_path}/config/environment"
+ super
+ end
+
+ def logs
+ @logs ||= @logger.logged(:info)
+ end
+
+ test "logger logs proper HTTP verb and path" do
+ get "/blah"
+ wait
+ assert_match /^Started GET "\/blah"/, logs[0]
+ end
+
+ test "logger logs HTTP verb override" do
+ post "/", {:_method => 'put'}
+ wait
+ assert_match /^Started PUT "\/"/, logs[0]
+ end
+
+ test "logger logs HEAD requests" do
+ post "/", {:_method => 'head'}
+ wait
+ assert_match /^Started HEAD "\/"/, logs[0]
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 59e5ef4dee..d77c2d14ab 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -64,6 +64,28 @@ module ApplicationTests
assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` }
end
+ def test_rake_routes_shows_custom_assets
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/custom/assets', :to => 'custom_assets#show'
+ end
+ RUBY
+ assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_logger_is_flushed_when_exiting_production_rake_tasks
+ add_to_config <<-RUBY
+ rake_tasks do
+ task :log_something => :environment do
+ Rails.logger.error("Sample log message")
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `rake log_something RAILS_ENV=production && cat log/production.log` }
+ assert_match "Sample log message", output
+ end
+
def test_model_and_migration_generator_with_change_syntax
Dir.chdir(app_path) do
`rails generate model user username:string password:string`
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 62589c998d..e3a7f8a63c 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -1,14 +1,14 @@
require 'isolation/abstract_unit'
+require 'rack/test'
module ApplicationTests
class RoutingTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
def setup
build_app
boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
end
test "rails/info/properties in development" do
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 1fbbb40132..f96319f472 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -65,6 +65,31 @@ module ApplicationTests
run_test 'integration/posts_test.rb'
end
+ test "performance test" do
+ controller 'posts', <<-RUBY
+ class PostsController < ActionController::Base
+ end
+ RUBY
+
+ app_file 'app/views/posts/index.html.erb', <<-HTML
+ Posts#index
+ HTML
+
+ app_file 'test/performance/posts_test.rb', <<-RUBY
+ require 'test_helper'
+ require 'rails/performance_test_help'
+
+ class PostsTest < ActionDispatch::PerformanceTest
+ def test_index
+ get '/posts'
+ assert_response :success
+ end
+ end
+ RUBY
+
+ run_test 'performance/posts_test.rb'
+ end
+
private
def run_test(name)
result = ruby '-Itest', "#{app_path}/test/#{name}"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 81263a6ce9..9e1d47cd2f 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -39,6 +39,8 @@ DEFAULT_APP_FILES = %w(
class AppGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
@@ -64,8 +66,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_application_new_exits_with_non_zero_code_on_invalid_application_name
- # TODO: Suppress the output of this (it's because of a Thor::Error)
- `rails new test`
+ quietly { system 'rails new test' }
assert_equal false, $?.success?
end
@@ -95,7 +96,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true },
:destination_root => app_moved_root, :shell => @shell
generator.send(:app_const)
- silence(:stdout){ generator.send(:create_config_files) }
+ quietly { generator.send(:create_config_files) }
assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/
assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
end
@@ -110,7 +111,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, :destination_root => app_root, :shell => @shell
generator.send(:app_const)
- silence(:stdout){ generator.send(:create_config_files) }
+ quietly { generator.send(:create_config_files) }
assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
end
@@ -135,6 +136,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /jdbcmysql/
assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_file "Gemfile", /^gem\s+["']jruby-openssl["']$/ if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6"
end
def test_config_jdbcsqlite3_database
@@ -226,9 +228,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given
run_generator [destination_root, "--skip-test-unit"]
- assert_file "config/application.rb" do |file|
- assert_match /config.generators.test_framework = false/, file
- end
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ end
+
+ def test_no_active_record_or_test_unit_if_skips_given
+ run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
def test_new_hash_style
@@ -252,7 +258,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
protected
def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
+ silence(:stdout) { generator.send(*args, &block) }
end
end
@@ -278,6 +284,6 @@ protected
end
def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
+ silence(:stdout) { generator.send(*args, &block) }
end
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index f4fdc46328..bf1cfe5305 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -10,7 +10,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "app/mailers/notifier.rb" do |mailer|
assert_match /class Notifier < ActionMailer::Base/, mailer
- assert_match /default :from => "from@example.com"/, mailer
+ if RUBY_VERSION < "1.9"
+ assert_match /default :from => "from@example.com"/, mailer
+ else
+ assert_match /default from: "from@example.com"/, mailer
+ end
end
end
@@ -73,15 +77,33 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_file "app/mailers/notifier.rb" do |mailer|
assert_instance_method :foo, mailer do |foo|
- assert_match /mail :to => "to@example.org"/, foo
+ if RUBY_VERSION < "1.9"
+ assert_match /mail :to => "to@example.org"/, foo
+ else
+ assert_match /mail to: "to@example.org"/, foo
+ end
assert_match /@greeting = "Hi"/, foo
end
assert_instance_method :bar, mailer do |bar|
- assert_match /mail :to => "to@example.org"/, bar
+ if RUBY_VERSION < "1.9"
+ assert_match /mail :to => "to@example.org"/, bar
+ else
+ assert_match /mail to: "to@example.org"/, bar
+ end
assert_match /@greeting = "Hi"/, bar
end
end
+ end
+ def test_force_old_style_hash
+ run_generator ["notifier", "foo", "--old-style-hash"]
+ assert_file "app/mailers/notifier.rb" do |mailer|
+ assert_match /default :from => "from@example.com"/, mailer
+
+ assert_instance_method :foo, mailer do |foo|
+ assert_match /mail :to => "to@example.org"/, foo
+ end
+ end
end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index eb56e8d1a4..38f024f061 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -163,7 +163,11 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
assert_file "app/mailers/test_app/notifier.rb" do |mailer|
assert_match /module TestApp/, mailer
assert_match /class Notifier < ActionMailer::Base/, mailer
- assert_match /default :from => "from@example.com"/, mailer
+ if RUBY_VERSION < "1.9"
+ assert_match /default :from => "from@example.com"/, mailer
+ else
+ assert_match /default from: "from@example.com"/, mailer
+ end
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 33c8d83f9c..2af728e766 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -22,6 +22,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
destination File.join(Rails.root, "tmp/bukkits")
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
@@ -117,17 +119,17 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
end
- def test_ensure_that_tests_works
+ def test_ensure_that_tests_work
run_generator
FileUtils.cd destination_root
- `bundle install`
+ quietly { system 'bundle install' }
assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
def test_ensure_that_tests_works_in_full_mode
run_generator [destination_root, "--full", "--skip_active_record"]
FileUtils.cd destination_root
- `bundle install`
+ quietly { system 'bundle install' }
assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index c9c5d2fad2..be9aef8a41 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -1,9 +1,11 @@
+#
+# Tests, setup, and teardown common to the application and plugin generator suites.
+#
module SharedGeneratorTests
def setup
Rails.application = TestApp::Application
super
Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
- @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
Kernel::silence_warnings do
Thor::Base.shell.send(:attr_accessor, :always_force)
@@ -21,7 +23,12 @@ module SharedGeneratorTests
def test_skeleton_is_created
run_generator
- default_files.each{ |path| assert_file path }
+ default_files.each { |path| assert_file path }
+ end
+
+ def test_generation_runs_bundle_install
+ generator([destination_root]).expects(:bundle_command).with('install').once
+ quietly { generator.invoke_all }
end
def test_plugin_new_generate_pretend
@@ -81,20 +88,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
- end
-
- def test_dev_option
- generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke_all }
- rails_path = File.expand_path('../../..', Rails.root)
- assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
- end
-
- def test_edge_option
- generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke_all }
- assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$}
+ assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
end
def test_template_raises_an_error_with_invalid_path
@@ -109,7 +103,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
+ assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
end
def test_template_is_executed_when_supplied_an_https_path
@@ -118,21 +112,36 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
+ assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
end
def test_dev_option
- generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke_all }
+ generator([destination_root], :dev => true).expects(:bundle_command).with('install').once
+ quietly { generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
- generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke_all }
+ generator([destination_root], :edge => true).expects(:bundle_command).with('install').once
+ quietly { generator.invoke_all }
assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$}
end
+
+ def test_skip_gemfile
+ generator([destination_root], :skip_gemfile => true).expects(:bundle_command).never
+ quietly { generator.invoke_all }
+ assert_no_file 'Gemfile'
+ end
+
+ def test_skip_bundle
+ generator([destination_root], :skip_bundle => true).expects(:bundle_command).never
+ quietly { generator.invoke_all }
+
+ # skip_bundle is only about running bundle install, ensure the Gemfile is still
+ # generated.
+ assert_file 'Gemfile'
+ end
end
module SharedCustomGeneratorTests
@@ -140,7 +149,6 @@ module SharedCustomGeneratorTests
Rails.application = TestApp::Application
super
Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
- @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
end
def teardown
@@ -188,7 +196,7 @@ module SharedCustomGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- capture(:stdout) { generator.invoke_all }
+ quietly { generator.invoke_all }
default_files.each{ |path| assert_no_file(path) }
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 0c588ba773..b5b21f9ebe 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -93,6 +93,34 @@ module RailtiesTest
assert_equal "HELLO WORLD", last_response.body
end
+ test "pass the value of the segment" do
+ controller "foo", <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ render :text => params[:username]
+ end
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ root :to => "foo#index"
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ mount(Bukkits::Engine => "/:username")
+ end
+ RUBY
+
+ boot_rails
+
+ get("/arunagw")
+ assert_equal "arunagw", last_response.body
+
+ end
+
test "it provides routes as default endpoint" do
@plugin.write "lib/bukkits.rb", <<-RUBY
class Bukkits
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index e975950b85..e5debf29ae 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -15,11 +15,10 @@ module RailtiesTest
boot_rails
require 'rack/test'
- require 'coffee_script'
extend Rack::Test::Methods
get "/assets/engine.js"
- assert_match "alert();", last_response.body
+ assert_match "alert()", last_response.body
end
def test_copying_migrations
diff --git a/version.rb b/version.rb
index b076881c21..fc6c0a0204 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end