aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrick <technoweenie@gmail.com>2008-09-20 13:00:20 -0700
committerrick <technoweenie@gmail.com>2008-09-20 13:00:20 -0700
commit22e830f883af0b56de81186c184751b6398d0141 (patch)
tree0de20fad9f3a7ce2e49d660d1243b5b02a32e290
parent0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2 (diff)
parenta3b7fa78bfdc33e45e39c095b67e02d50a2c7bea (diff)
downloadrails-22e830f883af0b56de81186c184751b6398d0141.tar.gz
rails-22e830f883af0b56de81186c184751b6398d0141.tar.bz2
rails-22e830f883af0b56de81186c184751b6398d0141.zip
Merge branch 'master' of git@github.com:rails/rails
-rw-r--r--actionmailer/CHANGELOG5
-rw-r--r--actionmailer/lib/action_mailer/base.rb47
-rw-r--r--actionmailer/lib/action_mailer/helpers.rb4
-rw-r--r--actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb1
-rw-r--r--actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb1
-rw-r--r--actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb1
-rw-r--r--actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb1
-rw-r--r--actionmailer/test/fixtures/layouts/spam.html.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.erb~10
-rw-r--r--actionmailer/test/mail_layout_test.rb78
-rw-r--r--actionmailer/test/mail_service_test.rb52
-rw-r--r--actionpack/CHANGELOG46
-rw-r--r--actionpack/Rakefile14
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/assertions/routing_assertions.rb28
-rw-r--r--actionpack/lib/action_controller/assertions/selector_assertions.rb86
-rw-r--r--actionpack/lib/action_controller/base.rb106
-rw-r--r--actionpack/lib/action_controller/benchmarking.rb61
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb8
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb6
-rw-r--r--actionpack/lib/action_controller/cgi_process.rb9
-rw-r--r--actionpack/lib/action_controller/components.rb5
-rw-r--r--actionpack/lib/action_controller/filters.rb8
-rw-r--r--actionpack/lib/action_controller/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/integration.rb8
-rw-r--r--actionpack/lib/action_controller/layout.rb80
-rw-r--r--actionpack/lib/action_controller/mime_type.rb2
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb6
-rw-r--r--actionpack/lib/action_controller/rack_process.rb3
-rwxr-xr-x[-rw-r--r--]actionpack/lib/action_controller/request.rb77
-rw-r--r--actionpack/lib/action_controller/rescue.rb5
-rw-r--r--actionpack/lib/action_controller/resources.rb150
-rw-r--r--actionpack/lib/action_controller/response.rb2
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb12
-rw-r--r--actionpack/lib/action_controller/routing/optimisations.rb5
-rw-r--r--actionpack/lib/action_controller/routing/recognition_optimisation.rb22
-rw-r--r--actionpack/lib/action_controller/routing/route.rb2
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_controller/routing/segments.rb6
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb3
-rw-r--r--actionpack/lib/action_controller/session_management.rb8
-rw-r--r--actionpack/lib/action_controller/test_process.rb19
-rw-r--r--actionpack/lib/action_controller/translation.rb13
-rw-r--r--actionpack/lib/action_controller/verification.rb4
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/base.rb192
-rw-r--r--actionpack/lib/action_view/helpers.rb1
-rw-r--r--actionpack/lib/action_view/helpers/active_record_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_country_helper.rb92
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb31
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb11
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb56
-rw-r--r--actionpack/lib/action_view/partials.rb2
-rw-r--r--actionpack/lib/action_view/renderable.rb27
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb26
-rw-r--r--actionpack/lib/action_view/template.rb2
-rw-r--r--actionpack/lib/action_view/template_handlers/builder.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb2
-rw-r--r--actionpack/test/controller/assert_select_test.rb49
-rw-r--r--actionpack/test/controller/base_test.rb8
-rw-r--r--actionpack/test/controller/components_test.rb66
-rw-r--r--actionpack/test/controller/filter_params_test.rb4
-rw-r--r--actionpack/test/controller/filters_test.rb26
-rw-r--r--actionpack/test/controller/integration_test.rb2
-rw-r--r--actionpack/test/controller/layout_test.rb49
-rw-r--r--actionpack/test/controller/new_render_test.rb981
-rw-r--r--actionpack/test/controller/render_test.rb898
-rw-r--r--actionpack/test/controller/request_test.rb3
-rw-r--r--actionpack/test/controller/resources_test.rb147
-rw-r--r--actionpack/test/controller/routing_test.rb59
-rw-r--r--actionpack/test/controller/session/cookie_store_test.rb53
-rw-r--r--actionpack/test/controller/test_test.rb5
-rw-r--r--actionpack/test/controller/translation_test.rb26
-rw-r--r--actionpack/test/template/form_country_helper_test.rb1549
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb11
-rw-r--r--actionpack/test/template/prototype_helper_test.rb15
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb8
-rw-r--r--actionpack/test/template/text_helper_test.rb34
-rw-r--r--activemodel/lib/active_model/state_machine/event.rb6
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/uniqueness.rb8
-rw-r--r--activerecord/CHANGELOG34
-rw-r--r--activerecord/lib/active_record.rb6
-rw-r--r--activerecord/lib/active_record/aggregations.rb144
-rw-r--r--activerecord/lib/active_record/association_preload.rb13
-rwxr-xr-xactiverecord/lib/active_record/associations.rb172
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb79
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb61
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb34
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb8
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/base.rb161
-rw-r--r--activerecord/lib/active_record/calculations.rb25
-rw-r--r--activerecord/lib/active_record/callbacks.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb278
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb257
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb42
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/connection_adapters/abstract_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb41
-rw-r--r--activerecord/lib/active_record/dirty.rb25
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/i18n_interpolation_deprecation.rb26
-rw-r--r--activerecord/lib/active_record/locale/en-US.yml29
-rw-r--r--activerecord/lib/active_record/named_scope.rb34
-rw-r--r--activerecord/lib/active_record/reflection.rb35
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/test_case.rb15
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb110
-rw-r--r--activerecord/test/cases/aggregations_test.rb39
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb53
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb43
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations_test.rb124
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb9
-rw-r--r--activerecord/test/cases/base_test.rb39
-rw-r--r--activerecord/test/cases/connection_test_mysql.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb31
-rw-r--r--activerecord/test/cases/dirty_test.rb49
-rw-r--r--activerecord/test/cases/finder_test.rb58
-rw-r--r--activerecord/test/cases/helper.rb14
-rw-r--r--activerecord/test/cases/i18n_test.rb19
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb9
-rw-r--r--activerecord/test/cases/method_scoping_test.rb81
-rw-r--r--activerecord/test/cases/migration_test.rb6
-rw-r--r--activerecord/test/cases/named_scope_test.rb19
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb87
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/sanitize_test.rb25
-rw-r--r--activerecord/test/cases/schema_authorization_test_postgresql.rb4
-rw-r--r--activerecord/test/cases/threaded_connections_test.rb48
-rw-r--r--activerecord/test/cases/transactions_test.rb12
-rw-r--r--activerecord/test/cases/validations_i18n_test.rb65
-rw-r--r--activerecord/test/cases/validations_test.rb77
-rw-r--r--activerecord/test/fixtures/customers.yml11
-rw-r--r--activerecord/test/models/author.rb3
-rw-r--r--activerecord/test/models/category.rb3
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/customer.rb20
-rw-r--r--activerecord/test/models/parrot.rb1
-rw-r--r--activerecord/test/models/post.rb6
-rw-r--r--activeresource/lib/active_resource/base.rb103
-rw-r--r--activeresource/lib/active_resource/connection.rb25
-rw-r--r--activeresource/lib/active_resource/custom_methods.rb23
-rw-r--r--activeresource/lib/active_resource/formats/json_format.rb12
-rw-r--r--activeresource/lib/active_resource/formats/xml_format.rb18
-rw-r--r--activeresource/lib/active_resource/http_mock.rb2
-rw-r--r--activeresource/lib/active_resource/validations.rb44
-rw-r--r--activeresource/test/authorization_test.rb16
-rw-r--r--activeresource/test/base/custom_methods_test.rb3
-rw-r--r--activeresource/test/base/load_test.rb4
-rw-r--r--activeresource/test/base_test.rb10
-rw-r--r--activeresource/test/connection_test.rb14
-rw-r--r--activeresource/test/format_test.rb34
-rw-r--r--activesupport/CHANGELOG12
-rw-r--r--activesupport/lib/active_support.rb7
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb50
-rw-r--r--activesupport/lib/active_support/cache/synchronized_memory_store.rb46
-rw-r--r--activesupport/lib/active_support/callbacks.rb2
-rw-r--r--activesupport/lib/active_support/clean_logger.rb127
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/bigdecimal/conversions.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date/behavior.rb29
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb129
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb138
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb154
-rw-r--r--activesupport/lib/active_support/core_ext/module/model_naming.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb39
-rw-r--r--activesupport/lib/active_support/core_ext/object/misc.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/range/blockless_step.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/rexml.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/behavior.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb37
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb18
-rw-r--r--activesupport/lib/active_support/inflector.rb19
-rw-r--r--activesupport/lib/active_support/memoizable.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb5
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/secure_random.rb197
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb10
-rw-r--r--activesupport/lib/active_support/test_case.rb8
-rw-r--r--activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb6
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb96
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb2
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb37
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb65
-rw-r--r--activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Colombo.rb30
-rw-r--r--activesupport/test/buffered_logger_test.rb3
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb23
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb75
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb5
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb43
-rw-r--r--activesupport/test/core_ext/module/synchronization_test.rb85
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb117
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb74
-rw-r--r--activesupport/test/dependencies_test.rb18
-rw-r--r--activesupport/test/inflector_test.rb6
-rw-r--r--activesupport/test/inflector_test_cases.rb8
-rw-r--r--activesupport/test/multibyte_chars_test.rb4
-rw-r--r--activesupport/test/secure_random_test.rb15
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/Rakefile45
-rw-r--r--railties/doc/guides/activerecord/association_basics.txt1657
-rw-r--r--railties/doc/guides/debugging/debugging_rails_applications.txt604
-rw-r--r--railties/doc/guides/forms/form_helpers.txt270
-rw-r--r--railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt348
-rw-r--r--railties/doc/guides/index.txt53
-rw-r--r--railties/doc/guides/migrations/anatomy_of_a_migration.txt85
-rw-r--r--railties/doc/guides/migrations/creating_a_migration.txt109
-rw-r--r--railties/doc/guides/migrations/foreign_keys.txt7
-rw-r--r--railties/doc/guides/migrations/migrations.txt21
-rw-r--r--railties/doc/guides/migrations/rakeing_around.txt111
-rw-r--r--railties/doc/guides/migrations/scheming.txt47
-rw-r--r--railties/doc/guides/migrations/using_models_in_migrations.txt46
-rw-r--r--railties/doc/guides/migrations/writing_a_migration.txt159
-rw-r--r--railties/doc/guides/routing/routing_outside_in.txt838
-rw-r--r--railties/environments/environment.rb5
-rw-r--r--railties/lib/commands/console.rb13
-rw-r--r--railties/lib/commands/dbconsole.rb2
-rw-r--r--railties/lib/commands/plugin.rb18
-rw-r--r--railties/lib/commands/process/spawner.rb4
-rw-r--r--railties/lib/initializer.rb3
-rw-r--r--railties/lib/rails_generator/generators/applications/app/app_generator.rb3
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb1
-rw-r--r--railties/lib/rails_generator/secret_key_generator.rb152
-rw-r--r--railties/lib/tasks/databases.rake36
-rw-r--r--railties/lib/tasks/documentation.rake4
-rw-r--r--railties/lib/tasks/misc.rake7
-rw-r--r--railties/test/secret_key_generation_test.rb10
-rwxr-xr-xrelease.rb7
267 files changed, 10347 insertions, 5055 deletions
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index bdae0d4d3d..fc02ae8ffc 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -1,3 +1,8 @@
+* Add layout functionality to mailers [Pratik]
+
+ Mailer layouts behaves just like controller layouts, except layout names need to
+ have '_mailer' postfix for them to be automatically picked up.
+
*2.1.0 (May 31st, 2008)*
* Fixed that a return-path header would be ignored #7572 [joost]
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 72c94529b5..bfe435550b 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -246,7 +246,10 @@ module ActionMailer #:nodoc:
# +implicit_parts_order+.
class Base
include AdvAttrAccessor, PartContainer
- include ActionController::UrlWriter if Object.const_defined?(:ActionController)
+ if Object.const_defined?(:ActionController)
+ include ActionController::UrlWriter
+ include ActionController::Layout
+ end
private_class_method :new #:nodoc:
@@ -362,6 +365,7 @@ module ActionMailer #:nodoc:
# The mail object instance referenced by this mailer.
attr_reader :mail
+ attr_reader :template_name, :default_template_name, :action_name
class << self
attr_writer :mailer_name
@@ -374,11 +378,16 @@ module ActionMailer #:nodoc:
alias_method :controller_name, :mailer_name
alias_method :controller_path, :mailer_name
- def method_missing(method_symbol, *parameters)#:nodoc:
- case method_symbol.id2name
- when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
- when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
- when "new" then nil
+ def respond_to?(method_symbol, include_private = false) #:nodoc:
+ matches_dynamic_method?(method_symbol) || super
+ end
+
+ def method_missing(method_symbol, *parameters) #:nodoc:
+ match = matches_dynamic_method?(method_symbol)
+ case match[1]
+ when 'create' then new(match[2], *parameters).mail
+ when 'deliver' then new(match[2], *parameters).deliver!
+ when 'new' then nil
else super
end
end
@@ -424,6 +433,12 @@ module ActionMailer #:nodoc:
def template_root=(root)
self.view_paths = ActionView::Base.process_view_paths(root)
end
+
+ private
+ def matches_dynamic_method?(method_name) #:nodoc:
+ method_name = method_name.to_s
+ /(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
+ end
end
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
@@ -451,7 +466,7 @@ module ActionMailer #:nodoc:
template = template_root["#{mailer_name}/#{File.basename(path)}"]
# Skip unless template has a multipart format
- next unless template.multipart?
+ next unless template && template.multipart?
@parts << Part.new(
:content_type => template.content_type,
@@ -519,6 +534,7 @@ module ActionMailer #:nodoc:
@content_type ||= @@default_content_type.dup
@implicit_parts_order ||= @@default_implicit_parts_order.dup
@template ||= method_name
+ @default_template_name = @action_name = @template
@mailer_name ||= self.class.name.underscore
@parts ||= []
@headers ||= {}
@@ -535,7 +551,22 @@ module ActionMailer #:nodoc:
if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render))
opts[:file] = "#{mailer_name}/#{opts[:file]}"
end
- initialize_template_class(body).render(opts)
+
+ begin
+ old_template, @template = @template, initialize_template_class(body)
+ layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false
+ @template.render(opts.merge(:layout => layout))
+ ensure
+ @template = old_template
+ end
+ end
+
+ def default_template_format
+ :html
+ end
+
+ def candidate_for_layout?(options)
+ !@template.send(:_exempt_from_layout?, default_template_name)
end
def template_root
diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb
index 9c5fcc6afb..5f6dcd77cd 100644
--- a/actionmailer/lib/action_mailer/helpers.rb
+++ b/actionmailer/lib/action_mailer/helpers.rb
@@ -72,7 +72,7 @@ module ActionMailer
methods.flatten.each do |method|
master_helper_module.module_eval <<-end_eval
def #{method}(*args, &block)
- controller.send!(%(#{method}), *args, &block)
+ controller.__send__(%(#{method}), *args, &block)
end
end_eval
end
@@ -92,7 +92,7 @@ module ActionMailer
inherited_without_helper(child)
begin
child.master_helper_module = Module.new
- child.master_helper_module.send! :include, master_helper_module
+ child.master_helper_module.__send__(:include, master_helper_module)
child.helper child.name.to_s.underscore
rescue MissingSourceFile => e
raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper")
diff --git a/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb b/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb
new file mode 100644
index 0000000000..54950788f7
--- /dev/null
+++ b/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb
@@ -0,0 +1 @@
+Inside \ No newline at end of file
diff --git a/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb b/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb
new file mode 100644
index 0000000000..0533a3b2fe
--- /dev/null
+++ b/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb
@@ -0,0 +1 @@
+You logged out \ No newline at end of file
diff --git a/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb b/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb
new file mode 100644
index 0000000000..4789e888c6
--- /dev/null
+++ b/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb
@@ -0,0 +1 @@
+We do not spam \ No newline at end of file
diff --git a/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb b/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb
new file mode 100644
index 0000000000..932271450c
--- /dev/null
+++ b/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb
@@ -0,0 +1 @@
+Hello from layout <%= yield %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/layouts/spam.html.erb b/actionmailer/test/fixtures/layouts/spam.html.erb
new file mode 100644
index 0000000000..619d6b16b4
--- /dev/null
+++ b/actionmailer/test/fixtures/layouts/spam.html.erb
@@ -0,0 +1 @@
+Spammer layout <%= yield %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.erb~ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.erb~
new file mode 100644
index 0000000000..946d99ede5
--- /dev/null
+++ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.erb~
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ HTML formatted message to <strong><%= @recipient %></strong>.
+ </body>
+</html>
+<html>
+ <body>
+ HTML formatted message to <strong><%= @recipient %></strong>.
+ </body>
+</html>
diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb
new file mode 100644
index 0000000000..ffba9a16bd
--- /dev/null
+++ b/actionmailer/test/mail_layout_test.rb
@@ -0,0 +1,78 @@
+require 'abstract_unit'
+
+class AutoLayoutMailer < ActionMailer::Base
+ def hello(recipient)
+ recipients recipient
+ subject "You have a mail"
+ from "tester@example.com"
+ end
+
+ def spam(recipient)
+ recipients recipient
+ subject "You have a mail"
+ from "tester@example.com"
+ body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" })
+ end
+
+ def nolayout(recipient)
+ recipients recipient
+ subject "You have a mail"
+ from "tester@example.com"
+ body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
+ end
+end
+
+class ExplicitLayoutMailer < ActionMailer::Base
+ layout 'spam', :except => [:logout]
+
+ def signup(recipient)
+ recipients recipient
+ subject "You have a mail"
+ from "tester@example.com"
+ end
+
+ def logout(recipient)
+ recipients recipient
+ subject "You have a mail"
+ from "tester@example.com"
+ end
+end
+
+class LayoutMailerTest < Test::Unit::TestCase
+ def setup
+ set_delivery_method :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+
+ @recipient = 'test@localhost'
+ end
+
+ def teardown
+ restore_delivery_method
+ end
+
+ def test_should_pickup_default_layout
+ mail = AutoLayoutMailer.create_hello(@recipient)
+ assert_equal "Hello from layout Inside", mail.body.strip
+ end
+
+ def test_should_pickup_layout_given_to_render
+ mail = AutoLayoutMailer.create_spam(@recipient)
+ assert_equal "Spammer layout Hello, Earth", mail.body.strip
+ end
+
+ def test_should_respect_layout_false
+ mail = AutoLayoutMailer.create_nolayout(@recipient)
+ assert_equal "Hello, Earth", mail.body.strip
+ end
+
+ def test_explicit_class_layout
+ mail = ExplicitLayoutMailer.create_signup(@recipient)
+ assert_equal "Spammer layout We do not spam", mail.body.strip
+ end
+
+ def test_explicit_layout_exceptions
+ mail = ExplicitLayoutMailer.create_logout(@recipient)
+ assert_equal "You logged out", mail.body.strip
+ end
+end
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 882b07d675..f57c6f3fb8 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -968,3 +968,55 @@ class MethodNamingTest < Test::Unit::TestCase
end
end
end
+
+class RespondToTest < Test::Unit::TestCase
+ class RespondToMailer < ActionMailer::Base; end
+
+ def setup
+ set_delivery_method :test
+ end
+
+ def teardown
+ restore_delivery_method
+ end
+
+ def test_should_respond_to_new
+ assert RespondToMailer.respond_to?(:new)
+ end
+
+ def test_should_respond_to_create_with_template_suffix
+ assert RespondToMailer.respond_to?(:create_any_old_template)
+ end
+
+ def test_should_respond_to_deliver_with_template_suffix
+ assert RespondToMailer.respond_to?(:deliver_any_old_template)
+ end
+
+ def test_should_not_respond_to_new_with_template_suffix
+ assert !RespondToMailer.respond_to?(:new_any_old_template)
+ end
+
+ def test_should_not_respond_to_create_with_template_suffix_unless_it_is_separated_by_an_underscore
+ assert !RespondToMailer.respond_to?(:createany_old_template)
+ end
+
+ def test_should_not_respond_to_deliver_with_template_suffix_unless_it_is_separated_by_an_underscore
+ assert !RespondToMailer.respond_to?(:deliverany_old_template)
+ end
+
+ def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_uppercase_letter
+ assert !RespondToMailer.respond_to?(:create_Any_old_template)
+ end
+
+ def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_uppercase_letter
+ assert !RespondToMailer.respond_to?(:deliver_Any_old_template)
+ end
+
+ def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_digit
+ assert !RespondToMailer.respond_to?(:create_1_template)
+ end
+
+ def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_digit
+ assert !RespondToMailer.respond_to?(:deliver_1_template)
+ end
+end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index be490093ac..54ea93fb72 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,51 @@
*Edge*
+* Set HttpOnly for the cookie session store's cookie. #1046
+
+* Added FormTagHelper#image_submit_tag confirm option #784 [Alastair Brunton]
+
+* Fixed FormTagHelper#submit_tag with :disable_with option wouldn't submit the button's value when was clicked #633 [Jose Fernandez]
+
+* Stopped logging template compiles as it only clogs up the log [DHH]
+
+* Changed the X-Runtime header to report in milliseconds [DHH]
+
+* Changed BenchmarkHelper#benchmark to report in milliseconds [DHH]
+
+* Changed logging format to be millisecond based and skip misleading stats [DHH]. Went from:
+
+ Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK [http://example.com]
+
+ ...to:
+
+ Completed in 100ms (View: 40, DB: 4) | 200 OK [http://example.com]
+
+* Add support for shallow nesting of routes. #838 [S. Brent Faulkner]
+
+ Example :
+
+ map.resources :users, :shallow => true do |user|
+ user.resources :posts
+ end
+
+ - GET /users/1/posts (maps to PostsController#index action as usual)
+ named route "user_posts" is added as usual.
+
+ - GET /posts/2 (maps to PostsController#show action as if it were not nested)
+ Additionally, named route "post" is added too.
+
+* Added button_to_remote helper. #3641 [Donald Piret, Tarmo Tänav]
+
+* Deprecate render_component. Please use render_component plugin from http://github.com/rails/render_component/tree/master [Pratik]
+
+* Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 [Brennan Dunn, Gaius Centus Novus]
+ map.resource :posts, :collection => { :search => [:get, :post] }
+ map.session 'session', :requirements => { :method => [:get, :post, :delete] }
+
+* Deprecated implicit local assignments when rendering partials [Josh Peek]
+
+* Introduce current_cycle helper method to return the current value without bumping the cycle. #417 [Ken Collins]
+
* Allow polymorphic_url helper to take url options. #880 [Tarmo Tänav]
* Switched integration test runner to use Rack processor instead of CGI [Josh Peek]
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 1880f21f67..0ee9382e89 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -25,14 +25,16 @@ task :default => [ :test ]
desc "Run all unit tests"
task :test => [:test_action_pack, :test_active_record_integration]
-Rake::TestTask.new(:test_action_pack) { |t|
+Rake::TestTask.new(:test_action_pack) do |t|
t.libs << "test"
-# make sure we include the tests in alphabetical order as on some systems
-# this will not happen automatically and the tests (as a whole) will error
- t.test_files=Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
-# t.pattern = 'test/*/*_test.rb'
+
+ # make sure we include the tests in alphabetical order as on some systems
+ # this will not happen automatically and the tests (as a whole) will error
+ t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
+
t.verbose = true
-}
+ #t.warning = true
+end
desc 'ActiveRecord Integration Tests'
Rake::TestTask.new(:test_active_record_integration) do |t|
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e58071d4af..2efd0dad2e 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -54,6 +54,7 @@ require 'action_controller/rack_process'
require 'action_controller/record_identifier'
require 'action_controller/request_forgery_protection'
require 'action_controller/headers'
+require 'action_controller/translation'
require 'action_view'
@@ -74,4 +75,5 @@ ActionController::Base.class_eval do
include ActionController::Components
include ActionController::RecordIdentifier
include ActionController::RequestForgeryProtection
+ include ActionController::Translation
end
diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb
index 312b4e228b..8a837c592c 100644
--- a/actionpack/lib/action_controller/assertions/routing_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb
@@ -10,32 +10,32 @@ module ActionController
# and a :method containing the required HTTP verb.
#
# # assert that POSTing to /items will call the create action on ItemsController
- # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
+ # assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
# extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
- # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
+ # assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
# ==== Examples
# # Check the default route (i.e., the index action)
- # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
+ # assert_recognizes {:controller => 'items', :action => 'index'}, 'items'
#
# # Test a specific action
- # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
+ # assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list'
#
# # Test an action with a parameter
- # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
+ # assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1'
#
# # Test a custom route
- # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
+ # assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1'
#
# # Check a Simply RESTful generated route
- # assert_recognizes(list_items_url, 'items/list')
+ # assert_recognizes list_items_url, 'items/list'
def assert_recognizes(expected_options, path, extras={}, message=nil)
if path.is_a? Hash
request_method = path[:method]
@@ -67,13 +67,13 @@ module ActionController
#
# ==== Examples
# # Asserts that the default action is generated for a route with no action
- # assert_generates("/items", :controller => "items", :action => "index")
+ # assert_generates "/items", :controller => "items", :action => "index"
#
# # Tests that the list action is properly routed
- # assert_generates("/items/list", :controller => "items", :action => "list")
+ # assert_generates "/items/list", :controller => "items", :action => "list"
#
# # Tests the generation of a route with a parameter
- # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
+ # assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
@@ -104,19 +104,19 @@ module ActionController
#
# ==== Examples
# # Assert a basic route: a controller with the default action (index)
- # assert_routing('/home', :controller => 'home', :action => 'index')
+ # assert_routing '/home', :controller => 'home', :action => 'index'
#
# # Test a route generated with a specific controller, action, and parameter (id)
- # assert_routing('/entries/show/23', :controller => 'entries', :action => 'show', id => 23)
+ # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23
#
# # Assert a basic route (controller + default action), with an error message if it fails
- # assert_routing('/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly')
+ # assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
#
# # Tests a route, providing a defaults hash
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
#
# # Tests a route with a HTTP method
- # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
+ # assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb
index 9114894b1d..bcbb570e4b 100644
--- a/actionpack/lib/action_controller/assertions/selector_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -396,54 +396,31 @@ module ActionController
# # The same, but shorter.
# assert_select "ol>li", 4
def assert_select_rjs(*args, &block)
- rjs_type = nil
- arg = args.shift
+ rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
+ id = args.first.is_a?(String) ? args.shift : nil
# If the first argument is a symbol, it's the type of RJS statement we're looking
# for (update, replace, insertion, etc). Otherwise, we're looking for just about
# any RJS statement.
- if arg.is_a?(Symbol)
- rjs_type = arg
-
+ if rjs_type
if rjs_type == :insert
- arg = args.shift
- position = arg
- insertion = "insert_#{arg}".to_sym
- raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion]
+ position = args.shift
+ insertion = "insert_#{position}".to_sym
+ raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
statement = "(#{RJS_STATEMENTS[insertion]})"
else
raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
statement = "(#{RJS_STATEMENTS[rjs_type]})"
end
- arg = args.shift
else
statement = "#{RJS_STATEMENTS[:any]}"
end
- position ||= Regexp.new(RJS_INSERTIONS.join('|'))
# Next argument we're looking for is the element identifier. If missing, we pick
- # any element.
- if arg.is_a?(String)
- id = Regexp.quote(arg)
- arg = args.shift
- else
- id = "[^\"]*"
- end
-
- pattern =
- case rjs_type
- when :chained_replace, :chained_replace_html
- Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
- when :remove, :show, :hide, :toggle
- Regexp.new("#{statement}\\(\"#{id}\"\\)")
- when :replace, :replace_html
- Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)")
- when :insert, :insert_html
- Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);")
- else
- Regexp.union(Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)"),
- Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);"))
- end
+ # any element, otherwise we replace it in the statement.
+ pattern = Regexp.new(
+ id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
+ )
# Duplicate the body since the next step involves destroying it.
matches = nil
@@ -472,7 +449,13 @@ module ActionController
matches
else
# RJS statement not found.
- flunk args.shift || "No RJS statement that replaces or inserts HTML content."
+ case rjs_type
+ when :remove, :show, :hide, :toggle
+ flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
+ else
+ flunk_message = "No RJS statement that replaces or inserts HTML content."
+ end
+ flunk args.shift || flunk_message
end
end
@@ -582,26 +565,23 @@ module ActionController
protected
unless const_defined?(:RJS_STATEMENTS)
- RJS_STATEMENTS = {
- :replace => /Element\.replace/,
- :replace_html => /Element\.update/,
- :chained_replace => /\.replace/,
- :chained_replace_html => /\.update/,
- :remove => /Element\.remove/,
- :show => /Element\.show/,
- :hide => /Element\.hide/,
- :toggle => /Element\.toggle/
+ RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
+ RJS_ANY_ID = "\"([^\"])*\""
+ RJS_STATEMENTS = {
+ :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
+ :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
+ :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
+ :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)"
}
- RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
- RJS_PATTERN_HTML = /"((\\"|[^"])*)"/
- RJS_INSERTIONS = [:top, :bottom, :before, :after]
+ [:remove, :show, :hide, :toggle].each do |action|
+ RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
+ end
+ RJS_INSERTIONS = ["top", "bottom", "before", "after"]
RJS_INSERTIONS.each do |insertion|
- RJS_STATEMENTS["insert_#{insertion}".to_sym] = /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/
+ RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
end
- RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion|
- /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/
- end.join('|'))
- RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
+ RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
+ RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
end
@@ -615,8 +595,8 @@ module ActionController
root = HTML::Node.new(nil)
while true
- next if body.sub!(RJS_PATTERN_EVERYTHING) do |match|
- html = unescape_rjs($3)
+ next if body.sub!(RJS_STATEMENTS[:any]) do |match|
+ html = unescape_rjs(match)
matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
root.children.concat matches
""
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 91023cd774..457b9e85bc 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -260,10 +260,11 @@ module ActionController #:nodoc:
include StatusCodes
+ cattr_reader :protected_instance_variables
# Controller specific instance variables which will not be accessible inside views.
- @@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
- @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
- @_flash @_response)
+ @@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
+ @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
+ @_flash @_response)
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example:
@@ -393,16 +394,9 @@ module ActionController #:nodoc:
# directive. Values should always be specified as strings.
attr_internal :headers
- # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
- # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
- attr_accessor :assigns
-
# Returns the name of the action this controller is processing.
attr_accessor :action_name
- # Templates that are exempt from layouts
- @@exempt_from_layout = Set.new([/\.rjs$/])
-
class << self
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
@@ -440,7 +434,11 @@ module ActionController #:nodoc:
# render("test/template") will be looked up in the view load paths array and the closest match will be
# returned.
def view_paths
- @view_paths || superclass.view_paths
+ if defined? @view_paths
+ @view_paths
+ else
+ superclass.view_paths
+ end
end
def view_paths=(value)
@@ -455,7 +453,7 @@ module ActionController #:nodoc:
# ArticleController.prepend_view_path(["views/default", "views/custom"])
#
def prepend_view_path(path)
- @view_paths = superclass.view_paths.dup if @view_paths.nil?
+ @view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil?
@view_paths.unshift(*path)
end
@@ -520,13 +518,7 @@ module ActionController #:nodoc:
protected :filter_parameters
end
- # Don't render layouts for templates with the given extensions.
- def exempt_from_layout(*extensions)
- regexps = extensions.collect do |extension|
- extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
- end
- @@exempt_from_layout.merge regexps
- end
+ delegate :exempt_from_layout, :to => 'ActionView::Base'
end
public
@@ -538,7 +530,6 @@ module ActionController #:nodoc:
assign_shortcuts(request, response)
initialize_current_url
assign_names
- forget_variables_added_to_assigns
log_processing
@@ -548,12 +539,16 @@ module ActionController #:nodoc:
@@guard.synchronize { send(method, *arguments) }
end
- response.prepare! unless component_request?
- response
+ send_response
ensure
process_cleanup
end
+ def send_response
+ response.prepare! unless component_request?
+ response
+ end
+
# Returns a URL that has been rewritten according to the options hash and the defined Routes.
# (For doing a complete redirect, use redirect_to).
#
@@ -859,7 +854,7 @@ module ActionController #:nodoc:
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
if options.nil?
- return render_for_file(default_template_name, nil, true)
+ return render(:file => default_template_name, :layout => true)
elsif !extra_options.is_a?(Hash)
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
else
@@ -870,6 +865,9 @@ module ActionController #:nodoc:
end
end
+ response.layout = layout = pick_layout(options)
+ logger.info("Rendering template within #{layout}") if logger && layout
+
if content_type = options[:content_type]
response.content_type = content_type.to_s
end
@@ -879,26 +877,21 @@ module ActionController #:nodoc:
end
if options.has_key?(:text)
- render_for_text(options[:text], options[:status])
+ text = layout ? @template.render(options.merge(:text => options[:text], :layout => layout)) : options[:text]
+ render_for_text(text, options[:status])
else
if file = options[:file]
- render_for_file(file, options[:status], nil, options[:locals] || {})
+ render_for_file(file, options[:status], layout, options[:locals] || {})
elsif template = options[:template]
- render_for_file(template, options[:status], true, options[:locals] || {})
+ render_for_file(template, options[:status], layout, options[:locals] || {})
elsif inline = options[:inline]
- add_variables_to_assigns
- render_for_text(@template.render(options), options[:status])
+ render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
elsif action_name = options[:action]
- template = default_template_name(action_name.to_s)
- if options[:layout] && !template_exempt_from_layout?(template)
- render_with_a_layout(:file => template, :status => options[:status], :layout => true)
- else
- render_with_no_layout(:file => template, :status => options[:status])
- end
+ render_for_file(default_template_name(action_name.to_s), options[:status], layout)
elsif xml = options[:xml]
response.content_type ||= Mime::XML
@@ -912,12 +905,14 @@ module ActionController #:nodoc:
elsif options[:partial]
options[:partial] = default_template_name if options[:partial] == true
- add_variables_to_assigns
- render_for_text(@template.render(options), options[:status])
+ if layout
+ render_for_text(@template.render(:text => @template.render(options), :layout => layout), options[:status])
+ else
+ render_for_text(@template.render(options), options[:status])
+ end
elsif options[:update]
- add_variables_to_assigns
- @template.send! :evaluate_assigns
+ @template.send(:_evaluate_assigns_and_ivars)
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
response.content_type = Mime::JS
@@ -927,7 +922,7 @@ module ActionController #:nodoc:
render_for_text(nil, options[:status])
else
- render_for_file(default_template_name, options[:status], true)
+ render_for_file(default_template_name, options[:status], layout)
end
end
end
@@ -938,7 +933,6 @@ module ActionController #:nodoc:
render(options, &block)
ensure
erase_render_results
- forget_variables_added_to_assigns
reset_variables_added_to_assigns
end
@@ -1123,10 +1117,9 @@ module ActionController #:nodoc:
private
- def render_for_file(template_path, status = nil, use_full_path = nil, locals = {}) #:nodoc:
- add_variables_to_assigns
+ def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
- render_for_text(@template.render(:file => template_path, :locals => locals), status)
+ render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
end
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
@@ -1148,7 +1141,7 @@ module ActionController #:nodoc:
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
- response.template.extend self.class.master_helper_module
+ response.template.helpers.send :include, self.class.master_helper_module
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
@@ -1161,7 +1154,6 @@ module ActionController #:nodoc:
@_session = @_response.session
@template = @_response.template
- @assigns = @_response.template.assigns
@_headers = @_response.headers
end
@@ -1225,27 +1217,10 @@ module ActionController #:nodoc:
hidden_actions
end
- def add_variables_to_assigns
- unless @variables_added
- add_instance_variables_to_assigns
- @variables_added = true
- end
- end
-
- def forget_variables_added_to_assigns
- @variables_added = nil
- end
-
def reset_variables_added_to_assigns
@template.instance_variable_set("@assigns_added", nil)
end
- def add_instance_variables_to_assigns
- (instance_variable_names - @@protected_view_variables).each do |var|
- @assigns[var[1..-1]] = instance_variable_get(var)
- end
- end
-
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
@@ -1261,12 +1236,7 @@ module ActionController #:nodoc:
end
def template_exists?(template_name = default_template_name)
- @template.file_exists?(template_name)
- end
-
- def template_exempt_from_layout?(template_name = default_template_name)
- template_name = @template.pick_template(template_name).to_s if @template
- @@exempt_from_layout.any? { |ext| template_name =~ ext }
+ @template.send(:_pick_template, template_name) ? true : false
rescue ActionView::MissingTemplate
false
end
diff --git a/actionpack/lib/action_controller/benchmarking.rb b/actionpack/lib/action_controller/benchmarking.rb
index 98b0325ba3..fa572ebf3d 100644
--- a/actionpack/lib/action_controller/benchmarking.rb
+++ b/actionpack/lib/action_controller/benchmarking.rb
@@ -24,7 +24,7 @@ module ActionController #:nodoc:
if logger && logger.level == log_level
result = nil
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
+ logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
result
else
yield
@@ -42,53 +42,66 @@ module ActionController #:nodoc:
protected
def render_with_benchmark(options = nil, extra_options = {}, &block)
- unless logger
- render_without_benchmark(options, extra_options, &block)
- else
- db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+ if logger
+ if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+ db_runtime = ActiveRecord::Base.connection.reset_runtime
+ end
render_output = nil
- @rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) }
+ @view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
@db_rt_before_render = db_runtime
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
- @rendering_runtime -= @db_rt_after_render
+ @view_runtime -= @db_rt_after_render
end
render_output
+ else
+ render_without_benchmark(options, extra_options, &block)
end
end
private
def perform_action_with_benchmark
- unless logger
- perform_action_without_benchmark
- else
- runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
+ if logger
+ seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
+ logging_view = defined?(@view_runtime)
+ logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+
+ log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
+
+ if logging_view || logging_active_record
+ log_message << " ("
+ log_message << view_runtime if logging_view
+
+ if logging_active_record
+ log_message << ", " if logging_view
+ log_message << active_record_runtime + ")"
+ else
+ ")"
+ end
+ end
- log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
- log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
- log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
log_message << " | #{headers["Status"]}"
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
- response.headers["X-Runtime"] = sprintf("%.5f", runtime)
+ response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
+ else
+ perform_action_without_benchmark
end
end
- def rendering_runtime(runtime)
- percentage = @rendering_runtime * 100 / runtime
- " | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i]
+ def view_runtime
+ "View: %.0f" % (@view_runtime * 1000)
end
- def active_record_runtime(runtime)
- db_runtime = ActiveRecord::Base.connection.reset_runtime
- db_runtime += @db_rt_before_render if @db_rt_before_render
- db_runtime += @db_rt_after_render if @db_rt_after_render
- db_percentage = db_runtime * 100 / runtime
- " | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i]
+ def active_record_runtime
+ db_runtime = ActiveRecord::Base.connection.reset_runtime
+ db_runtime += @db_rt_before_render if @db_rt_before_render
+ db_runtime += @db_rt_after_render if @db_rt_after_render
+ "DB: %.0f" % (db_runtime * 1000)
end
end
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index f3535f8330..7c803a9830 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
- # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
- # controller.send(:list_url, c.params[:id]) }
+ # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
+ # controller.send(:list_url, controller.params[:id]) }
# end
#
# If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
@@ -90,7 +90,7 @@ module ActionController #:nodoc:
set_content_type!(controller, cache_path.extension)
options = { :text => cache }
options.merge!(:layout => true) if cache_layout?
- controller.send!(:render, options)
+ controller.__send__(:render, options)
false
else
controller.action_cache_path = cache_path
@@ -121,7 +121,7 @@ module ActionController #:nodoc:
end
def content_for_layout(controller)
- controller.response.layout && controller.response.template.instance_variable_get('@content_for_layout')
+ controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout')
end
end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index 61559e9ec7..c7992d7769 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -83,13 +83,13 @@ module ActionController #:nodoc:
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
- send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
- send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
+ __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
+ __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
def method_missing(method, *arguments)
return if @controller.nil?
- @controller.send!(method, *arguments)
+ @controller.__send__(method, *arguments)
end
end
end
diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb
index 0ca27b30db..fabacd9b83 100644
--- a/actionpack/lib/action_controller/cgi_process.rb
+++ b/actionpack/lib/action_controller/cgi_process.rb
@@ -42,13 +42,14 @@ module ActionController #:nodoc:
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
- :cookie_only => true
+ :cookie_only => true,
+ :session_http_only=> true
}
def initialize(cgi, session_options = {})
@cgi = cgi
@session_options = session_options
- @env = @cgi.send!(:env_table)
+ @env = @cgi.__send__(:env_table)
super()
end
@@ -107,7 +108,7 @@ module ActionController #:nodoc:
end
def method_missing(method_id, *arguments)
- @cgi.send!(method_id, *arguments) rescue super
+ @cgi.__send__(method_id, *arguments) rescue super
end
private
@@ -164,7 +165,7 @@ end_msg
begin
output.write(@cgi.header(@headers))
- if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
+ if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
return
elsif @body.respond_to?(:call)
# Flush the output now in case the @body Proc uses
diff --git a/actionpack/lib/action_controller/components.rb b/actionpack/lib/action_controller/components.rb
index 8275bd380a..f446b91e7e 100644
--- a/actionpack/lib/action_controller/components.rb
+++ b/actionpack/lib/action_controller/components.rb
@@ -38,6 +38,7 @@ module ActionController #:nodoc:
def self.included(base) #:nodoc:
base.class_eval do
include InstanceMethods
+ include ActiveSupport::Deprecation
extend ClassMethods
helper HelperMethods
@@ -64,7 +65,7 @@ module ActionController #:nodoc:
module HelperMethods
def render_component(options)
- @controller.send!(:render_component_as_string, options)
+ @controller.__send__(:render_component_as_string, options)
end
end
@@ -82,6 +83,7 @@ module ActionController #:nodoc:
render_for_text(component_response(options, true).body, response.headers["Status"])
end
end
+ deprecate :render_component => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
# Returns the component response as a string
def render_component_as_string(options) #:doc:
@@ -95,6 +97,7 @@ module ActionController #:nodoc:
end
end
end
+ deprecate :render_component_as_string => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
def flash_with_components(refresh = false) #:nodoc:
if !defined?(@_flash) || refresh
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
index 1d7236f18a..9022b8b279 100644
--- a/actionpack/lib/action_controller/filters.rb
+++ b/actionpack/lib/action_controller/filters.rb
@@ -199,8 +199,8 @@ module ActionController #:nodoc:
Proc.new do |controller, action|
method.before(controller)
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ if controller.__send__(:performed?)
+ controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
@@ -223,8 +223,8 @@ module ActionController #:nodoc:
def call(controller, &block)
super
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ if controller.__send__(:performed?)
+ controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb
index ce5e8be54c..9cffa07b26 100644
--- a/actionpack/lib/action_controller/helpers.rb
+++ b/actionpack/lib/action_controller/helpers.rb
@@ -204,8 +204,8 @@ module ActionController #:nodoc:
begin
child.master_helper_module = Module.new
- child.master_helper_module.send! :include, master_helper_module
- child.send! :default_helper_module!
+ child.master_helper_module.__send__ :include, master_helper_module
+ child.__send__ :default_helper_module!
rescue MissingSourceFile => e
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
end
diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb
index 31db012ef2..2ed810db7d 100644
--- a/actionpack/lib/action_controller/http_authentication.rb
+++ b/actionpack/lib/action_controller/http_authentication.rb
@@ -117,7 +117,7 @@ module ActionController
def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
- controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
+ controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
end
end
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 198a22e8dc..a98c1af7f9 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -444,7 +444,7 @@ EOF
reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless %w(cookies assigns).include?(method)
- returning @integration_session.send!(method, *args) do
+ returning @integration_session.__send__(method, *args) do
copy_session_variables!
end
end
@@ -469,12 +469,12 @@ EOF
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
- extras.send!(:define_method, name) { |*args| delegate.send(name, *args) }
+ extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
end
end
# delegate add_assertion to the test case
- extras.send!(:define_method, :add_assertion) { test_result.add_assertion }
+ extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
session.extend(extras)
session.delegate = self
session.test_result = @_result
@@ -488,7 +488,7 @@ EOF
def copy_session_variables! #:nodoc:
return unless @integration_session
%w(controller response request).each do |var|
- instance_variable_set("@#{var}", @integration_session.send!(var))
+ instance_variable_set("@#{var}", @integration_session.__send__(var))
end
end
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
index 8b6febe254..3631ce86af 100644
--- a/actionpack/lib/action_controller/layout.rb
+++ b/actionpack/lib/action_controller/layout.rb
@@ -3,11 +3,6 @@ module ActionController #:nodoc:
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
- # NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
- # defined as a publicly exposed method
- alias_method :render_with_no_layout, :render
- alias_method :render, :render_with_a_layout
-
class << self
alias_method_chain :inherited, :layout
end
@@ -169,17 +164,17 @@ module ActionController #:nodoc:
# performance and have access to them as any normal template would.
def layout(template_name, conditions = {}, auto = false)
add_layout_conditions(conditions)
- write_inheritable_attribute "layout", template_name
- write_inheritable_attribute "auto_layout", auto
+ write_inheritable_attribute(:layout, template_name)
+ write_inheritable_attribute(:auto_layout, auto)
end
def layout_conditions #:nodoc:
- @layout_conditions ||= read_inheritable_attribute("layout_conditions")
+ @layout_conditions ||= read_inheritable_attribute(:layout_conditions)
end
def default_layout(format) #:nodoc:
- layout = read_inheritable_attribute("layout")
- return layout unless read_inheritable_attribute("auto_layout")
+ layout = read_inheritable_attribute(:layout)
+ return layout unless read_inheritable_attribute(:auto_layout)
@default_layout ||= {}
@default_layout[format] ||= default_layout_with_format(format, layout)
@default_layout[format]
@@ -199,7 +194,7 @@ module ActionController #:nodoc:
end
def add_layout_conditions(conditions)
- write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
+ write_inheritable_hash(:layout_conditions, normalize_conditions(conditions))
end
def normalize_conditions(conditions)
@@ -221,10 +216,10 @@ module ActionController #:nodoc:
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
def active_layout(passed_layout = nil)
- layout = passed_layout || self.class.default_layout(response.template.template_format)
+ layout = passed_layout || self.class.default_layout(default_template_format)
active_layout = case layout
when String then layout
- when Symbol then send!(layout)
+ when Symbol then __send__(layout)
when Proc then layout.call(self)
end
@@ -240,51 +235,24 @@ module ActionController #:nodoc:
end
end
- protected
- def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
- template_with_options = options.is_a?(Hash)
-
- if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options)
- options = options.merge :layout => false if template_with_options
- logger.info("Rendering template within #{layout}") if logger
-
- content_for_layout = render_with_no_layout(options, extra_options, &block)
- erase_render_results
- add_variables_to_assigns
- @template.instance_variable_set("@content_for_layout", content_for_layout)
- response.layout = layout
- status = template_with_options ? options[:status] : nil
- render_for_text(@template.render(layout), status)
- else
- render_with_no_layout(options, extra_options, &block)
- end
- end
-
-
private
- def apply_layout?(template_with_options, options)
- return false if options == :update
- template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
- end
-
def candidate_for_layout?(options)
- (options.has_key?(:layout) && options[:layout] != false) ||
- options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
- !template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
+ options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
+ !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
end
- def pick_layout(template_with_options, options)
- if template_with_options
- case layout = options[:layout]
- when FalseClass
- nil
- when NilClass, TrueClass
- active_layout if action_has_layout?
- else
- active_layout(layout)
+ def pick_layout(options)
+ if options.has_key?(:layout)
+ case layout = options.delete(:layout)
+ when FalseClass
+ nil
+ when NilClass, TrueClass
+ active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
+ else
+ active_layout(layout)
end
else
- active_layout if action_has_layout?
+ active_layout if action_has_layout? && candidate_for_layout?(options)
end
end
@@ -304,7 +272,13 @@ module ActionController #:nodoc:
end
def layout_directory?(layout_name)
- @template.file_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}")
+ @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false
+ rescue ActionView::MissingTemplate
+ false
+ end
+
+ def default_template_format
+ response.template.template_format
end
end
end
diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb
index a7215e6ea3..26edca3b69 100644
--- a/actionpack/lib/action_controller/mime_type.rb
+++ b/actionpack/lib/action_controller/mime_type.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module Mime
SET = []
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 30564c7bb3..cc228c4230 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -108,7 +108,7 @@ module ActionController
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
- send!(named_route, *args)
+ __send__(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
@@ -149,7 +149,7 @@ module ActionController
if parent.is_a?(Symbol) || parent.is_a?(String)
string << "#{parent}_"
else
- string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
+ string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_"
end
end
end
@@ -157,7 +157,7 @@ module ActionController
if record.is_a?(Symbol) || record.is_a?(String)
route << "#{record}_"
else
- route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
+ route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_"
end
action_prefix(options) + namespace + route + routing_type(options).to_s
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 1ace16da07..e8ea3704a8 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -14,7 +14,8 @@ module ActionController #:nodoc:
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
- :cookie_only => true
+ :cookie_only => true,
+ :session_http_only=> true
}
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 364e6201cc..8e6cfb41dc 100644..100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -22,7 +22,7 @@ module ActionController
# such as { 'RAILS_ENV' => 'production' }.
attr_reader :env
- # The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
+ # The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
def request_method
method = @env['REQUEST_METHOD']
@@ -32,7 +32,7 @@ module ActionController
end
memoize :request_method
- # The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
+ # The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
# equivalent from the application's perspective.
def method
@@ -59,19 +59,21 @@ module ActionController
request_method == :delete
end
- # Is this a HEAD request? <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
- # so check the HTTP method directly.
+ # Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
+ # this \method checks the actual HTTP \method directly.
def head?
request_method == :head
end
# Provides access to the request's HTTP headers, for example:
- # request.headers["Content-Type"] # => "text/plain"
+ #
+ # request.headers["Content-Type"] # => "text/plain"
def headers
ActionController::Http::Headers.new(@env)
end
memoize :headers
+ # Returns the content length of the request as an integer.
def content_length
@env['CONTENT_LENGTH'].to_i
end
@@ -79,14 +81,14 @@ module ActionController
# The MIME type of the HTTP request, such as Mime::XML.
#
- # For backward compatibility, the post format is extracted from the
+ # For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
Mime::Type.lookup(content_type_without_parameters)
end
memoize :content_type
- # Returns the accepted MIME type for the request
+ # Returns the accepted MIME type for the request.
def accepts
header = @env['HTTP_ACCEPT'].to_s.strip
@@ -123,7 +125,7 @@ module ActionController
not_modified?(response.last_modified) || etag_matches?(response.etag)
end
- # Returns the Mime type for the format used in the request.
+ # Returns the Mime type for the \format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
@@ -142,8 +144,8 @@ module ActionController
end
- # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
- # Example:
+ # Sets the \format by string extension, which can be used to force custom formats
+ # that are not controlled by the extension.
#
# class ApplicationController < ActionController::Base
# before_filter :adjust_format_for_iphone
@@ -159,7 +161,7 @@ module ActionController
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
- # If no format is given it returns <tt>:js</tt>for AJAX requests and <tt>:html</tt>
+ # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters[:format]
@@ -190,17 +192,19 @@ module ActionController
# the right-hand-side of X-Forwarded-For
TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
- # Determine originating IP address. REMOTE_ADDR is the standard
+ # Determines originating IP address. REMOTE_ADDR is the standard
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
def remote_ip
- if TRUSTED_PROXIES !~ @env['REMOTE_ADDR']
- return @env['REMOTE_ADDR']
- end
+ remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
+ unless remote_addr_list.blank?
+ not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
+ return not_trusted_addrs.first unless not_trusted_addrs.empty?
+ end
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
if @env.include? 'HTTP_CLIENT_IP'
@@ -235,13 +239,13 @@ EOM
memoize :server_software
- # Returns the complete URL used for this request
+ # Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
end
memoize :url
- # Return 'https://' if this is an SSL request and 'http://' otherwise.
+ # Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
ssl? ? 'https://' : 'http://'
end
@@ -252,6 +256,7 @@ EOM
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
end
+ # Returns the \host for this request, such as "example.com".
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
@@ -266,8 +271,8 @@ EOM
end
memoize :host
- # Returns a host:port string for this request, such as example.com or
- # example.com:8080.
+ # Returns a \host:\port string for this request, such as "example.com" or
+ # "example.com:8080".
def host_with_port
"#{host}#{port_string}"
end
@@ -283,7 +288,7 @@ EOM
end
memoize :port
- # Returns the standard port number for this request's protocol
+ # Returns the standard \port number for this request's protocol.
def standard_port
case protocol
when 'https://' then 443
@@ -291,13 +296,13 @@ EOM
end
end
- # Returns a port suffix like ":8080" if the port number of this request
- # is not the default HTTP port 80 or HTTPS port 443.
+ # Returns a \port suffix like ":8080" if the \port number of this request
+ # is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
port == standard_port ? '' : ":#{port}"
end
- # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
+ # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain(tld_length = 1)
return nil unless named_host?(host)
@@ -305,8 +310,9 @@ EOM
host.split('.').last(1 + tld_length).join('.')
end
- # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
- # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
+ # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
+ # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
+ # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
# in "www.rubyonrails.co.uk".
def subdomains(tld_length = 1)
return [] unless named_host?(host)
@@ -314,7 +320,7 @@ EOM
parts[0..-(tld_length+2)]
end
- # Return the query string, accounting for server idiosyncrasies.
+ # Returns the query string, accounting for server idiosyncrasies.
def query_string
if uri = @env['REQUEST_URI']
uri.split('?', 2)[1] || ''
@@ -324,7 +330,7 @@ EOM
end
memoize :query_string
- # Return the request URI, accounting for server idiosyncrasies.
+ # Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
def request_uri
if uri = @env['REQUEST_URI']
@@ -350,7 +356,8 @@ EOM
end
memoize :request_uri
- # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
+ # Returns the interpreted \path to requested resource after all the installation
+ # directory of this application was taken into account.
def path
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
@@ -360,7 +367,7 @@ EOM
end
memoize :path
- # Read the request body. This is useful for web services that need to
+ # Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
unless env.include? 'RAW_POST_DATA'
@@ -370,7 +377,7 @@ EOM
env['RAW_POST_DATA']
end
- # Returns both GET and POST parameters in a single hash.
+ # Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
@@ -380,17 +387,17 @@ EOM
@symbolized_path_parameters = @parameters = nil
end
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
@symbolized_path_parameters ||= path_parameters.symbolize_keys
end
- # Returns a hash with the parameters used to form the path of the request.
- # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
- #
- # Example:
+ # Returns a hash with the \parameters used to form the \path of the request.
+ # Returned hash keys are strings:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
+ #
+ # See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
@path_parameters ||= {}
end
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index a1a9d68a35..83c4218af4 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -148,7 +148,7 @@ module ActionController #:nodoc:
end
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
- # default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
+ # default will call render_optional_error_file. Override this method to provide more user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
@@ -177,11 +177,8 @@ module ActionController #:nodoc:
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(exception)
- add_variables_to_assigns
@template.instance_variable_set("@exception", exception)
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
- @template.send!(:assign_variables_from_controller)
-
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index 5f579cdb11..872b0dab3d 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -1,23 +1,23 @@
module ActionController
# == Overview
#
- # ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
# is something that can be pointed at and it will respond with a representation of the data requested.
# In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
# requests XML data.
#
# RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a resource (the noun).
+ # application can request from a \resource (the noun).
#
- # Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
# denotes the type of action that should take place.
#
# === The Different Methods and their Usage
#
- # [+GET+] Requests for a resource, no saving or editing of a resource should occur in a GET request
- # [+POST+] Creation of resources
- # [+PUT+] Editing of attributes on a resource
- # [+DELETE+] Deletion of a resource
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
+ # * POST - Creation of \resources.
+ # * PUT - Editing of attributes on a \resource.
+ # * DELETE - Deletion of a \resource.
#
# === Examples
#
@@ -85,16 +85,24 @@ module ActionController
@new_path ||= "#{path}/#{new_action}"
end
+ def shallow_path_prefix
+ @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
+ end
+
def member_path
- @member_path ||= "#{path}/:id"
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
end
def nesting_path_prefix
- @nesting_path_prefix ||= "#{path}/:#{singular}_id"
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
+ end
+
+ def shallow_name_prefix
+ @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
end
def nesting_name_prefix
- "#{name_prefix}#{singular}_"
+ "#{shallow_name_prefix}#{singular}_"
end
def action_separator
@@ -141,12 +149,14 @@ module ActionController
super
end
+ alias_method :shallow_path_prefix, :path_prefix
+ alias_method :shallow_name_prefix, :name_prefix
alias_method :member_path, :path
alias_method :nesting_path_prefix, :path
end
# Creates named routes for implementing verb-oriented controllers
- # for a collection resource.
+ # for a collection \resource.
#
# For example:
#
@@ -238,23 +248,24 @@ module ActionController
#
# The +resources+ method accepts the following options to customize the resulting routes:
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
- # or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
+ # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
+ # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
+ # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
# * <tt>:controller</tt> - Specify the controller name for the routes.
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
- # * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
+ # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
+ # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
# # products_path == '/productos'
# map.resources :products, :as => 'productos' do |product|
# # product_reviews_path(product) == '/productos/1234/comentarios'
# product.resources :product_reviews, :as => 'comentarios'
# end
#
- # * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
+ # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
+ # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
#
# You may directly specify the routing association with +has_one+ and +has_many+ like:
#
@@ -277,18 +288,18 @@ module ActionController
#
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
#
- # Weblog comments usually belong to a post, so you might use resources like:
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
#
# map.resources :articles
# map.resources :comments, :path_prefix => '/articles/:article_id'
#
- # You can nest resources calls to set this automatically:
+ # You can nest +resources+ calls to set this automatically:
#
# map.resources :articles do |article|
# article.resources :comments
# end
#
- # The comment resources work the same, but must now include a value for <tt>:article_id</tt>.
+ # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
#
# article_comments_url(@article)
# article_comment_url(@article, @comment)
@@ -306,17 +317,42 @@ module ActionController
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
#
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
+ # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
#
# map.resources :articles do |article|
# article.resources :comments, :name_prefix => nil
# end
#
- # This will yield named resources like so:
+ # This will yield named \resources like so:
#
# comments_url(@article)
# comment_url(@article, @comment)
#
+ # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
+ #
+ # The <tt>:shallow</tt> option is inherited by any nested resource(s).
+ #
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
+ #
+ # map.resources :users, :shallow => true do |user|
+ # user.resources :posts do |post|
+ # post.resources :comments
+ # end
+ # end
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
+ # # also adds the usual named route called "user_posts"
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
+ # # also adds the named route called "post"
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
+ # # also adds the named route called "post_comments"
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
+ # # also adds the named route called "comment"
+ #
+ # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
+ #
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
+ #
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
#
# Examples:
@@ -349,28 +385,28 @@ module ActionController
#
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
+ # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
def resources(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_resource(entity, options.dup, &block) }
end
- # Creates named routes for implementing verb-oriented controllers for a singleton resource.
- # A singleton resource is global to its current context. For unnested singleton resources,
- # the resource is global to the current user visiting the application, such as a user's
- # /account profile. For nested singleton resources, the resource is global to its parent
- # resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
- # The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
+ # the \resource is global to the current user visiting the application, such as a user's
+ # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
+ # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
+ # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
#
# map.resources :projects do |project|
# project.resource :project_manager
# end
#
- # See map.resources for general conventions. These are the main differences:
- # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
+ # See +resources+ for general conventions. These are the main differences:
+ # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
# * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
- # * No default index route is created for the singleton resource controller.
- # * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ # * No default index route is created for the singleton \resource controller.
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
#
# For example:
#
@@ -442,7 +478,7 @@ module ActionController
map_associations(resource, options)
if block_given?
- with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
+ with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
end
end
end
@@ -459,29 +495,45 @@ module ActionController
map_associations(resource, options)
if block_given?
- with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
+ with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
end
end
end
def map_associations(resource, options)
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
- Array(options[:has_many]).each do |association|
- resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
+ Array(options[:has_one]).each do |association|
+ resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow])
end
+ end
- Array(options[:has_one]).each do |association|
- resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
+ def map_has_many_associations(resource, associations, options)
+ case associations
+ when Hash
+ associations.each do |association,has_many|
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
+ end
+ when Array
+ associations.each do |association|
+ map_has_many_associations(resource, association, options)
+ end
+ when Symbol, String
+ resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many])
+ else
end
end
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
- action_options = action_options_for(action, resource, method)
- map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
+ [method].flatten.each do |m|
+ action_options = action_options_for(action, resource, m)
+ map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
+ end
end
end
end
@@ -521,17 +573,19 @@ module ActionController
def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
- action_options = action_options_for(action, resource, method)
+ [method].flatten.each do |m|
+ action_options = action_options_for(action, resource, m)
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= Base.resources_path_names[action] || action
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= Base.resources_path_names[action] || action
- map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
+ map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
+ end
end
end
show_action_options = action_options_for("show", resource)
- map_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
+ map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options)
update_action_options = action_options_for("update", resource)
map_unnamed_routes(map, resource.member_path, update_action_options)
@@ -574,4 +628,4 @@ end
class ActionController::Routing::RouteSet::Mapper
include ActionController::Resources
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 5dac4128bb..54a99996ef 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -129,8 +129,8 @@ module ActionController # :nodoc:
def prepare!
assign_default_content_type_and_charset!
- set_content_length!
handle_conditional_get!
+ set_content_length!
convert_content_type!
end
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index 03427e41de..5704d9d01a 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -187,12 +187,14 @@ module ActionController
private
def validate_route_conditions(conditions)
if method = conditions[:method]
- if method == :head
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
+ [method].flatten.each do |m|
+ if m == :head
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
- unless HTTP_METHODS.include?(method.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
+ unless HTTP_METHODS.include?(m.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
index 0fe836606c..894d4109e4 100644
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ b/actionpack/lib/action_controller/routing/optimisations.rb
@@ -103,9 +103,10 @@ module ActionController
end
# This case uses almost the same code as positional arguments,
- # but add an args.last.to_query on the end
+ # but add a question mark and args.last.to_query on the end,
+ # unless the last arg is empty
def generation_code
- super.insert(-2, '?#{args.last.to_query}')
+ super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
end
# To avoid generating "http://localhost/?host=foo.example.com" we
diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
index 6d54d0334c..4935432d87 100644
--- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb
+++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
@@ -134,6 +134,9 @@ module ActionController
def write_recognize_optimized!
tree = segment_tree(routes)
body = generate_code(tree)
+
+ remove_recognize_optimized!
+
instance_eval %{
def recognize_optimized(path, env)
segments = to_plain_segments(path)
@@ -147,6 +150,25 @@ module ActionController
end
}, __FILE__, __LINE__
end
+
+ def clear_recognize_optimized!
+ remove_recognize_optimized!
+
+ class << self
+ def recognize_optimized(path, environment)
+ write_recognize_optimized!
+ recognize_optimized(path, environment)
+ end
+ end
+ end
+
+ def remove_recognize_optimized!
+ if respond_to?(:recognize_optimized)
+ class << self
+ remove_method :recognize_optimized
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb
index 2106ac09e0..3b2cb28545 100644
--- a/actionpack/lib/action_controller/routing/route.rb
+++ b/actionpack/lib/action_controller/routing/route.rb
@@ -201,7 +201,7 @@ module ActionController
# recognition, not generation.
def recognition_conditions
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
- result << "conditions[:method] === env[:method]" if conditions[:method]
+ result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
result
end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 8dfc22f94f..ff448490e9 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -115,7 +115,7 @@ module ActionController
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
reset! if regenerate
Array(destinations).each do |dest|
- dest.send! :include, @module
+ dest.__send__(:include, @module)
end
end
@@ -195,7 +195,7 @@ module ActionController
self.routes = []
self.named_routes = NamedRouteCollection.new
- write_recognize_optimized!
+ clear_recognize_optimized!
end
# Subclasses and plugins may override this method to specify a different
@@ -217,7 +217,7 @@ module ActionController
@routes_by_controller = nil
# This will force routing/recognition_optimization.rb
# to refresh optimisations.
- @compiled_recognize_optimized = nil
+ clear_recognize_optimized!
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
@@ -353,7 +353,7 @@ module ActionController
if generate_all
# Used by caching to expire all paths for a resource
return routes.collect do |route|
- route.send!(method, options, merged, expire_on)
+ route.__send__(method, options, merged, expire_on)
end.compact
end
@@ -361,7 +361,7 @@ module ActionController
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
routes.each do |route|
- results = route.send!(method, options, merged, expire_on)
+ results = route.__send__(method, options, merged, expire_on)
return results if results && (!results.is_a?(Array) || results.first)
end
end
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
index 9d4b740a44..e5f174ae2c 100644
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ b/actionpack/lib/action_controller/routing/segments.rb
@@ -160,7 +160,7 @@ module ActionController
s << "\n#{expiry_statement}"
end
- def interpolation_chunk(value_code = "#{local_name}")
+ def interpolation_chunk(value_code = local_name)
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
end
@@ -231,7 +231,7 @@ module ActionController
end
# Don't URI.escape the controller name since it may contain slashes.
- def interpolation_chunk(value_code = "#{local_name}")
+ def interpolation_chunk(value_code = local_name)
"\#{#{value_code}.to_s}"
end
@@ -251,7 +251,7 @@ module ActionController
end
class PathSegment < DynamicSegment #:nodoc:
- def interpolation_chunk(value_code = "#{local_name}")
+ def interpolation_chunk(value_code = local_name)
"\#{#{value_code}}"
end
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index 5bf7503f04..f2fb200950 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -70,7 +70,8 @@ class CGI::Session::CookieStore
'path' => options['session_path'],
'domain' => options['session_domain'],
'expires' => options['session_expires'],
- 'secure' => options['session_secure']
+ 'secure' => options['session_secure'],
+ 'http_only' => options['session_http_only']
}
# Set no_hidden and no_cookies since the session id is unused and we
diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb
index 80a3ddd2c5..fd3d94ed97 100644
--- a/actionpack/lib/action_controller/session_management.rb
+++ b/actionpack/lib/action_controller/session_management.rb
@@ -60,6 +60,10 @@ module ActionController #:nodoc:
# # the session will only work over HTTPS, but only for the foo action
# session :only => :foo, :session_secure => true
#
+ # # the session by default uses HttpOnly sessions for security reasons.
+ # # this can be switched off.
+ # session :only => :foo, :session_http_only => false
+ #
# # the session will only be disabled for 'foo', and only if it is
# # requested as a web service
# session :off, :only => :foo,
@@ -86,14 +90,14 @@ module ActionController #:nodoc:
raise ArgumentError, "only one of either :only or :except are allowed"
end
- write_inheritable_array("session_options", [options])
+ write_inheritable_array(:session_options, [options])
end
# So we can declare session options in the Rails initializer.
alias_method :session=, :session
def cached_session_options #:nodoc:
- @session_options ||= read_inheritable_attribute("session_options") || []
+ @session_options ||= read_inheritable_attribute(:session_options) || []
end
def session_options_for(request, action) #:nodoc:
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index c6b1470070..cde1f2052b 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -3,6 +3,8 @@ require 'action_controller/test_case'
module ActionController #:nodoc:
class Base
+ attr_reader :assigns
+
# Process a test request called with a TestRequest object.
def self.process_test(request)
new.process_test(request)
@@ -14,7 +16,12 @@ module ActionController #:nodoc:
def process_with_test(*args)
returning process_without_test(*args) do
- add_variables_to_assigns
+ @assigns = {}
+ (instance_variable_names - @@protected_instance_variables).each do |var|
+ value = instance_variable_get(var)
+ @assigns[var[1..-1]] = value
+ response.template.assigns[var[1..-1]] = value if response
+ end
end
end
@@ -211,7 +218,7 @@ module ActionController #:nodoc:
# Returns the template of the file which was used to
# render this response (or nil)
def rendered_template
- template._first_render
+ template.send(:_first_render)
end
# A shortcut to the flash. Returns an empty hash if no session flash exists.
@@ -331,7 +338,7 @@ module ActionController #:nodoc:
attr_reader :original_filename
# The content type of the "uploaded" file
- attr_reader :content_type
+ attr_accessor :content_type
def initialize(path, content_type = Mime::TEXT, binary = false)
raise "#{path} file does not exist" unless File.exist?(path)
@@ -350,7 +357,7 @@ module ActionController #:nodoc:
alias local_path path
def method_missing(method_name, *args, &block) #:nodoc:
- @tempfile.send!(method_name, *args, &block)
+ @tempfile.__send__(method_name, *args, &block)
end
end
@@ -396,7 +403,7 @@ module ActionController #:nodoc:
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
- returning send!(request_method, action, parameters, session, flash) do
+ returning __send__(request_method, action, parameters, session, flash) do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
@@ -429,7 +436,7 @@ module ActionController #:nodoc:
def build_request_uri(action, parameters)
unless @request.env['REQUEST_URI']
- options = @controller.send!(:rewrite_options, parameters)
+ options = @controller.__send__(:rewrite_options, parameters)
options.update(:only_path => true, :action => action)
url = ActionController::UrlRewriter.new(@request, parameters)
diff --git a/actionpack/lib/action_controller/translation.rb b/actionpack/lib/action_controller/translation.rb
new file mode 100644
index 0000000000..9bb63cdb15
--- /dev/null
+++ b/actionpack/lib/action_controller/translation.rb
@@ -0,0 +1,13 @@
+module ActionController
+ module Translation
+ def translate(*args)
+ I18n.translate *args
+ end
+ alias :t :translate
+
+ def localize(*args)
+ I18n.localize *args
+ end
+ alias :l :localize
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/verification.rb b/actionpack/lib/action_controller/verification.rb
index 35b12a7f13..7bf09ba6ea 100644
--- a/actionpack/lib/action_controller/verification.rb
+++ b/actionpack/lib/action_controller/verification.rb
@@ -80,7 +80,7 @@ module ActionController #:nodoc:
# array (may also be a single value).
def verify(options={})
before_filter :only => options[:only], :except => options[:except] do |c|
- c.send! :verify_action, options
+ c.__send__ :verify_action, options
end
end
end
@@ -116,7 +116,7 @@ module ActionController #:nodoc:
end
def apply_redirect_to(redirect_to_option) # :nodoc:
- (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option
+ (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option
end
def apply_remaining_actions(options) # :nodoc:
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 3590ab6d49..7cd9b633ac 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -43,9 +43,7 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
-I18n.backend.populate do
- I18n.load_translations "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml"
-end
+I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml"
require 'action_view/helpers'
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index d174c784f3..8c00670087 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -162,7 +162,6 @@ module ActionView #:nodoc:
attr_accessor :base_path, :assigns, :template_extension
attr_accessor :controller
- attr_accessor :_first_render, :_last_render
attr_writer :template_format
@@ -185,6 +184,17 @@ module ActionView #:nodoc:
"deprecated and has no effect. Please remove it from your config files.", caller)
end
+ # Templates that are exempt from layouts
+ @@exempt_from_layout = Set.new([/\.rjs$/])
+
+ # Don't render layouts for templates with the given extensions.
+ def self.exempt_from_layout(*extensions)
+ regexps = extensions.collect do |extension|
+ extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
+ end
+ @@exempt_from_layout.merge(regexps)
+ end
+
# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
@@debug_rjs = false
@@ -208,10 +218,24 @@ module ActionView #:nodoc:
ActionView::PathSet.new(Array(value))
end
+ attr_reader :helpers
+
+ class ProxyModule < Module
+ def initialize(receiver)
+ @receiver = receiver
+ end
+
+ def include(*args)
+ super(*args)
+ @receiver.extend(*args)
+ end
+ end
+
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
@assigns = assigns_for_first_render
@assigns_added = nil
@controller = controller
+ @helpers = ProxyModule.new(self)
self.view_paths = view_paths
end
@@ -232,33 +256,20 @@ module ActionView #:nodoc:
update_page(&block)
elsif options.is_a?(Hash)
options = options.reverse_merge(:locals => {})
-
- if partial_layout = options.delete(:layout)
- if block_given?
- begin
- @_proc_for_layout = block
- concat(render(options.merge(:partial => partial_layout)))
- ensure
- @_proc_for_layout = nil
- end
- else
- begin
- original_content_for_layout, @content_for_layout = @content_for_layout, render(options)
- render(options.merge(:partial => partial_layout))
- ensure
- @content_for_layout = original_content_for_layout
- end
- end
+ if options[:layout]
+ _render_with_layout(options, local_assigns, &block)
elsif options[:file]
if options[:use_full_path]
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
end
- pick_template(options[:file]).render_template(self, options[:locals])
+ _pick_template(options[:file]).render_template(self, options[:locals])
elsif options[:partial]
render_partial(options)
elsif options[:inline]
InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
+ elsif options[:text]
+ options[:text]
end
end
end
@@ -267,83 +278,106 @@ module ActionView #:nodoc:
# the same name but differing formats. See +Request#template_format+
# for more details.
def template_format
- return @template_format if @template_format
-
- if controller && controller.respond_to?(:request)
+ if defined? @template_format
+ @template_format
+ elsif controller && controller.respond_to?(:request)
@template_format = controller.request.template_format
else
@template_format = :html
end
end
- def file_exists?(template_path)
- pick_template(template_path) ? true : false
- rescue MissingTemplate
- false
- end
+ private
+ attr_accessor :_first_render, :_last_render
- # Gets the extension for an existing template with the given template_path.
- # Returns the format with the extension if that template exists.
- #
- # pick_template('users/show')
- # # => 'users/show.html.erb'
- #
- # pick_template('users/legacy')
- # # => 'users/legacy.rhtml'
- #
- def pick_template(template_path)
- return template_path if template_path.respond_to?(:render)
-
- path = template_path.sub(/^\//, '')
- if m = path.match(/(.*)\.(\w+)$/)
- template_file_name, template_file_extension = m[1], m[2]
- else
- template_file_name = path
- end
+ # Evaluate the local assigns and pushes them to the view.
+ def _evaluate_assigns_and_ivars #:nodoc:
+ unless @assigns_added
+ @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
- # OPTIMIZE: Checks to lookup template in view path
- if template = self.view_paths["#{template_file_name}.#{template_format}"]
- template
- elsif template = self.view_paths[template_file_name]
- template
- elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
- template
- elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
- @template_format = :html
- template
- else
- template = Template.new(template_path, view_paths)
-
- if self.class.warn_cache_misses && logger
- logger.debug "[PERFORMANCE] Rendering a template that was " +
- "not found in view path. Templates outside the view path are " +
- "not cached and result in expensive disk operations. Move this " +
- "file into #{view_paths.join(':')} or add the folder to your " +
- "view path list"
+ if @controller
+ variables = @controller.instance_variables
+ variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
+ variables.each {|name| instance_variable_set(name, @controller.instance_variable_get(name)) }
+ end
+
+ @assigns_added = true
end
+ end
- template
+ def _set_controller_content_type(content_type) #:nodoc:
+ if controller.respond_to?(:response)
+ controller.response.content_type ||= content_type
+ end
end
- end
- memoize :pick_template
- private
- # Evaluate the local assigns and pushes them to the view.
- def evaluate_assigns
- unless @assigns_added
- assign_variables_from_controller
- @assigns_added = true
+ def _pick_template(template_path)
+ return template_path if template_path.respond_to?(:render)
+
+ path = template_path.sub(/^\//, '')
+ if m = path.match(/(.*)\.(\w+)$/)
+ template_file_name, template_file_extension = m[1], m[2]
+ else
+ template_file_name = path
+ end
+
+ # OPTIMIZE: Checks to lookup template in view path
+ if template = self.view_paths["#{template_file_name}.#{template_format}"]
+ template
+ elsif template = self.view_paths[template_file_name]
+ template
+ elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
+ template
+ elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
+ @template_format = :html
+ template
+ else
+ template = Template.new(template_path, view_paths)
+
+ if self.class.warn_cache_misses && logger
+ logger.debug "[PERFORMANCE] Rendering a template that was " +
+ "not found in view path. Templates outside the view path are " +
+ "not cached and result in expensive disk operations. Move this " +
+ "file into #{view_paths.join(':')} or add the folder to your " +
+ "view path list"
+ end
+
+ template
end
end
+ memoize :_pick_template
- # Assigns instance variables from the controller to the view.
- def assign_variables_from_controller
- @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
+ def _exempt_from_layout?(template_path) #:nodoc:
+ template = _pick_template(template_path).to_s
+ @@exempt_from_layout.any? { |ext| template =~ ext }
+ rescue ActionView::MissingTemplate
+ return false
end
- def set_controller_content_type(content_type)
- if controller.respond_to?(:response)
- controller.response.content_type ||= content_type
+ def _render_with_layout(options, local_assigns, &block) #:nodoc:
+ partial_layout = options.delete(:layout)
+
+ if block_given?
+ begin
+ @_proc_for_layout = block
+ concat(render(options.merge(:partial => partial_layout)))
+ ensure
+ @_proc_for_layout = nil
+ end
+ else
+ begin
+ original_content_for_layout = @content_for_layout if defined?(@content_for_layout)
+ @content_for_layout = render(options)
+
+ if (options[:inline] || options[:file] || options[:text])
+ @cached_content_for_layout = @content_for_layout
+ render(:file => partial_layout, :locals => local_assigns)
+ else
+ render(options.merge(:partial => partial_layout))
+ end
+ ensure
+ @content_for_layout = original_content_for_layout
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 05e1cf990a..ff97df204c 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -21,7 +21,6 @@ module ActionView #:nodoc:
include CaptureHelper
include DateHelper
include DebugHelper
- include FormCountryHelper
include FormHelper
include FormOptionsHelper
include FormTagHelper
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index c339e10701..8b56d241ae 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -246,7 +246,7 @@ module ActionView
alias_method :tag_without_error_wrapping, :tag
def tag(name, options)
- if object.respond_to?("errors") && object.errors.respond_to?("on")
+ if object.respond_to?(:errors) && object.errors.respond_to?(:on)
error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
else
tag_without_error_wrapping(name, options)
@@ -255,7 +255,7 @@ module ActionView
alias_method :content_tag_without_error_wrapping, :content_tag
def content_tag(name, value, options)
- if object.respond_to?("errors") && object.errors.respond_to?("on")
+ if object.respond_to?(:errors) && object.errors.respond_to?(:on)
error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
else
content_tag_without_error_wrapping(name, value, options)
@@ -264,7 +264,7 @@ module ActionView
alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
def to_date_select_tag(options = {}, html_options = {})
- if object.respond_to?("errors") && object.errors.respond_to?("on")
+ if object.respond_to?(:errors) && object.errors.respond_to?(:on)
error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
else
to_date_select_tag_without_error_wrapping(options, html_options)
@@ -273,7 +273,7 @@ module ActionView
alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
def to_datetime_select_tag(options = {}, html_options = {})
- if object.respond_to?("errors") && object.errors.respond_to?("on")
+ if object.respond_to?(:errors) && object.errors.respond_to?(:on)
error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
else
to_datetime_select_tag_without_error_wrapping(options, html_options)
@@ -282,7 +282,7 @@ module ActionView
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
def to_time_select_tag(options = {}, html_options = {})
- if object.respond_to?("errors") && object.errors.respond_to?("on")
+ if object.respond_to?(:errors) && object.errors.respond_to?(:on)
error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
else
to_time_select_tag_without_error_wrapping(options, html_options)
@@ -298,7 +298,7 @@ module ActionView
end
def column_type
- object.send("column_for_attribute", @method_name).type
+ object.send(:column_for_attribute, @method_name).type
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 623ed1e8df..a926599e25 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -104,7 +104,7 @@ module ActionView
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
- JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
+ JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
@@ -463,7 +463,8 @@ module ActionView
end
private
- COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!
+ COMPUTED_PUBLIC_PATHS = {}
+ COMPUTED_PUBLIC_PATHS_GUARD = Mutex.new
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
@@ -483,23 +484,24 @@ module ActionView
dir, source, ext, include_host ].join
end
- source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do
- begin
- source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
-
- if source =~ %r{^[-a-z]+://}
- source
- else
- source = "/#{dir}/#{source}" unless source[0] == ?/
- if has_request
- unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
- source = "#{ActionController::Base.relative_url_root}#{source}"
+ COMPUTED_PUBLIC_PATHS_GUARD.synchronize do
+ source = COMPUTED_PUBLIC_PATHS[cache_key] ||=
+ begin
+ source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
+
+ if source =~ %r{^[-a-z]+://}
+ source
+ else
+ source = "/#{dir}/#{source}" unless source[0] == ?/
+ if has_request
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
+ source = "#{ActionController::Base.relative_url_root}#{source}"
+ end
end
- end
- rewrite_asset_path(source)
+ rewrite_asset_path(source)
+ end
end
- end
end
if include_host && source !~ %r{^[-a-z]+://}
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
index 743d1d40ec..bd72cda700 100644
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ b/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -15,15 +15,15 @@ module ActionView
# <%= expensive_files_operation %>
# <% end %>
#
- # That would add something like "Process data files (0.34523)" to the log,
+ # That would add something like "Process data files (345.2ms)" to the log,
# which you can then use to compare timings when optimizing your code.
#
# You may give an optional logger level as the second argument
# (:debug, :info, :warn, :error); the default value is :info.
def benchmark(message = "Benchmarking", level = :info)
if controller.logger
- real = Benchmark.realtime { yield }
- controller.logger.send(level, "#{message} (#{'%.5f' % real})")
+ seconds = Benchmark.realtime { yield }
+ controller.logger.send(level, "#{message} (#{'%.1f' % (seconds * 1000)}ms)")
else
yield
end
diff --git a/actionpack/lib/action_view/helpers/form_country_helper.rb b/actionpack/lib/action_view/helpers/form_country_helper.rb
deleted file mode 100644
index 84e811f61d..0000000000
--- a/actionpack/lib/action_view/helpers/form_country_helper.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'action_view/helpers/form_options_helper'
-
-module ActionView
- module Helpers
- module FormCountryHelper
-
- # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
- def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
- end
-
- # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
- # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
- # that they will be listed above the rest of the (long) list.
- #
- # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
- def country_options_for_select(selected = nil, priority_countries = nil)
- country_options = ""
-
- if priority_countries
- country_options += options_for_select(priority_countries, selected)
- country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
- end
-
- return country_options + options_for_select(COUNTRIES, selected)
- end
-
- private
-
- # All the countries included in the country_options output.
- COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
- "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
- "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
- "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
- "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
- "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
- "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
- "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
- "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
- "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
- "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
- "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
- "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
- "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
- "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
- "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
- "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
- "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
- "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
- "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
- "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
- "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
- "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
- "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
- "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
- "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
- "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
- "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
- "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
- "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
- "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
- end
-
- class InstanceTag #:nodoc:
- include FormCountryHelper
-
- def to_country_select_tag(priority_countries, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag("select",
- add_options(
- country_options_for_select(value, priority_countries),
- options, value
- ), html_options
- )
- end
- end
-
- class FormBuilder
- def country_select(method, priority_countries = nil, options = {}, html_options = {})
- @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 9aae945408..33f8aaf9ed 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -324,9 +324,6 @@ module ActionView
value == selected
end
end
-
- # All the countries included in the country_options output.
- COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES'
end
class InstanceTag #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e8ca02d760..294c22521e 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -116,7 +116,7 @@ module ActionView
# Creates a label field
#
- # ==== Options
+ # ==== Options
# * Creates standard HTML attributes for the tag.
#
# ==== Examples
@@ -155,10 +155,10 @@ module ActionView
# Creates a file upload field. If you are using file uploads then you will also need
# to set the multipart option for the form tag:
#
- # <%= form_tag { :action => "post" }, { :multipart => true } %>
+ # <% form_tag '/upload', :multipart => true do %>
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
# <%= submit_tag %>
- # <%= end_form_tag %>
+ # <% end %>
#
# The specified URL will then be passed a File object containing the selected file, or if the field
# was left blank, a StringIO object.
@@ -351,19 +351,16 @@ module ActionView
disable_with = "this.value='#{disable_with}'"
disable_with << ";#{options.delete('onclick')}" if options['onclick']
- options["onclick"] = [
- "this.setAttribute('originalValue', this.value)",
- "this.disabled=true",
- disable_with,
- "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
- "if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
- "return result;",
- ].join(";")
+ options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
+ options["onclick"] << "else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }"
+ options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
+ options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
+ options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
end
if confirm = options.delete("confirm")
options["onclick"] ||= ''
- options["onclick"] += "return #{confirm_javascript_function(confirm)};"
+ options["onclick"] << "return #{confirm_javascript_function(confirm)};"
end
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
@@ -374,6 +371,9 @@ module ActionView
# <tt>source</tt> is passed to AssetTagHelper#image_path
#
# ==== Options
+ # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
+ # prompt with the question specified. If the user accepts, the form is
+ # processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * Any other key creates standard HTML options for the tag.
#
@@ -390,6 +390,13 @@ module ActionView
# image_submit_tag("agree.png", :disabled => true, :class => "agree-disagree-button")
# # => <input class="agree-disagree-button" disabled="disabled" src="/images/agree.png" type="image" />
def image_submit_tag(source, options = {})
+ options.stringify_keys!
+
+ if confirm = options.delete("confirm")
+ options["onclick"] ||= ''
+ options["onclick"] += "return #{confirm_javascript_function(confirm)};"
+ end
+
tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index ff83494e94..ff41a6d417 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -255,6 +255,14 @@ module ActionView
link_to_function(name, remote_function(options), html_options || options.delete(:html))
end
+ # Creates a button with an onclick event which calls a remote action
+ # via XMLHttpRequest
+ # The options for specifying the target with :url
+ # and defining callbacks is the same as link_to_remote.
+ def button_to_remote(name, options = {}, html_options = {})
+ button_to_function(name, remote_function(options), html_options)
+ end
+
# Periodically calls the specified url (<tt>options[:url]</tt>) every
# <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
# update a specified div (<tt>options[:update]</tt>) with the results
@@ -588,26 +596,8 @@ module ActionView
private
def include_helpers_from_context
- unless generator_methods_module = @context.instance_variable_get(:@__javascript_generator_methods__)
- modules = @context.extended_by - ([ActionView::Helpers] + ActionView::Helpers.included_modules)
-
- generator_methods_module = Module.new do
- modules.each do |mod|
- begin
- include mod
- rescue Exception => e
- # HACK: Probably not a good idea to suppress these warnings
- # AFAIK exceptions are only raised in while testing with mocha
- # because the module does not like to be included into other
- # non TestUnit classes
- end
- end
- include GeneratorMethods
- end
- @context.instance_variable_set(:@__javascript_generator_methods__, generator_methods_module)
- end
-
- extend generator_methods_module
+ extend @context.helpers if @context.respond_to?(:helpers)
+ extend GeneratorMethods
end
# JavaScriptGenerator generates blocks of JavaScript code that allow you
@@ -624,7 +614,7 @@ module ActionView
# Example:
#
# # Generates:
- # # new Element.insert("list", { bottom: <li>Some item</li>" });
+ # # new Element.insert("list", { bottom: "<li>Some item</li>" });
# # new Effect.Highlight("list");
# # ["status-indicator", "cancel-link"].each(Element.hide);
# update_page do |page|
@@ -1070,7 +1060,7 @@ module ActionView
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
- js_options['insertion'] = options[:position].to_s.downcase if options[:position]
+ js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
js_options['evalScripts'] = options[:script].nil? || options[:script]
if options[:form]
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 9bb235175e..0cdb70e217 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -49,9 +49,9 @@ module ActionView
#
def content_tag_for(tag_name, record, *args, &block)
prefix = args.first.is_a?(Hash) ? nil : args.shift
- options = args.first.is_a?(Hash) ? args.shift : {}
- concat content_tag(tag_name, capture(&block),
- options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) }))
+ options = args.extract_options!
+ options.merge!({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
+ content_tag(tag_name, options, &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index 637caf203b..435ba936e1 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,14 @@
require 'action_view/helpers/tag_helper'
-require 'html/document'
+
+begin
+ require 'html/document'
+rescue LoadError
+ html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner"
+ if File.directory?(html_scanner_path)
+ $:.unshift html_scanner_path
+ require 'html/document'
+ end
+end
module ActionView
module Helpers #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
index b938c1a801..1d01dafd0e 100644
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
@@ -95,7 +95,7 @@ module ActionView
# * <tt>:containment</tt> - Takes an element or array of elements to treat as
# potential drop targets (defaults to the original target element).
#
- # * <tt>:only</tt> - A CSS class name or arry of class names used to filter
+ # * <tt>:only</tt> - A CSS class name or array of class names used to filter
# out child elements as candidates.
#
# * <tt>:scroll</tt> - Determines whether to scroll the list during drag
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index f9096d0029..ab1fdc80bc 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -1,5 +1,14 @@
require 'action_view/helpers/tag_helper'
-require 'html/document'
+
+begin
+ require 'html/document'
+rescue LoadError
+ html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner"
+ if File.directory?(html_scanner_path)
+ $:.unshift html_scanner_path
+ require 'html/document'
+ end
+end
module ActionView
module Helpers #:nodoc:
@@ -289,7 +298,7 @@ module ActionView
""
else
textilized = RedCloth.new(text, [ :hard_breaks ])
- textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
+ textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=)
textilized.to_html
end
end
@@ -439,8 +448,10 @@ module ActionView
# array every time it is called. This can be used for example, to alternate
# classes for table rows. You can use named cycles to allow nesting in loops.
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
- # named cycle. You can manually reset a cycle by calling reset_cycle and passing the
- # name of the cycle.
+ # named cycle. The default name for a cycle without a +:name+ key is
+ # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
+ # and passing the name of the cycle. The current cycle string can be obtained
+ # anytime using the current_cycle method.
#
# ==== Examples
# # Alternate CSS classes for even and odd numbers...
@@ -487,6 +498,23 @@ module ActionView
return cycle.to_s
end
+ # Returns the current cycle string after a cycle has been started. Useful
+ # for complex table highlighing or any other design need which requires
+ # the current cycle string in more than one place.
+ #
+ # ==== Example
+ # # Alternate background colors
+ # @items = [1,2,3,4]
+ # <% @items.each do |item| %>
+ # <div style="background-color:<%= cycle("red","white","blue") %>">
+ # <span style="background-color:<%= current_cycle %>"><%= item %></span>
+ # </div>
+ # <% end %>
+ def current_cycle(name = "default")
+ cycle = get_cycle(name)
+ cycle.current_value unless cycle.nil?
+ end
+
# Resets a cycle so that it starts from the first element the next time
# it is called. Pass in +name+ to reset a named cycle.
#
@@ -523,11 +551,29 @@ module ActionView
@index = 0
end
+ def current_value
+ @values[previous_index].to_s
+ end
+
def to_s
value = @values[@index].to_s
- @index = (@index + 1) % @values.size
+ @index = next_index
return value
end
+
+ private
+
+ def next_index
+ step_index(1)
+ end
+
+ def previous_index
+ step_index(-1)
+ end
+
+ def step_index(n)
+ (@index + n) % @values.size
+ end
end
private
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 443c49b870..373bb92dc4 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -196,7 +196,7 @@ module ActionView
path = "_#{partial_path}"
end
- pick_template(path)
+ _pick_template(path)
end
memoize :_pick_partial_template
end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index c011f21550..0134bc988f 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -1,8 +1,7 @@
module ActionView
- module Renderable
- # NOTE: The template that this mixin is beening include into is frozen
- # So you can not set or modify any instance variables
-
+ # NOTE: The template that this mixin is being included into is frozen
+ # so you cannot set or modify any instance variables
+ module Renderable #:nodoc:
extend ActiveSupport::Memoizable
def self.included(base)
@@ -26,17 +25,18 @@ module ActionView
def render(view, local_assigns = {})
compile(local_assigns)
- view._first_render ||= self
- view._last_render = self
+ view.send(:_first_render=, self) unless view.send(:_first_render)
+ view.send(:_last_render=, self)
- view.send(:evaluate_assigns)
- view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
+ view.send(:_evaluate_assigns_and_ivars)
+ view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
view.send(method_name(local_assigns), local_assigns) do |*names|
- if proc = view.instance_variable_get("@_proc_for_layout")
+ ivar = :@_proc_for_layout
+ if view.instance_variable_defined?(ivar) and proc = view.instance_variable_get(ivar)
view.capture(*names, &proc)
- else
- view.instance_variable_get("@content_for_#{names.first || 'layout'}")
+ elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}")
+ view.instance_variable_get(ivar)
end
end
end
@@ -72,12 +72,9 @@ module ActionView
end_src
begin
- logger = Base.logger
- logger.debug "Compiling template #{render_symbol}" if logger
-
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
rescue Exception => e # errors from template code
- if logger
+ if logger = defined?(ActionController) && Base.logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
index 342850f0f0..d92ff1b8d3 100644
--- a/actionpack/lib/action_view/renderable_partial.rb
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -1,8 +1,7 @@
module ActionView
- module RenderablePartial
- # NOTE: The template that this mixin is beening include into is frozen
- # So you can not set or modify any instance variables
-
+ # NOTE: The template that this mixin is being included into is frozen
+ # so you cannot set or modify any instance variables
+ module RenderablePartial #:nodoc:
extend ActiveSupport::Memoizable
def variable_name
@@ -16,15 +15,28 @@ module ActionView
memoize :counter_name
def render(view, local_assigns = {})
- ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
+ if defined? ActionController
+ ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
+ super
+ end
+ else
super
end
end
def render_partial(view, object = nil, local_assigns = {}, as = nil)
object ||= local_assigns[:object] ||
- local_assigns[variable_name] ||
- view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
+ local_assigns[variable_name]
+
+ if view.respond_to?(:controller)
+ ivar = :"@#{variable_name}"
+ object ||=
+ if view.controller.instance_variable_defined?(ivar)
+ ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
+ view.controller.instance_variable_get(ivar),
+ "#{ivar} will no longer be implicitly assigned to #{variable_name}")
+ end
+ end
# Ensure correct object is reassigned to other accessors
local_assigns[:object] = local_assigns[variable_name] = object
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 5dc6708431..64597b3d39 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,3 +1,5 @@
+require 'action_controller/mime_type'
+
module ActionView #:nodoc:
class Template
extend TemplateHandlers
diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb
index 7d24a5c423..788dc93326 100644
--- a/actionpack/lib/action_view/template_handlers/builder.rb
+++ b/actionpack/lib/action_view/template_handlers/builder.rb
@@ -6,7 +6,7 @@ module ActionView
include Compilable
def compile(template)
- "set_controller_content_type(Mime::XML);" +
+ "_set_controller_content_type(Mime::XML);" +
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index adbb37fd09..c69f9455b2 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -54,7 +54,7 @@ module ActionView
private
def method_missing(selector, *args)
controller = TestController.new
- return controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
+ return controller.__send__(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
super
end
end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 1531e7c21a..08cbcbf302 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -433,6 +433,17 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select_rjs :remove, "test1"
end
+ def test_assert_select_rjs_for_remove_offers_useful_error_when_assertion_fails
+ render_rjs do |page|
+ page.remove "test_with_typo"
+ end
+
+ assert_select_rjs :remove, "test1"
+
+ rescue Test::Unit::AssertionFailedError
+ assert_equal "No RJS statement that removes 'test1' was rendered.", $!.message
+ end
+
def test_assert_select_rjs_for_remove_ignores_block
render_rjs do |page|
page.remove "test1"
@@ -454,6 +465,17 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select_rjs :show, "test1"
end
+ def test_assert_select_rjs_for_show_offers_useful_error_when_assertion_fails
+ render_rjs do |page|
+ page.show "test_with_typo"
+ end
+
+ assert_select_rjs :show, "test1"
+
+ rescue Test::Unit::AssertionFailedError
+ assert_equal "No RJS statement that shows 'test1' was rendered.", $!.message
+ end
+
def test_assert_select_rjs_for_show_ignores_block
render_rjs do |page|
page.show "test1"
@@ -475,6 +497,17 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select_rjs :hide, "test1"
end
+ def test_assert_select_rjs_for_hide_offers_useful_error_when_assertion_fails
+ render_rjs do |page|
+ page.hide "test_with_typo"
+ end
+
+ assert_select_rjs :hide, "test1"
+
+ rescue Test::Unit::AssertionFailedError
+ assert_equal "No RJS statement that hides 'test1' was rendered.", $!.message
+ end
+
def test_assert_select_rjs_for_hide_ignores_block
render_rjs do |page|
page.hide "test1"
@@ -496,6 +529,17 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select_rjs :toggle, "test1"
end
+ def test_assert_select_rjs_for_toggle_offers_useful_error_when_assertion_fails
+ render_rjs do |page|
+ page.toggle "test_with_typo"
+ end
+
+ assert_select_rjs :toggle, "test1"
+
+ rescue Test::Unit::AssertionFailedError
+ assert_equal "No RJS statement that toggles 'test1' was rendered.", $!.message
+ end
+
def test_assert_select_rjs_for_toggle_ignores_block
render_rjs do |page|
page.toggle "test1"
@@ -555,6 +599,11 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
+ def test_assert_select_rjs_raise_errors
+ assert_raises(ArgumentError) { assert_select_rjs(:destroy) }
+ assert_raises(ArgumentError) { assert_select_rjs(:insert, :left) }
+ end
+
# Simple selection from a single result.
def test_nested_assert_select_rjs_with_single_result
render_rjs do |page|
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index d49cc2a9aa..738c016c6e 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -84,11 +84,11 @@ class ControllerInstanceTests < Test::Unit::TestCase
def test_action_methods
@empty_controllers.each do |c|
hide_mocha_methods_from_controller(c)
- assert_equal Set.new, c.send!(:action_methods), "#{c.controller_path} should be empty!"
+ assert_equal Set.new, c.__send__(:action_methods), "#{c.controller_path} should be empty!"
end
@non_empty_controllers.each do |c|
hide_mocha_methods_from_controller(c)
- assert_equal Set.new(%w(public_action)), c.send!(:action_methods), "#{c.controller_path} should not be empty!"
+ assert_equal Set.new(%w(public_action)), c.__send__(:action_methods), "#{c.controller_path} should not be empty!"
end
end
@@ -100,7 +100,7 @@ class ControllerInstanceTests < Test::Unit::TestCase
:expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object,
:stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher,
]
- controller.class.send!(:hide_action, *mocha_methods)
+ controller.class.__send__(:hide_action, *mocha_methods)
end
end
@@ -140,7 +140,7 @@ class PerformActionTest < Test::Unit::TestCase
def test_method_missing_is_not_an_action_name
use_controller MethodMissingController
- assert ! @controller.send!(:action_methods).include?('method_missing')
+ assert ! @controller.__send__(:action_methods).include?('method_missing')
get :method_missing
assert_response :success
diff --git a/actionpack/test/controller/components_test.rb b/actionpack/test/controller/components_test.rb
index 71e8a18071..4d36fc411d 100644
--- a/actionpack/test/controller/components_test.rb
+++ b/actionpack/test/controller/components_test.rb
@@ -77,49 +77,64 @@ class ComponentsTest < Test::Unit::TestCase
end
def test_calling_from_controller
- get :calling_from_controller
- assert_equal "Lady of the House, speaking", @response.body
+ assert_deprecated do
+ get :calling_from_controller
+ assert_equal "Lady of the House, speaking", @response.body
+ end
end
def test_calling_from_controller_with_params
- get :calling_from_controller_with_params
- assert_equal "David of the House, speaking", @response.body
+ assert_deprecated do
+ get :calling_from_controller_with_params
+ assert_equal "David of the House, speaking", @response.body
+ end
end
def test_calling_from_controller_with_different_status_code
- get :calling_from_controller_with_different_status_code
- assert_equal 500, @response.response_code
+ assert_deprecated do
+ get :calling_from_controller_with_different_status_code
+ assert_equal 500, @response.response_code
+ end
end
def test_calling_from_template
- get :calling_from_template
- assert_equal "Ring, ring: Lady of the House, speaking", @response.body
+ assert_deprecated do
+ get :calling_from_template
+ assert_equal "Ring, ring: Lady of the House, speaking", @response.body
+ end
end
def test_etag_is_set_for_parent_template_when_calling_from_template
- get :calling_from_template
- expected_etag = etag_for("Ring, ring: Lady of the House, speaking")
- assert_equal expected_etag, @response.headers['ETag']
+ assert_deprecated do
+ get :calling_from_template
+ expected_etag = etag_for("Ring, ring: Lady of the House, speaking")
+ assert_equal expected_etag, @response.headers['ETag']
+ end
end
def test_internal_calling
- get :internal_caller
- assert_equal "Are you there? Yes, ma'am", @response.body
+ assert_deprecated do
+ get :internal_caller
+ assert_equal "Are you there? Yes, ma'am", @response.body
+ end
end
def test_flash
- get :set_flash
- assert_equal 'My stoney baby', flash[:notice]
- get :use_flash
- assert_equal 'My stoney baby', @response.body
- get :use_flash
- assert_equal 'no flash', @response.body
+ assert_deprecated do
+ get :set_flash
+ assert_equal 'My stoney baby', flash[:notice]
+ get :use_flash
+ assert_equal 'My stoney baby', @response.body
+ get :use_flash
+ assert_equal 'no flash', @response.body
+ end
end
def test_component_redirect_redirects
- get :calling_redirected
-
- assert_redirected_to :controller=>"callee", :action => "being_called"
+ assert_deprecated do
+ get :calling_redirected
+ assert_redirected_to :controller=>"callee", :action => "being_called"
+ end
end
def test_component_multiple_redirect_redirects
@@ -128,9 +143,10 @@ class ComponentsTest < Test::Unit::TestCase
end
def test_component_as_string_redirect_renders_redirected_action
- get :calling_redirected_as_string
-
- assert_equal "Lady of the House, speaking", @response.body
+ assert_deprecated do
+ get :calling_redirected_as_string
+ assert_equal "Lady of the House, speaking", @response.body
+ end
end
protected
diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb
index c4de10181d..0b259a7980 100644
--- a/actionpack/test/controller/filter_params_test.rb
+++ b/actionpack/test/controller/filter_params_test.rb
@@ -27,7 +27,7 @@ class FilterParamTest < Test::Unit::TestCase
test_hashes.each do |before_filter, after_filter, filter_words|
FilterParamController.filter_parameter_logging(*filter_words)
- assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
+ assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
filter_words.push('blah')
FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
@@ -37,7 +37,7 @@ class FilterParamTest < Test::Unit::TestCase
before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
- assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
+ assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 3652c482f1..dafa344473 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -111,15 +111,15 @@ class FilterTest < Test::Unit::TestCase
end
class OnlyConditionProcController < ConditionalFilterController
- before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
+ before_filter(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_filter", true) }
end
class ExceptConditionProcController < ConditionalFilterController
- before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true }
+ before_filter(:except => :show_without_filter) {|c| c.instance_variable_set(:"@ran_proc_filter", true) }
end
class ConditionalClassFilter
- def self.filter(controller) controller.assigns["ran_class_filter"] = true end
+ def self.filter(controller) controller.instance_variable_set(:"@ran_class_filter", true) end
end
class OnlyConditionClassController < ConditionalFilterController
@@ -131,7 +131,7 @@ class FilterTest < Test::Unit::TestCase
end
class AnomolousYetValidConditionController < ConditionalFilterController
- before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
+ before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)}
end
class ConditionalOptionsFilter < ConditionalFilterController
@@ -225,16 +225,16 @@ class FilterTest < Test::Unit::TestCase
end
class ProcController < PrependingController
- before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
+ before_filter(proc { |c| c.instance_variable_set(:"@ran_proc_filter", true) })
end
class ImplicitProcController < PrependingController
- before_filter { |c| c.assigns["ran_proc_filter"] = true }
+ before_filter { |c| c.instance_variable_set(:"@ran_proc_filter", true) }
end
class AuditFilter
def self.filter(controller)
- controller.assigns["was_audited"] = true
+ controller.instance_variable_set(:"@was_audited", true)
end
end
@@ -242,12 +242,12 @@ class FilterTest < Test::Unit::TestCase
def before(controller)
@execution_log = "before"
controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
- controller.assigns["before_ran"] = true
+ controller.instance_variable_set(:"@before_ran", true)
end
def after(controller)
- controller.assigns["execution_log"] = @execution_log + " and after"
- controller.assigns["after_ran"] = true
+ controller.instance_variable_set(:"@execution_log", @execution_log + " and after")
+ controller.instance_variable_set(:"@after_ran", true)
controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
end
end
@@ -364,7 +364,7 @@ class FilterTest < Test::Unit::TestCase
begin
yield
rescue ErrorToRescue => ex
- controller.send! :render, :text => "I rescued this: #{ex.inspect}"
+ controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
end
end
end
@@ -726,9 +726,9 @@ end
class ControllerWithProcFilter < PostsController
around_filter(:only => :no_raise) do |c,b|
- c.assigns['before'] = true
+ c.instance_variable_set(:"@before", true)
b.call
- c.assigns['after'] = true
+ c.instance_variable_set(:"@after", true)
end
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index c986941140..7e4c3e171a 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -243,7 +243,7 @@ class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest
reset!
stub_integration_session(@integration_session)
%w( get post head put delete ).each do |verb|
- assert_nothing_raised("'#{verb}' should use integration test methods") { send!(verb, '/') }
+ assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 71f110f241..1120fdbff5 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -79,53 +79,6 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase
end
end
-class ExemptFromLayoutTest < Test::Unit::TestCase
- def setup
- @controller = LayoutTest.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
- def test_rjs_exempt_from_layout
- assert @controller.send!(:template_exempt_from_layout?, 'test.rjs')
- end
-
- def test_rhtml_and_rxml_not_exempt_from_layout
- assert !@controller.send!(:template_exempt_from_layout?, 'test.rhtml')
- assert !@controller.send!(:template_exempt_from_layout?, 'test.rxml')
- end
-
- def test_other_extension_not_exempt_from_layout
- assert !@controller.send!(:template_exempt_from_layout?, 'test.random')
- end
-
- def test_add_extension_to_exempt_from_layout
- ['rpdf', :rpdf].each do |ext|
- assert_nothing_raised do
- ActionController::Base.exempt_from_layout ext
- end
- assert @controller.send!(:template_exempt_from_layout?, "test.#{ext}")
- end
- end
-
- def test_add_regexp_to_exempt_from_layout
- ActionController::Base.exempt_from_layout /\.rdoc/
- assert @controller.send!(:template_exempt_from_layout?, 'test.rdoc')
- end
-
- def test_rhtml_exempt_from_layout_status_should_prevent_layout_render
- ActionController::Base.exempt_from_layout :rhtml
-
- assert @controller.send!(:template_exempt_from_layout?, 'test.rhtml')
- assert @controller.send!(:template_exempt_from_layout?, 'hello.rhtml')
-
- get :hello
- assert_equal 'hello.rhtml', @response.body
- ActionController::Base.exempt_from_layout.delete(/\.rhtml$/)
- end
-end
-
-
class DefaultLayoutController < LayoutTest
end
@@ -179,8 +132,6 @@ class LayoutSetInResponseTest < Test::Unit::TestCase
ActionController::Base.exempt_from_layout :rhtml
@controller = RenderWithTemplateOptionController.new
- assert @controller.send(:template_exempt_from_layout?, 'alt/hello.rhtml')
-
get :hello
assert_equal "alt/hello.rhtml", @response.body.strip
diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb
deleted file mode 100644
index 82919b7777..0000000000
--- a/actionpack/test/controller/new_render_test.rb
+++ /dev/null
@@ -1,981 +0,0 @@
-require 'abstract_unit'
-require 'controller/fake_models'
-
-class CustomersController < ActionController::Base
-end
-
-module Fun
- class GamesController < ActionController::Base
- def hello_world
- end
- end
-end
-
-module NewRenderTestHelper
- def rjs_helper_method_from_module
- page.visual_effect :highlight
- end
-end
-
-class LabellingFormBuilder < ActionView::Helpers::FormBuilder
-end
-
-class NewRenderTestController < ActionController::Base
- layout :determine_layout
-
- def self.controller_name; "test"; end
- def self.controller_path; "test"; end
-
- def hello_world
- end
-
- def render_hello_world
- render :template => "test/hello_world"
- end
-
- def render_hello_world_from_variable
- @person = "david"
- render :text => "hello #{@person}"
- end
-
- def render_action_hello_world
- render :action => "hello_world"
- end
-
- def render_action_hello_world_as_symbol
- render :action => :hello_world
- end
-
- def render_text_hello_world
- render :text => "hello world"
- end
-
- def render_text_hello_world_with_layout
- @variable_for_layout = ", I'm here!"
- render :text => "hello world", :layout => true
- end
-
- def hello_world_with_layout_false
- render :layout => false
- end
-
- def render_custom_code
- render :text => "hello world", :status => "404 Moved"
- end
-
- def render_file_with_instance_variables
- @secret = 'in the sauce'
- path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
- render :file => path
- end
-
- def render_file_from_template
- @secret = 'in the sauce'
- @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
- end
-
- def render_file_with_locals
- path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
- render :file => path, :locals => {:secret => 'in the sauce'}
- end
-
- def render_file_not_using_full_path
- @secret = 'in the sauce'
- render :file => 'test/render_file_with_ivar'
- end
-
- def render_file_not_using_full_path_with_dot_in_path
- @secret = 'in the sauce'
- render :file => 'test/dot.directory/render_file_with_ivar'
- end
-
- def render_xml_hello
- @name = "David"
- render :template => "test/hello"
- end
-
- def greeting
- # let's just rely on the template
- end
-
- def layout_test
- render :action => "hello_world"
- end
-
- def layout_test_with_different_layout
- render :action => "hello_world", :layout => "standard"
- end
-
- def rendering_without_layout
- render :action => "hello_world", :layout => false
- end
-
- def layout_overriding_layout
- render :action => "hello_world", :layout => "standard"
- end
-
- def rendering_nothing_on_layout
- render :nothing => true
- end
-
- def builder_layout_test
- render :action => "hello"
- end
-
- def partials_list
- @test_unchanged = 'hello'
- @customers = [ Customer.new("david"), Customer.new("mary") ]
- render :action => "list"
- end
-
- def partial_only
- render :partial => true
- end
-
- def partial_only_with_layout
- render :partial => "partial_only", :layout => true
- end
-
- def partial_with_counter
- render :partial => "counter", :locals => { :counter_counter => 5 }
- end
-
- def partial_with_locals
- render :partial => "customer", :locals => { :customer => Customer.new("david") }
- end
-
- def partial_with_form_builder
- render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, @template, {}, Proc.new {})
- end
-
- def partial_with_form_builder_subclass
- render :partial => LabellingFormBuilder.new(:post, nil, @template, {}, Proc.new {})
- end
-
- def partial_collection
- render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
- end
-
- def partial_collection_with_as
- render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer
- end
-
- def partial_collection_with_spacer
- render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ]
- end
-
- def partial_collection_with_counter
- render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
- end
-
- def partial_collection_with_locals
- render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
- end
-
- def partial_collection_shorthand_with_locals
- render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
- end
-
- def partial_collection_shorthand_with_different_types_of_records
- render :partial => [
- BadCustomer.new("mark"),
- GoodCustomer.new("craig"),
- BadCustomer.new("john"),
- GoodCustomer.new("zach"),
- GoodCustomer.new("brandon"),
- BadCustomer.new("dan") ],
- :locals => { :greeting => "Bonjour" }
- end
-
- def partial_collection_shorthand_with_different_types_of_records_with_counter
- partial_collection_shorthand_with_different_types_of_records
- end
-
- def empty_partial_collection
- render :partial => "customer", :collection => []
- end
-
- def partial_with_hash_object
- render :partial => "hash_object", :object => {:first_name => "Sam"}
- end
-
- def partial_hash_collection
- render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ]
- end
-
- def partial_hash_collection_with_locals
- render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" }
- end
-
- def partial_with_implicit_local_assignment
- @customer = Customer.new("Marcel")
- render :partial => "customer"
- end
-
- def missing_partial
- render :partial => 'thisFileIsntHere'
- end
-
- def hello_in_a_string
- @customers = [ Customer.new("david"), Customer.new("mary") ]
- render :text => "How's there? " << render_to_string(:template => "test/list")
- end
-
- def render_to_string_with_assigns
- @before = "i'm before the render"
- render_to_string :text => "foo"
- @after = "i'm after the render"
- render :action => "test/hello_world"
- end
-
- def render_to_string_with_partial
- @partial_only = render_to_string :partial => "partial_only"
- @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
- render :action => "test/hello_world"
- end
-
- def render_to_string_with_exception
- render_to_string :file => "exception that will not be caught - this will certainly not work"
- end
-
- def render_to_string_with_caught_exception
- @before = "i'm before the render"
- begin
- render_to_string :file => "exception that will be caught- hope my future instance vars still work!"
- rescue
- end
- @after = "i'm after the render"
- render :action => "test/hello_world"
- end
-
- def accessing_params_in_template
- render :inline => "Hello: <%= params[:name] %>"
- end
-
- def accessing_request_in_template
- render :inline => "Hello: <%= request.host %>"
- end
-
- def accessing_logger_in_template
- render :inline => "<%= logger.class %>"
- end
-
- def accessing_action_name_in_template
- render :inline => "<%= action_name %>"
- end
-
- def accessing_controller_name_in_template
- render :inline => "<%= controller_name %>"
- end
-
- def accessing_params_in_template_with_layout
- render :layout => nil, :inline => "Hello: <%= params[:name] %>"
- end
-
- def render_with_explicit_template
- render :template => "test/hello_world"
- end
-
- def render_with_explicit_template_with_locals
- render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' }
- end
-
- def double_render
- render :text => "hello"
- render :text => "world"
- end
-
- def double_redirect
- redirect_to :action => "double_render"
- redirect_to :action => "double_render"
- end
-
- def render_and_redirect
- render :text => "hello"
- redirect_to :action => "double_render"
- end
-
- def render_to_string_and_render
- @stuff = render_to_string :text => "here is some cached stuff"
- render :text => "Hi web users! #{@stuff}"
- end
-
- def rendering_with_conflicting_local_vars
- @name = "David"
- def @template.name() nil end
- render :action => "potential_conflicts"
- end
-
- def hello_world_from_rxml_using_action
- render :action => "hello_world_from_rxml.builder"
- end
-
- def hello_world_from_rxml_using_template
- render :template => "test/hello_world_from_rxml.builder"
- end
-
- def head_with_location_header
- head :location => "/foo"
- end
-
- def head_with_symbolic_status
- head :status => params[:status].intern
- end
-
- def head_with_integer_status
- head :status => params[:status].to_i
- end
-
- def head_with_string_status
- head :status => params[:status]
- end
-
- def head_with_custom_header
- head :x_custom_header => "something"
- end
-
- def head_with_status_code_first
- head :forbidden, :x_custom_header => "something"
- end
-
- def render_with_location
- render :xml => "<hello/>", :location => "http://example.com", :status => 201
- end
-
- def render_with_object_location
- customer = Customer.new("Some guy", 1)
- render :xml => "<customer/>", :location => customer_url(customer), :status => :created
- end
-
- def render_with_to_xml
- to_xmlable = Class.new do
- def to_xml
- "<i-am-xml/>"
- end
- end.new
-
- render :xml => to_xmlable
- end
-
- helper NewRenderTestHelper
- helper do
- def rjs_helper_method(value)
- page.visual_effect :highlight, value
- end
- end
-
- def enum_rjs_test
- render :update do |page|
- page.select('.product').each do |value|
- page.rjs_helper_method_from_module
- page.rjs_helper_method(value)
- page.sortable(value, :url => { :action => "order" })
- page.draggable(value)
- end
- end
- end
-
- def delete_with_js
- @project_id = 4
- end
-
- def render_js_with_explicit_template
- @project_id = 4
- render :template => 'test/delete_with_js'
- end
-
- def render_js_with_explicit_action_template
- @project_id = 4
- render :action => 'delete_with_js'
- end
-
- def update_page
- render :update do |page|
- page.replace_html 'balance', '$37,000,000.00'
- page.visual_effect :highlight, 'balance'
- end
- end
-
- def update_page_with_instance_variables
- @money = '$37,000,000.00'
- @div_id = 'balance'
- render :update do |page|
- page.replace_html @div_id, @money
- page.visual_effect :highlight, @div_id
- end
- end
-
- def action_talk_to_layout
- # Action template sets variable that's picked up by layout
- end
-
- def render_text_with_assigns
- @hello = "world"
- render :text => "foo"
- end
-
- def yield_content_for
- render :action => "content_for", :layout => "yield"
- end
-
- def render_content_type_from_body
- response.content_type = Mime::RSS
- render :text => "hello world!"
- end
-
- def render_call_to_partial_with_layout
- render :action => "calling_partial_with_layout"
- end
-
- def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
- render :action => "calling_partial_with_layout"
- end
-
- def render_using_layout_around_block
- render :action => "using_layout_around_block"
- end
-
- def render_using_layout_around_block_with_args
- render :action => "using_layout_around_block_with_args"
- end
-
- def render_using_layout_around_block_in_main_layout_and_within_content_for_layout
- render :action => "using_layout_around_block"
- end
-
- def rescue_action(e) raise end
-
- private
- def determine_layout
- case action_name
- when "hello_world", "layout_test", "rendering_without_layout",
- "rendering_nothing_on_layout", "render_text_hello_world",
- "render_text_hello_world_with_layout",
- "hello_world_with_layout_false",
- "partial_only", "partial_only_with_layout",
- "accessing_params_in_template",
- "accessing_params_in_template_with_layout",
- "render_with_explicit_template",
- "render_js_with_explicit_template",
- "render_js_with_explicit_action_template",
- "delete_with_js", "update_page", "update_page_with_instance_variables"
-
- "layouts/standard"
- when "builder_layout_test"
- "layouts/builder"
- when "action_talk_to_layout", "layout_overriding_layout"
- "layouts/talk_from_action"
- when "render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout"
- "layouts/partial_with_layout"
- when "render_using_layout_around_block_in_main_layout_and_within_content_for_layout"
- "layouts/block_with_layout"
- end
- end
-end
-
-class NewRenderTest < Test::Unit::TestCase
- def setup
- @controller = NewRenderTestController.new
-
- # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
- # a more accurate simulation of what happens in "real life".
- @controller.logger = Logger.new(nil)
-
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
- @request.host = "www.nextangle.com"
- end
-
- def test_simple_show
- get :hello_world
- assert_response :success
- assert_template "test/hello_world"
- assert_equal "<html>Hello world!</html>", @response.body
- end
-
- def test_renders_default_template_for_missing_action
- get :'hyphen-ated'
- assert_template 'test/hyphen-ated'
- end
-
- def test_do_with_render
- get :render_hello_world
- assert_template "test/hello_world"
- end
-
- def test_do_with_render_from_variable
- get :render_hello_world_from_variable
- assert_equal "hello david", @response.body
- end
-
- def test_do_with_render_action
- get :render_action_hello_world
- assert_template "test/hello_world"
- end
-
- def test_do_with_render_action_as_symbol
- get :render_action_hello_world_as_symbol
- assert_template "test/hello_world"
- end
-
- def test_do_with_render_text
- get :render_text_hello_world
- assert_equal "hello world", @response.body
- end
-
- def test_do_with_render_text_and_layout
- get :render_text_hello_world_with_layout
- assert_equal "<html>hello world, I'm here!</html>", @response.body
- end
-
- def test_do_with_render_action_and_layout_false
- get :hello_world_with_layout_false
- assert_equal 'Hello world!', @response.body
- end
-
- def test_do_with_render_custom_code
- get :render_custom_code
- assert_response :missing
- end
-
- def test_render_file_with_instance_variables
- get :render_file_with_instance_variables
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- def test_render_file_not_using_full_path
- get :render_file_not_using_full_path
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- def test_render_file_not_using_full_path_with_dot_in_path
- get :render_file_not_using_full_path_with_dot_in_path
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- def test_render_file_with_locals
- get :render_file_with_locals
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- def test_render_file_from_template
- get :render_file_from_template
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- def test_attempt_to_access_object_method
- assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
- end
-
- def test_private_methods
- assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
- end
-
- def test_access_to_request_in_view
- get :accessing_request_in_template
- assert_equal "Hello: www.nextangle.com", @response.body
- end
-
- def test_access_to_logger_in_view
- get :accessing_logger_in_template
- assert_equal "Logger", @response.body
- end
-
- def test_access_to_action_name_in_view
- get :accessing_action_name_in_template
- assert_equal "accessing_action_name_in_template", @response.body
- end
-
- def test_access_to_controller_name_in_view
- get :accessing_controller_name_in_template
- assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller.
- end
-
- def test_render_xml
- get :render_xml_hello
- assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
- end
-
- def test_enum_rjs_test
- get :enum_rjs_test
- assert_equal <<-EOS.strip, @response.body
-$$(".product").each(function(value, index) {
-new Effect.Highlight(element,{});
-new Effect.Highlight(value,{});
-Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
-new Draggable(value, {});
-});
-EOS
- end
-
- def test_render_xml_with_default
- get :greeting
- assert_equal "<p>This is grand!</p>\n", @response.body
- end
-
- def test_render_with_default_from_accept_header
- xhr :get, :greeting
- assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
- end
-
- def test_render_rjs_with_default
- get :delete_with_js
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_render_rjs_template_explicitly
- get :render_js_with_explicit_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_rendering_rjs_action_explicitly
- get :render_js_with_explicit_action_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_layout_rendering
- get :layout_test
- assert_equal "<html>Hello world!</html>", @response.body
- end
-
- def test_layout_test_with_different_layout
- get :layout_test_with_different_layout
- assert_equal "<html>Hello world!</html>", @response.body
- end
-
- def test_rendering_without_layout
- get :rendering_without_layout
- assert_equal "Hello world!", @response.body
- end
-
- def test_layout_overriding_layout
- get :layout_overriding_layout
- assert_no_match %r{<title>}, @response.body
- end
-
- def test_rendering_nothing_on_layout
- get :rendering_nothing_on_layout
- assert_equal " ", @response.body
- end
-
- def test_render_xml_with_layouts
- get :builder_layout_test
- assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
- end
-
- def test_partial_only
- get :partial_only
- assert_equal "only partial", @response.body
- end
-
- def test_partial_only_with_layout
- get :partial_only_with_layout
- assert_equal "<html>only partial</html>", @response.body
- end
-
- def test_render_to_string
- assert_not_deprecated { get :hello_in_a_string }
- assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
- end
-
- def test_render_to_string_doesnt_break_assigns
- get :render_to_string_with_assigns
- assert_equal "i'm before the render", assigns(:before)
- assert_equal "i'm after the render", assigns(:after)
- end
-
- def test_render_to_string_partial
- get :render_to_string_with_partial
- assert_equal "only partial", assigns(:partial_only)
- assert_equal "Hello: david", assigns(:partial_with_locals)
- end
-
- def test_bad_render_to_string_still_throws_exception
- assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception }
- end
-
- def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
- assert_nothing_raised { get :render_to_string_with_caught_exception }
- assert_equal "i'm before the render", assigns(:before)
- assert_equal "i'm after the render", assigns(:after)
- end
-
- def test_nested_rendering
- get :hello_world
- assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
- end
-
- def test_accessing_params_in_template
- get :accessing_params_in_template, :name => "David"
- assert_equal "Hello: David", @response.body
- end
-
- def test_accessing_params_in_template_with_layout
- get :accessing_params_in_template_with_layout, :name => "David"
- assert_equal "<html>Hello: David</html>", @response.body
- end
-
- def test_render_with_explicit_template
- get :render_with_explicit_template
- assert_response :success
- end
-
- def test_double_render
- assert_raises(ActionController::DoubleRenderError) { get :double_render }
- end
-
- def test_double_redirect
- assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
- end
-
- def test_render_and_redirect
- assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
- end
-
- # specify the one exception to double render rule - render_to_string followed by render
- def test_render_to_string_and_render
- get :render_to_string_and_render
- assert_equal("Hi web users! here is some cached stuff", @response.body)
- end
-
- def test_rendering_with_conflicting_local_vars
- get :rendering_with_conflicting_local_vars
- assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
- end
-
- def test_action_talk_to_layout
- get :action_talk_to_layout
- assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
- end
-
- def test_partial_with_counter
- get :partial_with_counter
- assert_equal "5", @response.body
- end
-
- def test_partials_list
- get :partials_list
- assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
- end
-
- def test_partial_with_locals
- get :partial_with_locals
- assert_equal "Hello: david", @response.body
- end
-
- def test_partial_with_form_builder
- get :partial_with_form_builder
- assert_match(/<label/, @response.body)
- assert_template('test/_form')
- end
-
- def test_partial_with_form_builder_subclass
- get :partial_with_form_builder_subclass
- assert_match(/<label/, @response.body)
- assert_template('test/_labelling_form')
- end
-
- def test_partial_collection
- get :partial_collection
- assert_equal "Hello: davidHello: mary", @response.body
- end
-
- def test_partial_collection_with_as
- get :partial_collection_with_as
- assert_equal "david david davidmary mary mary", @response.body
- end
-
- def test_partial_collection_with_counter
- get :partial_collection_with_counter
- assert_equal "david0mary1", @response.body
- end
-
- def test_partial_collection_with_locals
- get :partial_collection_with_locals
- assert_equal "Bonjour: davidBonjour: mary", @response.body
- end
-
- def test_partial_collection_with_spacer
- get :partial_collection_with_spacer
- assert_equal "Hello: davidonly partialHello: mary", @response.body
- end
-
- def test_partial_collection_shorthand_with_locals
- get :partial_collection_shorthand_with_locals
- assert_equal "Bonjour: davidBonjour: mary", @response.body
- end
-
- def test_partial_collection_shorthand_with_different_types_of_records
- get :partial_collection_shorthand_with_different_types_of_records
- assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body
- end
-
- def test_empty_partial_collection
- get :empty_partial_collection
- assert_equal " ", @response.body
- end
-
- def test_partial_with_hash_object
- get :partial_with_hash_object
- assert_equal "Sam\nmaS\n", @response.body
- end
-
- def test_hash_partial_collection
- get :partial_hash_collection
- assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body
- end
-
- def test_partial_hash_collection_with_locals
- get :partial_hash_collection_with_locals
- assert_equal "Hola: PratikHola: Amy", @response.body
- end
-
- def test_partial_with_implicit_local_assignment
- get :partial_with_implicit_local_assignment
- assert_equal "Hello: Marcel", @response.body
- end
-
- def test_render_missing_partial_template
- assert_raises(ActionView::MissingTemplate) do
- get :missing_partial
- end
- end
-
- def test_render_text_with_assigns
- get :render_text_with_assigns
- assert_equal "world", assigns["hello"]
- end
-
- def test_template_with_locals
- get :render_with_explicit_template_with_locals
- assert_equal "The secret is area51\n", @response.body
- end
-
- def test_update_page
- get :update_page
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
- assert_equal 2, @response.body.split($/).length
- end
-
- def test_update_page_with_instance_variables
- get :update_page_with_instance_variables
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
- assert_match /balance/, @response.body
- assert_match /\$37/, @response.body
- end
-
- def test_yield_content_for
- assert_not_deprecated { get :yield_content_for }
- assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
- end
-
-
- def test_overwritting_rendering_relative_file_with_extension
- get :hello_world_from_rxml_using_template
- assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
-
- get :hello_world_from_rxml_using_action
- assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
- end
-
-
- def test_head_with_location_header
- get :head_with_location_header
- assert @response.body.blank?
- assert_equal "/foo", @response.headers["Location"]
- assert_response :ok
- end
-
- def test_head_with_custom_header
- get :head_with_custom_header
- assert @response.body.blank?
- assert_equal "something", @response.headers["X-Custom-Header"]
- assert_response :ok
- end
-
- def test_head_with_symbolic_status
- get :head_with_symbolic_status, :status => "ok"
- assert_equal "200 OK", @response.headers["Status"]
- assert_response :ok
-
- get :head_with_symbolic_status, :status => "not_found"
- assert_equal "404 Not Found", @response.headers["Status"]
- assert_response :not_found
-
- ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
- get :head_with_symbolic_status, :status => status.to_s
- assert_equal code, @response.response_code
- assert_response status
- end
- end
-
- def test_head_with_integer_status
- ActionController::StatusCodes::STATUS_CODES.each do |code, message|
- get :head_with_integer_status, :status => code.to_s
- assert_equal message, @response.message
- end
- end
-
- def test_head_with_string_status
- get :head_with_string_status, :status => "404 Eat Dirt"
- assert_equal 404, @response.response_code
- assert_equal "Eat Dirt", @response.message
- assert_response :not_found
- end
-
- def test_head_with_status_code_first
- get :head_with_status_code_first
- assert_equal 403, @response.response_code
- assert_equal "Forbidden", @response.message
- assert_equal "something", @response.headers["X-Custom-Header"]
- assert_response :forbidden
- end
-
- def test_rendering_with_location_should_set_header
- get :render_with_location
- assert_equal "http://example.com", @response.headers["Location"]
- end
-
- def test_rendering_xml_should_call_to_xml_if_possible
- get :render_with_to_xml
- assert_equal "<i-am-xml/>", @response.body
- end
-
- def test_rendering_with_object_location_should_set_header_with_url_for
- ActionController::Routing::Routes.draw do |map|
- map.resources :customers
- map.connect ':controller/:action/:id'
- end
-
- get :render_with_object_location
- assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
- end
-
- def test_render_call_to_partial_with_layout
- get :render_call_to_partial_with_layout
- assert_equal "Before (David)\nInside from partial (David)\nAfter", @response.body
- end
-
- def test_render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
- get :render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
- assert_equal "Before (Anthony)\nInside from partial (Anthony)\nAfter\nBefore (David)\nInside from partial (David)\nAfter\nBefore (Ramm)\nInside from partial (Ramm)\nAfter", @response.body
- end
-
- def test_using_layout_around_block
- get :render_using_layout_around_block
- assert_equal "Before (David)\nInside from block\nAfter", @response.body
- end
-
- def test_using_layout_around_block_in_main_layout_and_within_content_for_layout
- get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout
- assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body
- end
-
- def test_using_layout_around_block_with_args
- get :render_using_layout_around_block_with_args
- assert_equal "Before\narg1arg2\nAfter", @response.body
- end
-end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3008f5ca03..af7b5dde62 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -8,7 +8,22 @@ module Fun
end
end
+class MockLogger
+ attr_reader :logged
+
+ def initialize
+ @logged = []
+ end
+
+ def method_missing(method, *args)
+ @logged << args.first
+ end
+end
+
class TestController < ActionController::Base
+ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
+ end
+
layout :determine_layout
def hello_world
@@ -58,6 +73,57 @@ class TestController < ActionController::Base
render :text => "hello world"
end
+ def render_text_hello_world_with_layout
+ @variable_for_layout = ", I'm here!"
+ render :text => "hello world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :layout => false
+ end
+
+ def render_file_with_instance_variables
+ @secret = 'in the sauce'
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
+ render :file => path
+ end
+
+ def render_file_not_using_full_path
+ @secret = 'in the sauce'
+ render :file => 'test/render_file_with_ivar'
+ end
+
+ def render_file_not_using_full_path_with_dot_in_path
+ @secret = 'in the sauce'
+ render :file => 'test/dot.directory/render_file_with_ivar'
+ end
+
+ def render_file_from_template
+ @secret = 'in the sauce'
+ @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
+ end
+
+ def render_file_with_locals
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
+ render :file => path, :locals => {:secret => 'in the sauce'}
+ end
+
+ def accessing_request_in_template
+ render :inline => "Hello: <%= request.host %>"
+ end
+
+ def accessing_logger_in_template
+ render :inline => "<%= logger.class %>"
+ end
+
+ def accessing_action_name_in_template
+ render :inline => "<%= action_name %>"
+ end
+
+ def accessing_controller_name_in_template
+ render :inline => "<%= controller_name %>"
+ end
+
def render_json_hello_world
render :json => {:hello => 'world'}.to_json
end
@@ -126,7 +192,7 @@ class TestController < ActionController::Base
end
def builder_layout_test
- render :action => "hello"
+ render :action => "hello", :layout => "layouts/builder"
end
def builder_partial_test
@@ -168,8 +234,232 @@ class TestController < ActionController::Base
@foo = render_to_string :inline => "this is a test"
end
- def partial
- render :partial => 'partial'
+ def default_render
+ if @alternate_default_render
+ @alternate_default_render.call
+ else
+ render
+ end
+ end
+
+ def render_action_hello_world_as_symbol
+ render :action => :hello_world
+ end
+
+ def layout_test_with_different_layout
+ render :action => "hello_world", :layout => "standard"
+ end
+
+ def rendering_without_layout
+ render :action => "hello_world", :layout => false
+ end
+
+ def layout_overriding_layout
+ render :action => "hello_world", :layout => "standard"
+ end
+
+ def rendering_nothing_on_layout
+ render :nothing => true
+ end
+
+ def render_to_string_with_assigns
+ @before = "i'm before the render"
+ render_to_string :text => "foo"
+ @after = "i'm after the render"
+ render :action => "test/hello_world"
+ end
+
+ def render_to_string_with_exception
+ render_to_string :file => "exception that will not be caught - this will certainly not work"
+ end
+
+ def render_to_string_with_caught_exception
+ @before = "i'm before the render"
+ begin
+ render_to_string :file => "exception that will be caught- hope my future instance vars still work!"
+ rescue
+ end
+ @after = "i'm after the render"
+ render :action => "test/hello_world"
+ end
+
+ def accessing_params_in_template_with_layout
+ render :layout => nil, :inline => "Hello: <%= params[:name] %>"
+ end
+
+ def render_with_explicit_template
+ render :template => "test/hello_world"
+ end
+
+ def render_with_explicit_template_with_locals
+ render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' }
+ end
+
+ def double_render
+ render :text => "hello"
+ render :text => "world"
+ end
+
+ def double_redirect
+ redirect_to :action => "double_render"
+ redirect_to :action => "double_render"
+ end
+
+ def render_and_redirect
+ render :text => "hello"
+ redirect_to :action => "double_render"
+ end
+
+ def render_to_string_and_render
+ @stuff = render_to_string :text => "here is some cached stuff"
+ render :text => "Hi web users! #{@stuff}"
+ end
+
+ def rendering_with_conflicting_local_vars
+ @name = "David"
+ def @template.name() nil end
+ render :action => "potential_conflicts"
+ end
+
+ def hello_world_from_rxml_using_action
+ render :action => "hello_world_from_rxml.builder"
+ end
+
+ def hello_world_from_rxml_using_template
+ render :template => "test/hello_world_from_rxml.builder"
+ end
+
+ module RenderTestHelper
+ def rjs_helper_method_from_module
+ page.visual_effect :highlight
+ end
+ end
+
+ helper RenderTestHelper
+ helper do
+ def rjs_helper_method(value)
+ page.visual_effect :highlight, value
+ end
+ end
+
+ def enum_rjs_test
+ render :update do |page|
+ page.select('.product').each do |value|
+ page.rjs_helper_method_from_module
+ page.rjs_helper_method(value)
+ page.sortable(value, :url => { :action => "order" })
+ page.draggable(value)
+ end
+ end
+ end
+
+ def delete_with_js
+ @project_id = 4
+ end
+
+ def render_js_with_explicit_template
+ @project_id = 4
+ render :template => 'test/delete_with_js'
+ end
+
+ def render_js_with_explicit_action_template
+ @project_id = 4
+ render :action => 'delete_with_js'
+ end
+
+ def update_page
+ render :update do |page|
+ page.replace_html 'balance', '$37,000,000.00'
+ page.visual_effect :highlight, 'balance'
+ end
+ end
+
+ def update_page_with_instance_variables
+ @money = '$37,000,000.00'
+ @div_id = 'balance'
+ render :update do |page|
+ page.replace_html @div_id, @money
+ page.visual_effect :highlight, @div_id
+ end
+ end
+
+ def update_page_with_view_method
+ render :update do |page|
+ page.replace_html 'person', pluralize(2, 'person')
+ end
+ end
+
+ def action_talk_to_layout
+ # Action template sets variable that's picked up by layout
+ end
+
+ def render_text_with_assigns
+ @hello = "world"
+ render :text => "foo"
+ end
+
+ def yield_content_for
+ render :action => "content_for", :layout => "yield"
+ end
+
+ def render_content_type_from_body
+ response.content_type = Mime::RSS
+ render :text => "hello world!"
+ end
+
+ def head_with_location_header
+ head :location => "/foo"
+ end
+
+ def head_with_symbolic_status
+ head :status => params[:status].intern
+ end
+
+ def head_with_integer_status
+ head :status => params[:status].to_i
+ end
+
+ def head_with_string_status
+ head :status => params[:status]
+ end
+
+ def head_with_custom_header
+ head :x_custom_header => "something"
+ end
+
+ def head_with_status_code_first
+ head :forbidden, :x_custom_header => "something"
+ end
+
+ def render_with_location
+ render :xml => "<hello/>", :location => "http://example.com", :status => 201
+ end
+
+ def render_with_object_location
+ customer = Customer.new("Some guy", 1)
+ render :xml => "<customer/>", :location => customer_url(customer), :status => :created
+ end
+
+ def render_with_to_xml
+ to_xmlable = Class.new do
+ def to_xml
+ "<i-am-xml/>"
+ end
+ end.new
+
+ render :xml => to_xmlable
+ end
+
+ def render_using_layout_around_block
+ render :action => "using_layout_around_block"
+ end
+
+ def render_using_layout_around_block_with_args
+ render :action => "using_layout_around_block_with_args"
+ end
+
+ def render_using_layout_around_block_in_main_layout_and_within_content_for_layout
+ render :action => "using_layout_around_block", :layout => "layouts/block_with_layout"
end
def partial_dot_html
@@ -192,12 +482,8 @@ class TestController < ActionController::Base
end
end
- def default_render
- if @alternate_default_render
- @alternate_default_render.call
- else
- render
- end
+ def partial
+ render :partial => 'partial'
end
def render_alternate_default
@@ -209,14 +495,126 @@ class TestController < ActionController::Base
end
end
- def rescue_action(e) raise end
+ def partial_only_with_layout
+ render :partial => "partial_only", :layout => true
+ end
+
+ def render_to_string_with_partial
+ @partial_only = render_to_string :partial => "partial_only"
+ @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
+ render :action => "test/hello_world"
+ end
+
+ def partial_with_counter
+ render :partial => "counter", :locals => { :counter_counter => 5 }
+ end
+
+ def partial_with_locals
+ render :partial => "customer", :locals => { :customer => Customer.new("david") }
+ end
+
+ def partial_with_form_builder
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, @template, {}, Proc.new {})
+ end
+
+ def partial_with_form_builder_subclass
+ render :partial => LabellingFormBuilder.new(:post, nil, @template, {}, Proc.new {})
+ end
+
+ def partial_collection
+ render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
+ end
+
+ def partial_collection_with_as
+ render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer
+ end
+
+ def partial_collection_with_counter
+ render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
+ end
+
+ def partial_collection_with_locals
+ render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
+ end
+
+ def partial_collection_with_spacer
+ render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ]
+ end
+
+ def partial_collection_shorthand_with_locals
+ render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
+ end
+
+ def partial_collection_shorthand_with_different_types_of_records
+ render :partial => [
+ BadCustomer.new("mark"),
+ GoodCustomer.new("craig"),
+ BadCustomer.new("john"),
+ GoodCustomer.new("zach"),
+ GoodCustomer.new("brandon"),
+ BadCustomer.new("dan") ],
+ :locals => { :greeting => "Bonjour" }
+ end
+
+ def empty_partial_collection
+ render :partial => "customer", :collection => []
+ end
+
+ def partial_collection_shorthand_with_different_types_of_records_with_counter
+ partial_collection_shorthand_with_different_types_of_records
+ end
+
+ def missing_partial
+ render :partial => 'thisFileIsntHere'
+ end
+
+ def partial_with_hash_object
+ render :partial => "hash_object", :object => {:first_name => "Sam"}
+ end
+
+ def partial_hash_collection
+ render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ]
+ end
+
+ def partial_hash_collection_with_locals
+ render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" }
+ end
+
+ def partial_with_implicit_local_assignment
+ @customer = Customer.new("Marcel")
+ render :partial => "customer"
+ end
+
+ def render_call_to_partial_with_layout
+ render :action => "calling_partial_with_layout"
+ end
+
+ def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
+ render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout"
+ end
+
+ def rescue_action(e)
+ raise
+ end
private
def determine_layout
case action_name
- when "layout_test"; "layouts/standard"
- when "builder_layout_test"; "layouts/builder"
- when "render_symbol_json"; "layouts/standard" # to make sure layouts don't interfere
+ when "hello_world", "layout_test", "rendering_without_layout",
+ "rendering_nothing_on_layout", "render_text_hello_world",
+ "render_text_hello_world_with_layout",
+ "hello_world_with_layout_false",
+ "partial_only", "partial_only_with_layout",
+ "accessing_params_in_template",
+ "accessing_params_in_template_with_layout",
+ "render_with_explicit_template",
+ "render_js_with_explicit_template",
+ "render_js_with_explicit_action_template",
+ "delete_with_js", "update_page", "update_page_with_instance_variables"
+
+ "layouts/standard"
+ when "action_talk_to_layout", "layout_overriding_layout"
+ "layouts/talk_from_action"
end
end
end
@@ -227,13 +625,24 @@ class RenderTest < Test::Unit::TestCase
@response = ActionController::TestResponse.new
@controller = TestController.new
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
@request.host = "www.nextangle.com"
end
def test_simple_show
get :hello_world
assert_response 200
+ assert_response :success
assert_template "test/hello_world"
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_renders_default_template_for_missing_action
+ get :'hyphen-ated'
+ assert_template 'test/hyphen-ated'
end
def test_render
@@ -290,6 +699,41 @@ class RenderTest < Test::Unit::TestCase
assert_equal "hello world", @response.body
end
+ def test_do_with_render_text_and_layout
+ get :render_text_hello_world_with_layout
+ assert_equal "<html>hello world, I'm here!</html>", @response.body
+ end
+
+ def test_do_with_render_action_and_layout_false
+ get :hello_world_with_layout_false
+ assert_equal 'Hello world!', @response.body
+ end
+
+ def test_render_file_with_instance_variables
+ get :render_file_with_instance_variables
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_not_using_full_path
+ get :render_file_not_using_full_path
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_not_using_full_path_with_dot_in_path
+ get :render_file_not_using_full_path_with_dot_in_path
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_with_locals
+ get :render_file_with_locals
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_from_template
+ get :render_file_from_template
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
def test_render_json
get :render_json_hello_world
assert_equal '{"hello": "world"}', @response.body
@@ -317,6 +761,7 @@ class RenderTest < Test::Unit::TestCase
def test_render_custom_code
get :render_custom_code
assert_response 404
+ assert_response :missing
assert_equal 'hello world', @response.body
end
@@ -355,6 +800,26 @@ class RenderTest < Test::Unit::TestCase
assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
end
+ def test_access_to_request_in_view
+ get :accessing_request_in_template
+ assert_equal "Hello: www.nextangle.com", @response.body
+ end
+
+ def test_access_to_logger_in_view
+ get :accessing_logger_in_template
+ assert_equal "Logger", @response.body
+ end
+
+ def test_access_to_action_name_in_view
+ get :accessing_action_name_in_template
+ assert_equal "accessing_action_name_in_template", @response.body
+ end
+
+ def test_access_to_controller_name_in_view
+ get :accessing_controller_name_in_template
+ assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller.
+ end
+
def test_render_xml
get :render_xml_hello
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
@@ -371,6 +836,19 @@ class RenderTest < Test::Unit::TestCase
assert_equal "<test>\n <hello/>\n</test>\n", @response.body
end
+ def test_enum_rjs_test
+ get :enum_rjs_test
+ body = %{
+ $$(".product").each(function(value, index) {
+ new Effect.Highlight(element,{});
+ new Effect.Highlight(value,{});
+ Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
+ new Draggable(value, {});
+ });
+ }.gsub(/^ /, '').strip
+ assert_equal body, @response.body
+ end
+
def test_layout_rendering
get :layout_test
assert_equal "<html>Hello world!</html>", @response.body
@@ -381,14 +859,9 @@ class RenderTest < Test::Unit::TestCase
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
end
- # def test_partials_list
- # get :partials_list
- # assert_equal "goodbyeHello: davidHello: marygoodbye\n", process_request.body
- # end
-
- def test_partial_only
- get :partial_only
- assert_equal "only partial", @response.body
+ def test_partials_list
+ get :partials_list
+ assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
end
def test_render_to_string
@@ -438,6 +911,252 @@ class RenderTest < Test::Unit::TestCase
assert_equal '<test>passed formatted html erb</test>', @response.body
end
+ def test_should_render_xml_but_keep_custom_content_type
+ get :render_xml_with_custom_content_type
+ assert_equal "application/atomsvc+xml", @response.content_type
+ end
+
+ def test_render_with_default_from_accept_header
+ xhr :get, :greeting
+ assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
+ end
+
+ def test_render_rjs_with_default
+ get :delete_with_js
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_render_rjs_template_explicitly
+ get :render_js_with_explicit_template
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_rendering_rjs_action_explicitly
+ get :render_js_with_explicit_action_template
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_layout_test_with_different_layout
+ get :layout_test_with_different_layout
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_rendering_without_layout
+ get :rendering_without_layout
+ assert_equal "Hello world!", @response.body
+ end
+
+ def test_layout_overriding_layout
+ get :layout_overriding_layout
+ assert_no_match %r{<title>}, @response.body
+ end
+
+ def test_rendering_nothing_on_layout
+ get :rendering_nothing_on_layout
+ assert_equal " ", @response.body
+ end
+
+ def test_render_to_string
+ assert_not_deprecated { get :hello_in_a_string }
+ assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
+ end
+
+ def test_render_to_string_doesnt_break_assigns
+ get :render_to_string_with_assigns
+ assert_equal "i'm before the render", assigns(:before)
+ assert_equal "i'm after the render", assigns(:after)
+ end
+
+ def test_bad_render_to_string_still_throws_exception
+ assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception }
+ end
+
+ def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
+ assert_nothing_raised { get :render_to_string_with_caught_exception }
+ assert_equal "i'm before the render", assigns(:before)
+ assert_equal "i'm after the render", assigns(:after)
+ end
+
+ def test_accessing_params_in_template_with_layout
+ get :accessing_params_in_template_with_layout, :name => "David"
+ assert_equal "<html>Hello: David</html>", @response.body
+ end
+
+ def test_render_with_explicit_template
+ get :render_with_explicit_template
+ assert_response :success
+ end
+
+ def test_double_render
+ assert_raises(ActionController::DoubleRenderError) { get :double_render }
+ end
+
+ def test_double_redirect
+ assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
+ end
+
+ def test_render_and_redirect
+ assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
+ end
+
+ # specify the one exception to double render rule - render_to_string followed by render
+ def test_render_to_string_and_render
+ get :render_to_string_and_render
+ assert_equal("Hi web users! here is some cached stuff", @response.body)
+ end
+
+ def test_rendering_with_conflicting_local_vars
+ get :rendering_with_conflicting_local_vars
+ assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
+ end
+
+ def test_action_talk_to_layout
+ get :action_talk_to_layout
+ assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
+ end
+
+ def test_render_text_with_assigns
+ get :render_text_with_assigns
+ assert_equal "world", assigns["hello"]
+ end
+
+ def test_template_with_locals
+ get :render_with_explicit_template_with_locals
+ assert_equal "The secret is area51\n", @response.body
+ end
+
+ def test_update_page
+ get :update_page
+ assert_template nil
+ assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
+ assert_equal 2, @response.body.split($/).length
+ end
+
+ def test_update_page_with_instance_variables
+ get :update_page_with_instance_variables
+ assert_template nil
+ assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
+ assert_match /balance/, @response.body
+ assert_match /\$37/, @response.body
+ end
+
+ def test_update_page_with_view_method
+ get :update_page_with_view_method
+ assert_template nil
+ assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
+ assert_match /2 people/, @response.body
+ end
+
+ def test_yield_content_for
+ assert_not_deprecated { get :yield_content_for }
+ assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
+ end
+
+ def test_overwritting_rendering_relative_file_with_extension
+ get :hello_world_from_rxml_using_template
+ assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
+
+ get :hello_world_from_rxml_using_action
+ assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
+ end
+
+ def test_head_with_location_header
+ get :head_with_location_header
+ assert @response.body.blank?
+ assert_equal "/foo", @response.headers["Location"]
+ assert_response :ok
+ end
+
+ def test_head_with_custom_header
+ get :head_with_custom_header
+ assert @response.body.blank?
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :ok
+ end
+
+ def test_head_with_symbolic_status
+ get :head_with_symbolic_status, :status => "ok"
+ assert_equal "200 OK", @response.headers["Status"]
+ assert_response :ok
+
+ get :head_with_symbolic_status, :status => "not_found"
+ assert_equal "404 Not Found", @response.headers["Status"]
+ assert_response :not_found
+
+ ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
+ get :head_with_symbolic_status, :status => status.to_s
+ assert_equal code, @response.response_code
+ assert_response status
+ end
+ end
+
+ def test_head_with_integer_status
+ ActionController::StatusCodes::STATUS_CODES.each do |code, message|
+ get :head_with_integer_status, :status => code.to_s
+ assert_equal message, @response.message
+ end
+ end
+
+ def test_head_with_string_status
+ get :head_with_string_status, :status => "404 Eat Dirt"
+ assert_equal 404, @response.response_code
+ assert_equal "Eat Dirt", @response.message
+ assert_response :not_found
+ end
+
+ def test_head_with_status_code_first
+ get :head_with_status_code_first
+ assert_equal 403, @response.response_code
+ assert_equal "Forbidden", @response.message
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :forbidden
+ end
+
+ def test_rendering_with_location_should_set_header
+ get :render_with_location
+ assert_equal "http://example.com", @response.headers["Location"]
+ end
+
+ def test_rendering_xml_should_call_to_xml_if_possible
+ get :render_with_to_xml
+ assert_equal "<i-am-xml/>", @response.body
+ end
+
+ def test_rendering_with_object_location_should_set_header_with_url_for
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :customers
+ map.connect ':controller/:action/:id'
+ end
+
+ get :render_with_object_location
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ end
+
+ def test_should_use_implicit_content_type
+ get :implicit_content_type, :format => 'atom'
+ assert_equal Mime::ATOM, @response.content_type
+ end
+
+ def test_using_layout_around_block
+ get :render_using_layout_around_block
+ assert_equal "Before (David)\nInside from block\nAfter", @response.body
+ end
+
+ def test_using_layout_around_block_in_main_layout_and_within_content_for_layout
+ get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout
+ assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body
+ end
+
+ def test_using_layout_around_block_with_args
+ get :render_using_layout_around_block_with_args
+ assert_equal "Before\narg1arg2\nAfter", @response.body
+ end
+
+ def test_partial_only
+ get :partial_only
+ assert_equal "only partial", @response.body
+ end
+
def test_should_render_html_formatted_partial
get :partial
assert_equal 'partial html', @response.body
@@ -468,14 +1187,115 @@ class RenderTest < Test::Unit::TestCase
assert_equal %(Element.replace("foo", "partial html");), @response.body
end
- def test_should_render_xml_but_keep_custom_content_type
- get :render_xml_with_custom_content_type
- assert_equal "application/atomsvc+xml", @response.content_type
+ def test_partial_only_with_layout
+ get :partial_only_with_layout
+ assert_equal "<html>only partial</html>", @response.body
end
- def test_should_use_implicit_content_type
- get :implicit_content_type, :format => 'atom'
- assert_equal Mime::ATOM, @response.content_type
+ def test_render_to_string_partial
+ get :render_to_string_with_partial
+ assert_equal "only partial", assigns(:partial_only)
+ assert_equal "Hello: david", assigns(:partial_with_locals)
+ end
+
+ def test_partial_with_counter
+ get :partial_with_counter
+ assert_equal "5", @response.body
+ end
+
+ def test_partial_with_locals
+ get :partial_with_locals
+ assert_equal "Hello: david", @response.body
+ end
+
+ def test_partial_with_form_builder
+ get :partial_with_form_builder
+ assert_match(/<label/, @response.body)
+ assert_template('test/_form')
+ end
+
+ def test_partial_with_form_builder_subclass
+ get :partial_with_form_builder_subclass
+ assert_match(/<label/, @response.body)
+ assert_template('test/_labelling_form')
+ end
+
+ def test_partial_collection
+ get :partial_collection
+ assert_equal "Hello: davidHello: mary", @response.body
+ end
+
+ def test_partial_collection_with_as
+ get :partial_collection_with_as
+ assert_equal "david david davidmary mary mary", @response.body
+ end
+
+ def test_partial_collection_with_counter
+ get :partial_collection_with_counter
+ assert_equal "david0mary1", @response.body
+ end
+
+ def test_partial_collection_with_locals
+ get :partial_collection_with_locals
+ assert_equal "Bonjour: davidBonjour: mary", @response.body
+ end
+
+ def test_partial_collection_with_spacer
+ get :partial_collection_with_spacer
+ assert_equal "Hello: davidonly partialHello: mary", @response.body
+ end
+
+ def test_partial_collection_shorthand_with_locals
+ get :partial_collection_shorthand_with_locals
+ assert_equal "Bonjour: davidBonjour: mary", @response.body
+ end
+
+ def test_partial_collection_shorthand_with_different_types_of_records
+ get :partial_collection_shorthand_with_different_types_of_records
+ assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body
+ end
+
+ def test_empty_partial_collection
+ get :empty_partial_collection
+ assert_equal " ", @response.body
+ end
+
+ def test_partial_with_hash_object
+ get :partial_with_hash_object
+ assert_equal "Sam\nmaS\n", @response.body
+ end
+
+ def test_hash_partial_collection
+ get :partial_hash_collection
+ assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body
+ end
+
+ def test_partial_hash_collection_with_locals
+ get :partial_hash_collection_with_locals
+ assert_equal "Hola: PratikHola: Amy", @response.body
+ end
+
+ def test_partial_with_implicit_local_assignment
+ assert_deprecated do
+ get :partial_with_implicit_local_assignment
+ assert_equal "Hello: Marcel", @response.body
+ end
+ end
+
+ def test_render_missing_partial_template
+ assert_raises(ActionView::MissingTemplate) do
+ get :missing_partial
+ end
+ end
+
+ def test_render_call_to_partial_with_layout
+ get :render_call_to_partial_with_layout
+ assert_equal "Before (David)\nInside from partial (David)\nAfter", @response.body
+ end
+
+ def test_render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
+ get :render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout
+ assert_equal "Before (Anthony)\nInside from partial (Anthony)\nAfter\nBefore (David)\nInside from partial (David)\nAfter\nBefore (Ramm)\nInside from partial (Ramm)\nAfter", @response.body
end
end
@@ -501,6 +1321,12 @@ class EtagRenderTest < Test::Unit::TestCase
assert @response.body.empty?
end
+ def test_render_against_etag_request_should_have_no_content_length_when_match
+ @request.if_none_match = etag_for("hello david")
+ get :render_hello_world_from_variable
+ assert !@response.headers.has_key?("Content-Length")
+ end
+
def test_render_against_etag_request_should_200_when_no_match
@request.if_none_match = etag_for("hello somewhere else")
get :render_hello_world_from_variable
@@ -577,3 +1403,21 @@ class LastModifiedRenderTest < Test::Unit::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
end
+
+class RenderingLoggingTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_logger_prints_layout_and_template_rendering_info
+ @controller.logger = MockLogger.new
+ get :layout_test
+ logged = @controller.logger.logged.find_all {|l| l =~ /render/i }
+ assert_equal "Rendering template within layouts/standard", logged[0]
+ assert_equal "Rendering test/hello_world", logged[1]
+ end
+end
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 045dab4141..e79a0ea76b 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -17,6 +17,9 @@ class RequestTest < Test::Unit::TestCase
@request.remote_addr = '1.2.3.4'
assert_equal '1.2.3.4', @request.remote_ip(true)
+ @request.remote_addr = '1.2.3.4,3.4.5.6'
+ assert_equal '1.2.3.4', @request.remote_ip(true)
+
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
assert_equal '1.2.3.4', @request.remote_ip(true)
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index e153b0cc98..1fea82e564 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -264,6 +264,19 @@ class ResourcesTest < Test::Unit::TestCase
end
end
+ def test_array_as_collection_or_member_method_value
+ with_restful_routing :messages, :collection => { :search => [:get, :post] }, :member => { :toggle => [:get, :post] } do
+ assert_restful_routes_for :messages do |options|
+ [:get, :post].each do |method|
+ assert_recognizes(options.merge(:action => 'search'), :path => "/messages/search", :method => method)
+ end
+ [:get, :post].each do |method|
+ assert_recognizes(options.merge(:action => 'toggle', :id => '1'), :path => '/messages/1/toggle', :method => method)
+ end
+ end
+ end
+ end
+
def test_with_new_action
with_restful_routing :messages, :new => { :preview => :post } do
preview_options = {:action => 'preview'}
@@ -366,6 +379,31 @@ class ResourcesTest < Test::Unit::TestCase
end
end
+ def test_shallow_nested_restful_routes
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :threads, :shallow => true do |map|
+ map.resources :messages do |map|
+ map.resources :comments
+ end
+ end
+ end
+
+ assert_simply_restful_for :threads,
+ :shallow => true
+ assert_simply_restful_for :messages,
+ :name_prefix => 'thread_',
+ :path_prefix => 'threads/1/',
+ :shallow => true,
+ :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments,
+ :name_prefix => 'message_',
+ :path_prefix => 'messages/2/',
+ :shallow => true,
+ :options => { :message_id => '2' }
+ end
+ end
+
def test_restful_routes_dont_generate_duplicates
with_restful_routing :messages do
routes = ActionController::Routing::Routes.routes
@@ -416,6 +454,32 @@ class ResourcesTest < Test::Unit::TestCase
end
end
+ def test_resources_has_many_hash_should_become_nested_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :threads, :has_many => { :messages => [ :comments, { :authors => :threads } ] }
+ end
+
+ assert_simply_restful_for :threads
+ assert_simply_restful_for :messages, :name_prefix => "thread_", :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments, :name_prefix => "thread_message_", :path_prefix => 'threads/1/messages/1/', :options => { :thread_id => '1', :message_id => '1' }
+ assert_simply_restful_for :authors, :name_prefix => "thread_message_", :path_prefix => 'threads/1/messages/1/', :options => { :thread_id => '1', :message_id => '1' }
+ assert_simply_restful_for :threads, :name_prefix => "thread_message_author_", :path_prefix => 'threads/1/messages/1/authors/1/', :options => { :thread_id => '1', :message_id => '1', :author_id => '1' }
+ end
+ end
+
+ def test_shallow_resource_has_many_should_become_shallow_nested_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :has_many => [ :comments, :authors ], :shallow => true
+ end
+
+ assert_simply_restful_for :messages, :shallow => true
+ assert_simply_restful_for :comments, :name_prefix => "message_", :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' }
+ assert_simply_restful_for :authors, :name_prefix => "message_", :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' }
+ end
+ end
+
def test_resource_has_one_should_become_nested_resources
with_routing do |set|
set.draw do |map|
@@ -427,6 +491,17 @@ class ResourcesTest < Test::Unit::TestCase
end
end
+ def test_shallow_resource_has_one_should_become_shallow_nested_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :has_one => :logo, :shallow => true
+ end
+
+ assert_simply_restful_for :messages, :shallow => true
+ assert_singleton_restful_for :logo, :name_prefix => 'message_', :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' }
+ end
+ end
+
def test_singleton_resource_with_member_action
[:put, :post].each do |method|
with_singleton_resources :account, :member => { :reset => method } do
@@ -731,6 +806,13 @@ class ResourcesTest < Test::Unit::TestCase
options[:options] ||= {}
options[:options][:controller] = options[:controller] || controller_name.to_s
+ if options[:shallow]
+ options[:shallow_options] ||= {}
+ options[:shallow_options][:controller] = options[:options][:controller]
+ else
+ options[:shallow_options] = options[:options]
+ end
+
new_action = ActionController::Base.resources_path_names[:new] || "new"
edit_action = ActionController::Base.resources_path_names[:edit] || "edit"
if options[:path_names]
@@ -738,8 +820,10 @@ class ResourcesTest < Test::Unit::TestCase
edit_action = options[:path_names][:edit] if options[:path_names][:edit]
end
- collection_path = "/#{options[:path_prefix]}#{options[:as] || controller_name}"
- member_path = "#{collection_path}/1"
+ path = "#{options[:as] || controller_name}"
+ collection_path = "/#{options[:path_prefix]}#{path}"
+ shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}"
+ member_path = "#{shallow_path}/1"
new_path = "#{collection_path}/#{new_action}"
edit_member_path = "#{member_path}/#{edit_action}"
formatted_edit_member_path = "#{member_path}/#{edit_action}.xml"
@@ -747,10 +831,13 @@ class ResourcesTest < Test::Unit::TestCase
with_options(options[:options]) do |controller|
controller.assert_routing collection_path, :action => 'index'
controller.assert_routing new_path, :action => 'new'
- controller.assert_routing member_path, :action => 'show', :id => '1'
- controller.assert_routing edit_member_path, :action => 'edit', :id => '1'
controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
+ end
+
+ with_options(options[:shallow_options]) do |controller|
+ controller.assert_routing member_path, :action => 'show', :id => '1'
+ controller.assert_routing edit_member_path, :action => 'edit', :id => '1'
controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml'
controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
end
@@ -758,18 +845,18 @@ class ResourcesTest < Test::Unit::TestCase
assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
- assert_recognizes(options[:options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
-
- assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
- assert_recognizes(options[:options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
+ assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
+
+ assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
+ assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
yield options[:options] if block_given?
end
@@ -785,14 +872,24 @@ class ResourcesTest < Test::Unit::TestCase
options[:options] ||= {}
options[:options][:controller] = options[:controller] || controller_name.to_s
+ if options[:shallow]
+ options[:shallow_options] ||= {}
+ options[:shallow_options][:controller] = options[:options][:controller]
+ else
+ options[:shallow_options] = options[:options]
+ end
+
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
get :index, options[:options]
options[:options].delete :action
- full_prefix = "/#{options[:path_prefix]}#{options[:as] || controller_name}"
+ path = "#{options[:as] || controller_name}"
+ shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}"
+ full_path = "/#{options[:path_prefix]}#{path}"
name_prefix = options[:name_prefix]
+ shallow_prefix = "#{options[:name_prefix] unless options[:shallow]}"
new_action = "new"
edit_action = "edit"
@@ -801,15 +898,15 @@ class ResourcesTest < Test::Unit::TestCase
edit_action = options[:path_names][:edit] || "edit"
end
- assert_named_route "#{full_prefix}", "#{name_prefix}#{controller_name}_path", options[:options]
- assert_named_route "#{full_prefix}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge( :format => 'xml')
- assert_named_route "#{full_prefix}/1", "#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
- assert_named_route "#{full_prefix}/1.xml", "formatted_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
+ assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options]
+ assert_named_route "#{full_path}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
+ assert_named_route "#{shallow_path}/1.xml", "formatted_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
- assert_named_route "#{full_prefix}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options]
- assert_named_route "#{full_prefix}/#{new_action}.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge( :format => 'xml')
- assert_named_route "#{full_prefix}/1/#{edit_action}", "edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
- assert_named_route "#{full_prefix}/1/#{edit_action}.xml", "formatted_edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
+ assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options]
+ assert_named_route "#{full_path}/#{new_action}.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
+ assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "formatted_edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
yield options[:options] if block_given?
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 6cf134c26f..1eb26a7cfd 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1297,6 +1297,29 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
end
+ def test_recognize_array_of_methods
+ Object.const_set(:BooksController, Class.new(ActionController::Base))
+ rs.draw do |r|
+ r.connect '/match', :controller => 'books', :action => 'get_or_post', :conditions => { :method => [:get, :post] }
+ r.connect '/match', :controller => 'books', :action => 'not_get_or_post'
+ end
+
+ @request = ActionController::TestRequest.new
+ @request.env["REQUEST_METHOD"] = 'POST'
+ @request.request_uri = "/match"
+ assert_nothing_raised { rs.recognize(@request) }
+ assert_equal 'get_or_post', @request.path_parameters[:action]
+
+ # have to recreate or else the RouteSet uses a cached version:
+ @request = ActionController::TestRequest.new
+ @request.env["REQUEST_METHOD"] = 'PUT'
+ @request.request_uri = "/match"
+ assert_nothing_raised { rs.recognize(@request) }
+ assert_equal 'not_get_or_post', @request.path_parameters[:action]
+ ensure
+ Object.send(:remove_const, :BooksController) rescue nil
+ end
+
def test_subpath_recognized
Object.const_set(:SubpathBooksController, Class.new(ActionController::Base))
@@ -1352,6 +1375,36 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
x.send(:foo_with_requirement_url, "I am Against the requirements")
end
end
+
+ def test_routes_changed_correctly_after_clear
+ ActionController::Base.optimise_named_routes = true
+ rs = ::ActionController::Routing::RouteSet.new
+ rs.draw do |r|
+ r.connect 'ca', :controller => 'ca', :action => "aa"
+ r.connect 'cb', :controller => 'cb', :action => "ab"
+ r.connect 'cc', :controller => 'cc', :action => "ac"
+ r.connect ':controller/:action/:id'
+ r.connect ':controller/:action/:id.:format'
+ end
+
+ hash = rs.recognize_path "/cc"
+
+ assert_not_nil hash
+ assert_equal %w(cc ac), [hash[:controller], hash[:action]]
+
+ rs.draw do |r|
+ r.connect 'cb', :controller => 'cb', :action => "ab"
+ r.connect 'cc', :controller => 'cc', :action => "ac"
+ r.connect ':controller/:action/:id'
+ r.connect ':controller/:action/:id.:format'
+ end
+
+ hash = rs.recognize_path "/cc"
+
+ assert_not_nil hash
+ assert_equal %w(cc ac), [hash[:controller], hash[:action]]
+
+ end
end
class RouteTest < Test::Unit::TestCase
@@ -1671,6 +1724,12 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
controller.send(:multi_url, 7, "hello", 5, :baz => "bar")
end
+ def test_named_route_url_method_with_ordered_parameters_and_empty_hash
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ controller.send(:multi_url, 7, "hello", 5, {})
+ end
+
def test_named_route_url_method_with_no_positional_arguments
controller = setup_named_route_test
assert_equal "http://named.route.test/people?baz=bar",
diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb
index 5adaeaf5c5..010c00fa14 100644
--- a/actionpack/test/controller/session/cookie_store_test.rb
+++ b/actionpack/test/controller/session/cookie_store_test.rb
@@ -36,7 +36,9 @@ class CookieStoreTest < Test::Unit::TestCase
'session_key' => '_myapp_session',
'secret' => 'Keep it secret; keep it safe.',
'no_cookies' => true,
- 'no_hidden' => true }
+ 'no_hidden' => true,
+ 'session_http_only' => true
+ }
end
def self.cookies
@@ -149,6 +151,48 @@ class CookieStoreTest < Test::Unit::TestCase
assert_equal 1, session.cgi.output_cookies.size
cookie = session.cgi.output_cookies.first
assert_cookie cookie, cookie_value(:flashed)
+ assert_http_only_cookie cookie
+ assert_secure_cookie cookie, false
+ end
+ end
+
+ def test_writes_non_secure_cookie_by_default
+ set_cookie! cookie_value(:typical)
+ new_session do |session|
+ session['flash'] = {}
+ session.close
+ cookie = session.cgi.output_cookies.first
+ assert_secure_cookie cookie,false
+ end
+ end
+
+ def test_writes_secure_cookie
+ set_cookie! cookie_value(:typical)
+ new_session('session_secure'=>true) do |session|
+ session['flash'] = {}
+ session.close
+ cookie = session.cgi.output_cookies.first
+ assert_secure_cookie cookie
+ end
+ end
+
+ def test_http_only_cookie_by_default
+ set_cookie! cookie_value(:typical)
+ new_session do |session|
+ session['flash'] = {}
+ session.close
+ cookie = session.cgi.output_cookies.first
+ assert_http_only_cookie cookie
+ end
+ end
+
+ def test_overides_http_only_cookie
+ set_cookie! cookie_value(:typical)
+ new_session('session_http_only'=>false) do |session|
+ session['flash'] = {}
+ session.close
+ cookie = session.cgi.output_cookies.first
+ assert_http_only_cookie cookie, false
end
end
@@ -195,6 +239,13 @@ class CookieStoreTest < Test::Unit::TestCase
assert_equal expires, cookie.expires ? cookie.expires.to_date : cookie.expires, message
end
+ def assert_secure_cookie(cookie,value=true)
+ assert cookie.secure==value
+ end
+
+ def assert_http_only_cookie(cookie,value=true)
+ assert cookie.http_only==value
+ end
def cookies(*which)
self.class.cookies.values_at(*which)
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 58d9ca537f..9eff34a542 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -531,6 +531,11 @@ XML
assert_equal content_type, file.content_type
assert_equal file.path, file.local_path
assert_equal expected, file.read
+
+ new_content_type = "new content_type"
+ file.content_type = new_content_type
+ assert_equal new_content_type, file.content_type
+
end
def test_test_uploaded_file_with_binary
diff --git a/actionpack/test/controller/translation_test.rb b/actionpack/test/controller/translation_test.rb
new file mode 100644
index 0000000000..0bf61a6556
--- /dev/null
+++ b/actionpack/test/controller/translation_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+
+# class TranslatingController < ActionController::Base
+# end
+
+class TranslationControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = ActionController::Base.new
+ end
+
+ def test_action_controller_base_responds_to_translate
+ assert @controller.respond_to?(:translate)
+ end
+
+ def test_action_controller_base_responds_to_t
+ assert @controller.respond_to?(:t)
+ end
+
+ def test_action_controller_base_responds_to_localize
+ assert @controller.respond_to?(:localize)
+ end
+
+ def test_action_controller_base_responds_to_l
+ assert @controller.respond_to?(:l)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/form_country_helper_test.rb b/actionpack/test/template/form_country_helper_test.rb
deleted file mode 100644
index 8862e08222..0000000000
--- a/actionpack/test/template/form_country_helper_test.rb
+++ /dev/null
@@ -1,1549 +0,0 @@
-require 'abstract_unit'
-
-class FormCountryHelperTest < ActionView::TestCase
- tests ActionView::Helpers::FormCountryHelper
-
- silence_warnings do
- Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
- end
-
- def test_country_select
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
-<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option selected="selected" value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
- end
-
- def test_country_select_with_priority_countries
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
-<select id="post_origin" name="post[origin]"><option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
-<option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option selected="selected" value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
-
- def test_country_select_with_selected_priority_country
- @post = Post.new
- @post.origin = "New Zealand"
- expected_select = <<-COUNTRIES
-<select id="post_origin" name="post[origin]"><option selected="selected" value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
-<option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option selected="selected" value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
-
- def test_country_select_under_fields_for
- @post = Post.new
- @post.origin = "Australia"
- expected_select = <<-COUNTRIES
-<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option selected="selected" value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
-
- fields_for :post, @post do |f|
- concat f.country_select("origin")
- end
-
- assert_dom_equal(expected_select[0..-2], output_buffer)
- end
-
- def test_country_select_under_fields_for_with_index
- @post = Post.new
- @post.origin = "United States"
- expected_select = <<-COUNTRIES
-<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option selected="selected" value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
-
- fields_for :post, @post, :index => 325 do |f|
- concat f.country_select("origin")
- end
-
- assert_dom_equal(expected_select[0..-2], output_buffer)
- end
-
- def test_country_select_under_fields_for_with_auto_index
- @post = Post.new
- @post.origin = "Iraq"
- def @post.to_param; 325; end
-
- expected_select = <<-COUNTRIES
-<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option>
-<option value="Aland Islands">Aland Islands</option>
-<option value="Albania">Albania</option>
-<option value="Algeria">Algeria</option>
-<option value="American Samoa">American Samoa</option>
-<option value="Andorra">Andorra</option>
-<option value="Angola">Angola</option>
-<option value="Anguilla">Anguilla</option>
-<option value="Antarctica">Antarctica</option>
-<option value="Antigua And Barbuda">Antigua And Barbuda</option>
-<option value="Argentina">Argentina</option>
-<option value="Armenia">Armenia</option>
-<option value="Aruba">Aruba</option>
-<option value="Australia">Australia</option>
-<option value="Austria">Austria</option>
-<option value="Azerbaijan">Azerbaijan</option>
-<option value="Bahamas">Bahamas</option>
-<option value="Bahrain">Bahrain</option>
-<option value="Bangladesh">Bangladesh</option>
-<option value="Barbados">Barbados</option>
-<option value="Belarus">Belarus</option>
-<option value="Belgium">Belgium</option>
-<option value="Belize">Belize</option>
-<option value="Benin">Benin</option>
-<option value="Bermuda">Bermuda</option>
-<option value="Bhutan">Bhutan</option>
-<option value="Bolivia">Bolivia</option>
-<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
-<option value="Botswana">Botswana</option>
-<option value="Bouvet Island">Bouvet Island</option>
-<option value="Brazil">Brazil</option>
-<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
-<option value="Brunei Darussalam">Brunei Darussalam</option>
-<option value="Bulgaria">Bulgaria</option>
-<option value="Burkina Faso">Burkina Faso</option>
-<option value="Burundi">Burundi</option>
-<option value="Cambodia">Cambodia</option>
-<option value="Cameroon">Cameroon</option>
-<option value="Canada">Canada</option>
-<option value="Cape Verde">Cape Verde</option>
-<option value="Cayman Islands">Cayman Islands</option>
-<option value="Central African Republic">Central African Republic</option>
-<option value="Chad">Chad</option>
-<option value="Chile">Chile</option>
-<option value="China">China</option>
-<option value="Christmas Island">Christmas Island</option>
-<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
-<option value="Colombia">Colombia</option>
-<option value="Comoros">Comoros</option>
-<option value="Congo">Congo</option>
-<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
-<option value="Cook Islands">Cook Islands</option>
-<option value="Costa Rica">Costa Rica</option>
-<option value="Cote d'Ivoire">Cote d'Ivoire</option>
-<option value="Croatia">Croatia</option>
-<option value="Cuba">Cuba</option>
-<option value="Cyprus">Cyprus</option>
-<option value="Czech Republic">Czech Republic</option>
-<option value="Denmark">Denmark</option>
-<option value="Djibouti">Djibouti</option>
-<option value="Dominica">Dominica</option>
-<option value="Dominican Republic">Dominican Republic</option>
-<option value="Ecuador">Ecuador</option>
-<option value="Egypt">Egypt</option>
-<option value="El Salvador">El Salvador</option>
-<option value="Equatorial Guinea">Equatorial Guinea</option>
-<option value="Eritrea">Eritrea</option>
-<option value="Estonia">Estonia</option>
-<option value="Ethiopia">Ethiopia</option>
-<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
-<option value="Faroe Islands">Faroe Islands</option>
-<option value="Fiji">Fiji</option>
-<option value="Finland">Finland</option>
-<option value="France">France</option>
-<option value="French Guiana">French Guiana</option>
-<option value="French Polynesia">French Polynesia</option>
-<option value="French Southern Territories">French Southern Territories</option>
-<option value="Gabon">Gabon</option>
-<option value="Gambia">Gambia</option>
-<option value="Georgia">Georgia</option>
-<option value="Germany">Germany</option>
-<option value="Ghana">Ghana</option>
-<option value="Gibraltar">Gibraltar</option>
-<option value="Greece">Greece</option>
-<option value="Greenland">Greenland</option>
-<option value="Grenada">Grenada</option>
-<option value="Guadeloupe">Guadeloupe</option>
-<option value="Guam">Guam</option>
-<option value="Guatemala">Guatemala</option>
-<option value="Guernsey">Guernsey</option>
-<option value="Guinea">Guinea</option>
-<option value="Guinea-Bissau">Guinea-Bissau</option>
-<option value="Guyana">Guyana</option>
-<option value="Haiti">Haiti</option>
-<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
-<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
-<option value="Honduras">Honduras</option>
-<option value="Hong Kong">Hong Kong</option>
-<option value="Hungary">Hungary</option>
-<option value="Iceland">Iceland</option>
-<option value="India">India</option>
-<option value="Indonesia">Indonesia</option>
-<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
-<option selected="selected" value="Iraq">Iraq</option>
-<option value="Ireland">Ireland</option>
-<option value="Isle of Man">Isle of Man</option>
-<option value="Israel">Israel</option>
-<option value="Italy">Italy</option>
-<option value="Jamaica">Jamaica</option>
-<option value="Japan">Japan</option>
-<option value="Jersey">Jersey</option>
-<option value="Jordan">Jordan</option>
-<option value="Kazakhstan">Kazakhstan</option>
-<option value="Kenya">Kenya</option>
-<option value="Kiribati">Kiribati</option>
-<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
-<option value="Korea, Republic of">Korea, Republic of</option>
-<option value="Kuwait">Kuwait</option>
-<option value="Kyrgyzstan">Kyrgyzstan</option>
-<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
-<option value="Latvia">Latvia</option>
-<option value="Lebanon">Lebanon</option>
-<option value="Lesotho">Lesotho</option>
-<option value="Liberia">Liberia</option>
-<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
-<option value="Liechtenstein">Liechtenstein</option>
-<option value="Lithuania">Lithuania</option>
-<option value="Luxembourg">Luxembourg</option>
-<option value="Macao">Macao</option>
-<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
-<option value="Madagascar">Madagascar</option>
-<option value="Malawi">Malawi</option>
-<option value="Malaysia">Malaysia</option>
-<option value="Maldives">Maldives</option>
-<option value="Mali">Mali</option>
-<option value="Malta">Malta</option>
-<option value="Marshall Islands">Marshall Islands</option>
-<option value="Martinique">Martinique</option>
-<option value="Mauritania">Mauritania</option>
-<option value="Mauritius">Mauritius</option>
-<option value="Mayotte">Mayotte</option>
-<option value="Mexico">Mexico</option>
-<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
-<option value="Moldova, Republic of">Moldova, Republic of</option>
-<option value="Monaco">Monaco</option>
-<option value="Mongolia">Mongolia</option>
-<option value="Montenegro">Montenegro</option>
-<option value="Montserrat">Montserrat</option>
-<option value="Morocco">Morocco</option>
-<option value="Mozambique">Mozambique</option>
-<option value="Myanmar">Myanmar</option>
-<option value="Namibia">Namibia</option>
-<option value="Nauru">Nauru</option>
-<option value="Nepal">Nepal</option>
-<option value="Netherlands">Netherlands</option>
-<option value="Netherlands Antilles">Netherlands Antilles</option>
-<option value="New Caledonia">New Caledonia</option>
-<option value="New Zealand">New Zealand</option>
-<option value="Nicaragua">Nicaragua</option>
-<option value="Niger">Niger</option>
-<option value="Nigeria">Nigeria</option>
-<option value="Niue">Niue</option>
-<option value="Norfolk Island">Norfolk Island</option>
-<option value="Northern Mariana Islands">Northern Mariana Islands</option>
-<option value="Norway">Norway</option>
-<option value="Oman">Oman</option>
-<option value="Pakistan">Pakistan</option>
-<option value="Palau">Palau</option>
-<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
-<option value="Panama">Panama</option>
-<option value="Papua New Guinea">Papua New Guinea</option>
-<option value="Paraguay">Paraguay</option>
-<option value="Peru">Peru</option>
-<option value="Philippines">Philippines</option>
-<option value="Pitcairn">Pitcairn</option>
-<option value="Poland">Poland</option>
-<option value="Portugal">Portugal</option>
-<option value="Puerto Rico">Puerto Rico</option>
-<option value="Qatar">Qatar</option>
-<option value="Reunion">Reunion</option>
-<option value="Romania">Romania</option>
-<option value="Russian Federation">Russian Federation</option>
-<option value="Rwanda">Rwanda</option>
-<option value="Saint Barthelemy">Saint Barthelemy</option>
-<option value="Saint Helena">Saint Helena</option>
-<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
-<option value="Saint Lucia">Saint Lucia</option>
-<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
-<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
-<option value="Samoa">Samoa</option>
-<option value="San Marino">San Marino</option>
-<option value="Sao Tome and Principe">Sao Tome and Principe</option>
-<option value="Saudi Arabia">Saudi Arabia</option>
-<option value="Senegal">Senegal</option>
-<option value="Serbia">Serbia</option>
-<option value="Seychelles">Seychelles</option>
-<option value="Sierra Leone">Sierra Leone</option>
-<option value="Singapore">Singapore</option>
-<option value="Slovakia">Slovakia</option>
-<option value="Slovenia">Slovenia</option>
-<option value="Solomon Islands">Solomon Islands</option>
-<option value="Somalia">Somalia</option>
-<option value="South Africa">South Africa</option>
-<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
-<option value="Spain">Spain</option>
-<option value="Sri Lanka">Sri Lanka</option>
-<option value="Sudan">Sudan</option>
-<option value="Suriname">Suriname</option>
-<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
-<option value="Swaziland">Swaziland</option>
-<option value="Sweden">Sweden</option>
-<option value="Switzerland">Switzerland</option>
-<option value="Syrian Arab Republic">Syrian Arab Republic</option>
-<option value="Taiwan, Province of China">Taiwan, Province of China</option>
-<option value="Tajikistan">Tajikistan</option>
-<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
-<option value="Thailand">Thailand</option>
-<option value="Timor-Leste">Timor-Leste</option>
-<option value="Togo">Togo</option>
-<option value="Tokelau">Tokelau</option>
-<option value="Tonga">Tonga</option>
-<option value="Trinidad and Tobago">Trinidad and Tobago</option>
-<option value="Tunisia">Tunisia</option>
-<option value="Turkey">Turkey</option>
-<option value="Turkmenistan">Turkmenistan</option>
-<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
-<option value="Tuvalu">Tuvalu</option>
-<option value="Uganda">Uganda</option>
-<option value="Ukraine">Ukraine</option>
-<option value="United Arab Emirates">United Arab Emirates</option>
-<option value="United Kingdom">United Kingdom</option>
-<option value="United States">United States</option>
-<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
-<option value="Uruguay">Uruguay</option>
-<option value="Uzbekistan">Uzbekistan</option>
-<option value="Vanuatu">Vanuatu</option>
-<option value="Venezuela">Venezuela</option>
-<option value="Viet Nam">Viet Nam</option>
-<option value="Virgin Islands, British">Virgin Islands, British</option>
-<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
-<option value="Wallis and Futuna">Wallis and Futuna</option>
-<option value="Western Sahara">Western Sahara</option>
-<option value="Yemen">Yemen</option>
-<option value="Zambia">Zambia</option>
-<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
-
- fields_for "post[]", @post do |f|
- concat f.country_select("origin")
- end
-
- assert_dom_equal(expected_select[0..-2], output_buffer)
- end
-
-end \ No newline at end of file
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 9b41ff8179..6473d011d5 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -223,14 +223,14 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag
assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' onclick="this.setAttribute('originalValue', this.value);this.disabled=true;this.value='Saving...';alert('hello!');result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false };return result;" />),
+ %(<input name='commit' type='submit' value='Save' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';alert('hello!');result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />),
submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
)
end
def test_submit_tag_with_no_onclick_options
assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' onclick="this.setAttribute('originalValue', this.value);this.disabled=true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false };return result;" />),
+ %(<input name='commit' type='submit' value='Save' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />),
submit_tag("Save", :disable_with => "Saving...")
)
end
@@ -241,6 +241,13 @@ class FormTagHelperTest < ActionView::TestCase
submit_tag("Save", :confirm => "Are you sure?")
)
end
+
+ def test_image_submit_tag_with_confirmation
+ assert_dom_equal(
+ %(<input type="image" src="/images/save.gif" onclick="return confirm('Are you sure?');"/>),
+ image_submit_tag("save.gif", :confirm => "Are you sure?")
+ )
+ end
def test_pass
assert_equal 1, 1
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index abc9f930dd..a1f541fd7b 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -79,6 +79,8 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:false, evalScripts:true}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :type => :synchronous)
+ assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, insertion:'bottom'}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :position => :bottom)
end
def test_link_to_remote_html_options
@@ -91,6 +93,19 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
link_to_remote("Remote", { :url => { :action => "whatnot's" } })
end
+ def test_button_to_remote
+ assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true});\" />),
+ button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
+ assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}});\" />),
+ button_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}});\" />),
+ button_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />),
+ button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />),
+ button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
+ end
+
def test_periodically_call_remote
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 34a200b933..67aa047745 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -34,6 +34,14 @@ class RecordTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_block_not_in_erb_multiple_calls
+ expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
+ actual = div_for(@post, :class => "bar") { @post.body }
+ assert_dom_equal expected, actual
+ actual = div_for(@post, :class => "bar") { @post.body }
+ assert_dom_equal expected, actual
+ end
+
def test_block_works_with_content_tag_for_in_erb
__in_erb_template = ''
expected = %(<tr class="post" id="post_45">#{@post.body}</tr>)
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index a31d532567..5f9f715819 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -369,6 +369,40 @@ class TextHelperTest < ActionView::TestCase
assert_equal("red", cycle("red", "blue", :name => "colors"))
end
+ def test_current_cycle_with_default_name
+ cycle("even","odd")
+ assert_equal "even", current_cycle
+ cycle("even","odd")
+ assert_equal "odd", current_cycle
+ cycle("even","odd")
+ assert_equal "even", current_cycle
+ end
+
+ def test_current_cycle_with_named_cycles
+ cycle("red", "blue", :name => "colors")
+ assert_equal "red", current_cycle("colors")
+ cycle("red", "blue", :name => "colors")
+ assert_equal "blue", current_cycle("colors")
+ cycle("red", "blue", :name => "colors")
+ assert_equal "red", current_cycle("colors")
+ end
+
+ def test_current_cycle_safe_call
+ assert_nothing_raised { current_cycle }
+ assert_nothing_raised { current_cycle("colors") }
+ end
+
+ def test_current_cycle_with_more_than_two_names
+ cycle(1,2,3)
+ assert_equal "1", current_cycle
+ cycle(1,2,3)
+ assert_equal "2", current_cycle
+ cycle(1,2,3)
+ assert_equal "3", current_cycle
+ cycle(1,2,3)
+ assert_equal "1", current_cycle
+ end
+
def test_default_named_cycle
assert_equal("1", cycle(1, 2, 3))
assert_equal("2", cycle(1, 2, 3, :name => "default"))
diff --git a/activemodel/lib/active_model/state_machine/event.rb b/activemodel/lib/active_model/state_machine/event.rb
index e8bc8ebdb7..8acde7fd47 100644
--- a/activemodel/lib/active_model/state_machine/event.rb
+++ b/activemodel/lib/active_model/state_machine/event.rb
@@ -2,15 +2,15 @@ module ActiveModel
module StateMachine
class Event
attr_reader :name, :success
-
+
def initialize(machine, name, options = {}, &block)
@machine, @name, @transitions = machine, name, []
if machine
- machine.klass.send(:define_method, "#{name.to_s}!") do |*args|
+ machine.klass.send(:define_method, "#{name}!") do |*args|
machine.fire_event(name, self, true, *args)
end
- machine.klass.send(:define_method, "#{name.to_s}") do |*args|
+ machine.klass.send(:define_method, name.to_s) do |*args|
machine.fire_event(name, self, false, *args)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 7efe9901ca..460d2d82e5 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -4,7 +4,7 @@ module ActiveModel
module Validations
def self.included(base) # :nodoc:
base.extend(ClassMethods)
- base.send!(:include, ActiveSupport::Callbacks)
+ base.__send__(:include, ActiveSupport::Callbacks)
base.define_callbacks :validate, :validate_on_create, :validate_on_update
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index f3367abcf8..1592000fc7 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -26,7 +26,7 @@ module ActiveModel
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 9fc1caaabe..9b4cb64307 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -26,7 +26,7 @@ module ActiveModel
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
diff --git a/activemodel/lib/active_model/validations/uniqueness.rb b/activemodel/lib/active_model/validations/uniqueness.rb
index 2b47c6bc09..593d2cfd54 100644
--- a/activemodel/lib/active_model/validations/uniqueness.rb
+++ b/activemodel/lib/active_model/validations/uniqueness.rb
@@ -25,7 +25,7 @@ module ActiveModel
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken")
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+false+ by default).
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -79,8 +79,8 @@ module ActiveModel
results = finder_class.with_exclusive_scope do
connection.select_all(
construct_finder_sql(
- :select => "#{attr_name}",
- :from => "#{finder_class.quoted_table_name}",
+ :select => attr_name,
+ :from => finder_class.quoted_table_name,
:conditions => [condition_sql, *condition_params]
)
)
@@ -101,4 +101,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 12295ac799..ff2b064e1c 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,21 @@
*Edge*
+* MySQL: cope with quirky default values for not-null text columns. #1043 [Frederick Cheung]
+
+* Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing]
+
+* Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [miloops]
+
+* Added find_last_by dynamic finder #762 [miloops]
+
+* Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 [Hongli Lai]
+
+* Changed benchmarks to be reported in milliseconds [DHH]
+
+* Connection pooling. #936 [Nick Sieger]
+
+* Merge scoped :joins together instead of overwriting them. May expose scoping bugs in your code! #501 [Andrew White]
+
* before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted. #891 [Xavier Noria]
* Transactional migrations for databases which support them. #834 [divoxx, Adam Wiggins, Tarmo Tänav]
@@ -10,24 +26,6 @@
* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
-* Add :accessible option to associations for allowing (opt-in) mass assignment. #474. [David Dollar] Example :
-
- class Post < ActiveRecord::Base
- belongs_to :author, :accessible => true
- has_many :comments, :accessible => true
- end
-
- post = Post.create({
- :title => 'Accessible Attributes',
- :author => { :name => 'David Dollar' },
- :comments => [
- { :body => 'First Post!' },
- { :body => 'Nested Hashes are great!' }
- ]
- })
-
- post.comments << { :body => 'Another Comment' }
-
* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
# Ensure essay contains at least 100 words.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c47ca486c8..219cd30f94 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -77,7 +77,5 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
-I18n.backend.populate do
- I18n.load_translations File.dirname(__FILE__) + '/active_record/locale/en-US.yml'
-end
-
+require 'active_record/i18n_interpolation_deprecation'
+I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en-US.yml'
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index a5d3a50ef1..9eee7f2d98 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -10,10 +10,10 @@ module ActiveRecord
end unless self.new_record?
end
- # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
+ # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
# as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
- # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
- # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
+ # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
+ # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
# and how it can be turned back into attributes (when the entity is saved to the database). Example:
#
# class Customer < ActiveRecord::Base
@@ -30,10 +30,10 @@ module ActiveRecord
# class Money
# include Comparable
# attr_reader :amount, :currency
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
- #
- # def initialize(amount, currency = "USD")
- # @amount, @currency = amount, currency
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
+ #
+ # def initialize(amount, currency = "USD")
+ # @amount, @currency = amount, currency
# end
#
# def exchange_to(other_currency)
@@ -56,19 +56,19 @@ module ActiveRecord
#
# class Address
# attr_reader :street, :city
- # def initialize(street, city)
- # @street, @city = street, city
+ # def initialize(street, city)
+ # @street, @city = street, city
# end
#
- # def close_to?(other_address)
- # city == other_address.city
+ # def close_to?(other_address)
+ # city == other_address.city
# end
#
# def ==(other_address)
# city == other_address.city && street == other_address.street
# end
# end
- #
+ #
# Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
# composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
# +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
@@ -87,8 +87,8 @@ module ActiveRecord
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
# customer.address = Address.new("May Street", "Chicago")
- # customer.address_street # => "May Street"
- # customer.address_city # => "Chicago"
+ # customer.address_street # => "May Street"
+ # customer.address_city # => "Chicago"
#
# == Writing value objects
#
@@ -103,12 +103,51 @@ module ActiveRecord
# returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
# changed through means other than the writer method.
#
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
# change it afterwards will result in a ActiveSupport::FrozenObjectError.
- #
+ #
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
# immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
#
+ # == Custom constructors and converters
+ #
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
+ # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
+ # this convention then +composed_of+ allows a custom constructor to be specified.
+ #
+ # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
+ # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
+ # necessary.
+ #
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
+ # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
+ # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
+ # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
+ # meet these requirements:
+ #
+ # class NetworkResource < ActiveRecord::Base
+ # composed_of :cidr,
+ # :class_name => 'NetAddr::CIDR',
+ # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
+ # :allow_nil => true,
+ # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
+ # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
+ # end
+ #
+ # # This calls the :constructor
+ # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
+ #
+ # # These assignments will both use the :converter
+ # network_resource.cidr = [ '192.168.2.1', 8 ]
+ # network_resource.cidr = '192.168.0.1/24'
+ #
+ # # This assignment won't use the :converter as the value is already an instance of the value class
+ # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
+ #
+ # # Saving and then reloading will use the :constructor on reload
+ # network_resource.save
+ # network_resource.reload
+ #
# == Finding records by a value object
#
# Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
@@ -122,47 +161,71 @@ module ActiveRecord
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
#
# Options are:
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
# from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
# if the real class name is CompanyAddress, you'll have to specify it with this option.
- # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
- # to a constructor parameter on the value class.
- # * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
- # attributes are +nil+. Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes.
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
+ # is represented as an array where the first item is the name of the entity attribute and the second item is the
+ # name the attribute in the value object. The order in which mappings are defined determine the order in which
+ # attributes are sent to the value class constructor.
+ # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
# This defaults to +false+.
- #
- # An optional block can be passed to convert the argument that is passed to the writer method into an instance of
- # <tt>:class_name</tt>. The block will only be called if the argument is not already an instance of <tt>:class_name</tt>.
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
+ # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
+ # are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
+ # The default is <tt>:new</tt>.
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
+ # called when a new value is assigned to the value object. The converter is passed the single value that is used
+ # in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) {|balance| balance.to_money }
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
# composed_of :gps_location
# composed_of :gps_location, :allow_nil => true
+ # composed_of :ip_address,
+ # :class_name => 'IPAddr',
+ # :mapping => %w(ip to_i),
+ # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
+ # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
#
def composed_of(part_id, options = {}, &block)
- options.assert_valid_keys(:class_name, :mapping, :allow_nil)
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
name = part_id.id2name
- class_name = options[:class_name] || name.camelize
- mapping = options[:mapping] || [ name, name ]
+ class_name = options[:class_name] || name.camelize
+ mapping = options[:mapping] || [ name, name ]
mapping = [ mapping ] unless mapping.first.is_a?(Array)
- allow_nil = options[:allow_nil] || false
+ allow_nil = options[:allow_nil] || false
+ constructor = options[:constructor] || :new
+ converter = options[:converter] || block
+
+ ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
+
+ reader_method(name, class_name, mapping, allow_nil, constructor)
+ writer_method(name, class_name, mapping, allow_nil, converter)
- reader_method(name, class_name, mapping, allow_nil)
- writer_method(name, class_name, mapping, allow_nil, block)
-
create_reflection(:composed_of, part_id, options, self)
end
private
- def reader_method(name, class_name, mapping, allow_nil)
+ def reader_method(name, class_name, mapping, allow_nil, constructor)
module_eval do
define_method(name) do |*args|
force_reload = args.first || false
if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- instance_variable_set("@#{name}", class_name.constantize.new(*mapping.collect {|pair| read_attribute(pair.first)}))
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
+ object = case constructor
+ when Symbol
+ class_name.constantize.send(constructor, *attrs)
+ when Proc, Method
+ constructor.call(*attrs)
+ else
+ raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
+ end
+ instance_variable_set("@#{name}", object)
end
instance_variable_get("@#{name}")
end
@@ -170,14 +233,23 @@ module ActiveRecord
end
- def writer_method(name, class_name, mapping, allow_nil, conversion)
+ def writer_method(name, class_name, mapping, allow_nil, converter)
module_eval do
define_method("#{name}=") do |part|
if part.nil? && allow_nil
mapping.each { |pair| self[pair.first] = nil }
instance_variable_set("@#{name}", nil)
else
- part = conversion.call(part) unless part.is_a?(class_name.constantize) || conversion.nil?
+ unless part.is_a?(class_name.constantize) || converter.nil?
+ part = case converter
+ when Symbol
+ class_name.constantize.send(converter, part)
+ when Proc, Method
+ converter.call(part)
+ else
+ raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
+ end
+ end
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
instance_variable_set("@#{name}", part.freeze)
end
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 61fa34ac39..c60850fc77 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -95,7 +95,7 @@ module ActiveRecord
records.each {|record| record.send(reflection.name).loaded}
options = reflection.options
- conditions = "t0.#{reflection.primary_key_name} IN (?)"
+ conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(options, preload_options)
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
@@ -222,8 +222,6 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
- conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
- conditions << append_conditions(options, preload_options)
column_type = klass.columns.detect{|c| c.name == primary_key}.type
ids = id_map.keys.uniq.map do |id|
if column_type == :integer
@@ -234,6 +232,8 @@ module ActiveRecord
id
end
end
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
+ conditions << append_conditions(options, preload_options)
associated_records = klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
@@ -248,10 +248,10 @@ module ActiveRecord
table_name = reflection.klass.quoted_table_name
if interface = reflection.options[:as]
- conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
else
foreign_key = reflection.primary_key_name
- conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
end
conditions << append_conditions(options, preload_options)
@@ -277,6 +277,9 @@ module ActiveRecord
sql
end
+ def in_or_equals_for_ids(ids)
+ ids.size > 1 ? "IN (?)" : "= ?"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f915daafba..d7aa4bfa98 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -660,8 +660,8 @@ module ActiveRecord
#
# === Example
#
- # A Firm class declares <tt>has_many :clients</tt>, which will add:
- # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
# * <tt>Firm#clients=</tt>
@@ -739,9 +739,6 @@ module ActiveRecord
# If true, all the associated objects are readonly through the association.
# [:validate]
# If false, don't validate the associated objects when saving the parent object. true by default.
- # [:accessible]
- # Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
-
# Option examples:
# has_many :comments, :order => "posted_on"
# has_many :comments, :include => :author
@@ -855,8 +852,6 @@ module ActiveRecord
# If true, the associated object is readonly through the association.
# [:validate]
# If false, don't validate the associated object when saving the parent object. +false+ by default.
- # [:accessible]
- # Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -878,10 +873,10 @@ module ActiveRecord
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
- if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
- association["#{reflection.primary_key_name}"] = id
+ if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
+ association[reflection.primary_key_name] = id
association.save(true)
end
end
@@ -973,8 +968,6 @@ module ActiveRecord
# If true, the associated object is readonly through the association.
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
- # [:accessible]
- # Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -994,7 +987,7 @@ module ActiveRecord
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
if association && association.target
if association.new_record?
@@ -1002,8 +995,8 @@ module ActiveRecord
end
if association.updated?
- self["#{reflection.primary_key_name}"] = association.id
- self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
+ self[reflection.primary_key_name] = association.id
+ self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
end
@@ -1015,7 +1008,7 @@ module ActiveRecord
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
if !association.nil?
if association.new_record?
@@ -1023,7 +1016,7 @@ module ActiveRecord
end
if association.updated?
- self["#{reflection.primary_key_name}"] = association.id
+ self[reflection.primary_key_name] = association.id
end
end
end
@@ -1038,15 +1031,15 @@ module ActiveRecord
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ association = send(reflection.name)
+ association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
end
after_create method_name
method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ association = send(reflection.name)
+ association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
end
before_destroy method_name
@@ -1164,6 +1157,9 @@ module ActiveRecord
# If true, duplicate associated objects will be ignored by accessors and query methods.
# [:finder_sql]
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
+ # [:counter_sql]
+ # Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:delete_sql]
# Overwrite the default generated SQL statement used to remove links between the associated
# classes with a manual statement.
@@ -1187,8 +1183,6 @@ module ActiveRecord
# If true, all the associated objects are readonly through the association.
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
- # [:accessible<]
- # Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -1220,12 +1214,11 @@ module ActiveRecord
end
private
- # Generate a join table name from two provided tables names.
- # The order of names in join name is determined by lexical precedence.
- # join_table_name("members", "clubs")
- # => "clubs_members"
- # join_table_name("members", "special_clubs")
- # => "members_special_clubs"
+ # Generates a join table name from two provided table names.
+ # The names in the join table namesme end up in lexicographic order.
+ #
+ # join_table_name("members", "clubs") # => "clubs_members"
+ # join_table_name("members", "special_clubs") # => "members_special_clubs"
def join_table_name(first_table_name, second_table_name)
if first_table_name < second_table_name
join_table = "#{first_table_name}_#{second_table_name}"
@@ -1244,7 +1237,7 @@ module ActiveRecord
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
- if association.nil? || force_reload
+ if association.nil? || !association.loaded? || force_reload
association = association_proxy_class.new(self, reflection)
retval = association.reload
if retval.nil? and association_proxy_class == BelongsToAssociation
@@ -1264,16 +1257,24 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
- new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
-
if association_proxy_class == HasOneThroughAssociation
association.create_through_record(new_value)
self.send(reflection.name, new_value)
else
- association.replace(new_value)
+ association.replace(new_value)
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
end
+ end
- instance_variable_set(ivar, new_value.nil? ? nil : association)
+ if association_proxy_class == BelongsToAssociation
+ define_method("#{reflection.primary_key_name}=") do |target_id|
+ if instance_variable_defined?(ivar)
+ if association = instance_variable_get(ivar)
+ association.reset
+ end
+ end
+ write_attribute(reflection.primary_key_name, target_id)
+ end
end
define_method("set_#{reflection.name}_target") do |target|
@@ -1302,7 +1303,11 @@ module ActiveRecord
end
define_method("#{reflection.name.to_s.singularize}_ids") do
- send(reflection.name).map { |record| record.id }
+ if send(reflection.name).loaded?
+ send(reflection.name).map(&:id)
+ else
+ send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
+ end
end
end
@@ -1323,19 +1328,19 @@ module ActiveRecord
end
end
end
-
+
def add_single_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = instance_variable_get("@#{association_name}")
if !association.nil?
- errors.add "#{association_name}" unless association.target.nil? || association.valid?
+ errors.add association_name unless association.target.nil? || association.valid?
end
end
-
+
validate method_name
end
-
+
def add_multiple_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
ivar = "@#{association_name}"
@@ -1351,7 +1356,7 @@ module ActiveRecord
else
association.target.select { |record| record.new_record? }
end.each do |record|
- errors.add "#{association_name}" unless record.valid?
+ errors.add association_name unless record.valid?
end
end
end
@@ -1371,7 +1376,7 @@ module ActiveRecord
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
records_to_save = if @new_record_before_save
association
@@ -1438,7 +1443,7 @@ module ActiveRecord
when :destroy
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- send("#{reflection.name}").each { |o| o.destroy }
+ send(reflection.name).each { |o| o.destroy }
end
before_destroy method_name
when :delete_all
@@ -1457,22 +1462,22 @@ module ActiveRecord
when :destroy
method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.destroy unless association.nil?
end
before_destroy method_name
when :delete
method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.class.delete(association.id) unless association.nil?
end
before_destroy method_name
when :nullify
method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil?
+ association = send(reflection.name)
+ association.update_attribute(reflection.primary_key_name, nil) unless association.nil?
end
before_destroy method_name
else
@@ -1487,14 +1492,14 @@ module ActiveRecord
when :destroy
method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.destroy unless association.nil?
end
before_destroy method_name
when :delete
method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.class.delete(association.id) unless association.nil?
end
before_destroy method_name
@@ -1504,32 +1509,38 @@ module ActiveRecord
end
end
- def create_has_many_reflection(association_id, options, &extension)
- options.assert_valid_keys(
- :class_name, :table_name, :foreign_key, :primary_key,
- :dependent,
- :select, :conditions, :include, :order, :group, :limit, :offset,
- :as, :through, :source, :source_type,
- :uniq,
- :finder_sql, :counter_sql,
- :before_add, :after_add, :before_remove, :after_remove,
- :extend, :readonly,
- :validate, :accessible
- )
+ mattr_accessor :valid_keys_for_has_many_association
+ @@valid_keys_for_has_many_association = [
+ :class_name, :table_name, :foreign_key, :primary_key,
+ :dependent,
+ :select, :conditions, :include, :order, :group, :limit, :offset,
+ :as, :through, :source, :source_type,
+ :uniq,
+ :finder_sql, :counter_sql,
+ :before_add, :after_add, :before_remove, :after_remove,
+ :extend, :readonly,
+ :validate
+ ]
+ def create_has_many_reflection(association_id, options, &extension)
+ options.assert_valid_keys(valid_keys_for_has_many_association)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
create_reflection(:has_many, association_id, options, self)
end
- def create_has_one_reflection(association_id, options)
- options.assert_valid_keys(
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible
- )
+ mattr_accessor :valid_keys_for_has_one_association
+ @@valid_keys_for_has_one_association = [
+ :class_name, :foreign_key, :remote, :select, :conditions, :order,
+ :include, :dependent, :counter_cache, :extend, :as, :readonly,
+ :validate, :primary_key
+ ]
+ def create_has_one_reflection(association_id, options)
+ options.assert_valid_keys(valid_keys_for_has_one_association)
create_reflection(:has_one, association_id, options, self)
end
-
+
def create_has_one_through_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
@@ -1537,12 +1548,15 @@ module ActiveRecord
create_reflection(:has_one, association_id, options, self)
end
- def create_belongs_to_reflection(association_id, options)
- options.assert_valid_keys(
- :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
- :counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible
- )
+ mattr_accessor :valid_keys_for_belongs_to_association
+ @@valid_keys_for_belongs_to_association = [
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
+ :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
+ :validate
+ ]
+ def create_belongs_to_reflection(association_id, options)
+ options.assert_valid_keys(valid_keys_for_belongs_to_association)
reflection = create_reflection(:belongs_to, association_id, options, self)
if options[:polymorphic]
@@ -1560,7 +1574,7 @@ module ActiveRecord
:finder_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate, :accessible
+ :validate
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1597,7 +1611,7 @@ module ActiveRecord
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -1653,7 +1667,7 @@ module ActiveRecord
if is_distinct
sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
end
add_conditions!(sql, options[:conditions], scope)
@@ -1921,7 +1935,7 @@ module ActiveRecord
end
def aliased_primary_key
- "#{ aliased_prefix }_r0"
+ "#{aliased_prefix}_r0"
end
def aliased_table_name
@@ -1933,7 +1947,7 @@ module ActiveRecord
@column_names_with_alias = []
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
- @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
end
end
@@ -1970,11 +1984,11 @@ module ActiveRecord
@aliased_prefix = "t#{ join_dependency.joins.size }"
@parent_table_name = parent.active_record.table_name
@aliased_table_name = aliased_table_name_for(table_name)
-
+
if reflection.macro == :has_and_belongs_to_many
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
end
-
+
if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
@aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
end
@@ -2111,7 +2125,7 @@ module ActiveRecord
end
protected
-
+
def aliased_table_name_for(name, suffix = nil)
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
@join_dependency.table_aliases[name] += 1
@@ -2129,7 +2143,7 @@ module ActiveRecord
name
end
-
+
def pluralize(table_name)
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 9061037b39..afb817f8ae 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -2,6 +2,19 @@ require 'set'
module ActiveRecord
module Associations
+ # AssociationCollection is an abstract class that provides common stuff to
+ # ease the implementation of association proxies that represent
+ # collections. See the class hierarchy in AssociationProxy.
+ #
+ # You need to be careful with assumptions regarding the target: The proxy
+ # does not fetch records from the database until it needs them, but new
+ # ones created with +build+ are added to the target. So, the target may be
+ # non-empty and still lack children waiting to be read from the database.
+ # If you look directly to the database you cannot assume that's the entire
+ # collection because new records may have beed added to the target, etc.
+ #
+ # If you need to work on all current children, new and existing records,
+ # +load_target+ and the +loaded+ flag are your friends.
class AssociationCollection < AssociationProxy #:nodoc:
def initialize(owner, reflection)
super
@@ -97,8 +110,6 @@ module ActiveRecord
@owner.transaction do
flatten_deeper(records).each do |record|
- record = @reflection.klass.new(record) if @reflection.options[:accessible] && record.is_a?(Hash)
-
raise_on_type_mismatch(record)
add_record_to_target_with_callbacks(record) do |r|
result &&= insert_record(record) unless @owner.new_record?
@@ -128,6 +139,35 @@ module ActiveRecord
end
end
+ # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
+ # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
+ # descendant's +construct_sql+ method will have set :counter_sql automatically.
+ # Otherwise, construct options and pass them with scope to the target class's +count+.
+ def count(*args)
+ if @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
+ else
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
+ if @reflection.options[:uniq]
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
+ options.merge!(:distinct => true)
+ end
+
+ value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
+
+ limit = @reflection.options[:limit]
+ offset = @reflection.options[:offset]
+
+ if limit || offset
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
+ else
+ value
+ end
+ end
+ end
+
+
# Remove +records+ from this association. Does not destroy +records+.
def delete(*records)
records = flatten_deeper(records)
@@ -185,12 +225,21 @@ module ActiveRecord
end
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
- # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
- # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
+ # Returns the size of the collection by executing a SELECT COUNT(*)
+ # query if the collection hasn't been loaded, and calling
+ # <tt>collection.size</tt> if it has.
+ #
+ # If the collection has been already loaded +size+ and +length+ are
+ # equivalent. If not and you are going to need the records anyway
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
+ #
+ # This method is abstract in the sense that it relies on
+ # +count_records+, which is a method descendants have to provide.
def size
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
@target.size
+ elsif !loaded? && @reflection.options[:group]
+ load_target.size
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
unsaved_records = @target.select { |r| r.new_record? }
unsaved_records.size + count_records
@@ -199,12 +248,18 @@ module ActiveRecord
end
end
- # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
- # whether the collection is empty, use collection.length.zero? instead of collection.empty?
+ # Returns the size of the collection calling +size+ on the target.
+ #
+ # If the collection has been already loaded +length+ and +size+ are
+ # equivalent. If not and you are going to need the records anyway this
+ # method will take one less query. Otherwise +size+ is more efficient.
def length
load_target.size
end
+ # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
+ # not been already loaded and you are going to fetch the records anyway
+ # it is better to check <tt>collection.length.zero?</tt>.
def empty?
size.zero?
end
@@ -231,10 +286,6 @@ module ActiveRecord
# Replace this collection with +other_array+
# This will perform a diff and delete/add only records that have changed.
def replace(other_array)
- other_array.map! do |val|
- val.is_a?(Hash) ? @reflection.klass.new(val) : val
- end if @reflection.options[:accessible]
-
other_array.each { |val| raise_on_type_mismatch(val) }
load_target
@@ -322,7 +373,9 @@ module ActiveRecord
def create_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
ensure_owner_is_not_new
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
+ @reflection.build_association(attrs)
+ end
if block_given?
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
else
@@ -332,7 +385,7 @@ module ActiveRecord
def build_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
- record = @reflection.klass.new(attrs)
+ record = @reflection.build_association(attrs)
if block_given?
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
else
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 99b8748a48..acdcd14ec8 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -39,7 +39,7 @@ module ActiveRecord
# though the object behind <tt>blog.posts</tt> is not an Array, but an
# ActiveRecord::Associations::HasManyAssociation.
#
- # The <tt>@target</tt> object is not loaded until needed. For example,
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
#
# blog.posts.count
#
@@ -57,76 +57,100 @@ module ActiveRecord
reset
end
+ # Returns the owner of the proxy.
def proxy_owner
@owner
end
+ # Returns the reflection object that represents the association handled
+ # by the proxy.
def proxy_reflection
@reflection
end
+ # Returns the \target of the proxy, same as +target+.
def proxy_target
@target
end
- def respond_to?(symbol, include_priv = false)
- proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
+ # Does the proxy or its \target respond to +symbol+?
+ def respond_to?(*args)
+ proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
end
- # Explicitly proxy === because the instance method removal above
- # doesn't catch it.
+ # Forwards <tt>===</tt> explicitly to the \target because the instance method
+ # removal above doesn't catch it. Loads the \target if needed.
def ===(other)
load_target
other === @target
end
+ # Returns the name of the table of the related class:
+ #
+ # post.comments.aliased_table_name # => "comments"
+ #
def aliased_table_name
@reflection.klass.table_name
end
+ # Returns the SQL string that corresponds to the <tt>:conditions</tt>
+ # option of the macro, if given, or +nil+ otherwise.
def conditions
@conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
end
alias :sql_conditions :conditions
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
def reset
@loaded = false
@target = nil
end
+ # Reloads the \target and returns +self+ on success.
def reload
reset
load_target
self unless @target.nil?
end
+ # Has the \target been already \loaded?
def loaded?
@loaded
end
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
def loaded
@loaded = true
end
+ # Returns the target of this proxy, same as +proxy_target+.
def target
@target
end
+ # Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
def target=(target)
@target = target
loaded
end
+ # Forwards the call to the target. Loads the \target if needed.
def inspect
load_target
@target.inspect
end
protected
+ # Does the association have a <tt>:dependent</tt> option?
def dependent?
@reflection.options[:dependent]
end
+ # Returns a string with the IDs of +records+ joined with a comma, quoted
+ # if needed. The result is ready to be inserted into a SQL IN clause.
+ #
+ # quoted_record_ids(records) # => "23,56,58,67"
+ #
def quoted_record_ids(records)
records.map { |record| record.quoted_id }.join(',')
end
@@ -135,10 +159,13 @@ module ActiveRecord
@owner.send(:interpolate_sql, sql, record)
end
+ # Forwards the call to the reflection class.
def sanitize_sql(sql)
@reflection.klass.send(:sanitize_sql, sql)
end
+ # Assigns the ID of the owner to the corresponding foreign key in +record+.
+ # If the association is polymorphic the type of the owner is also set.
def set_belongs_to_association_for(record)
if @reflection.options[:as]
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
@@ -148,6 +175,7 @@ module ActiveRecord
end
end
+ # Merges into +options+ the ones coming from the reflection.
def merge_options_from_reflection!(options)
options.reverse_merge!(
:group => @reflection.options[:group],
@@ -160,11 +188,13 @@ module ActiveRecord
)
end
+ # Forwards +with_scope+ to the reflection.
def with_scope(*args, &block)
@reflection.klass.send :with_scope, *args, &block
end
private
+ # Forwards any missing method call to the \target.
def method_missing(method, *args)
if load_target
if block_given?
@@ -175,16 +205,16 @@ module ActiveRecord
end
end
- # Loads the target if needed and returns it.
+ # Loads the \target if needed and returns it.
#
# This method is abstract in the sense that it relies on +find_target+,
# which is expected to be provided by descendants.
#
- # If the target is already loaded it is just returned. Thus, you can call
- # +load_target+ unconditionally to get the target.
+ # If the \target is already \loaded it is just returned. Thus, you can call
+ # +load_target+ unconditionally to get the \target.
#
# ActiveRecord::RecordNotFound is rescued within the method, and it is
- # not reraised. The proxy is reset and +nil+ is the return value.
+ # not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
return nil unless defined?(@loaded)
@@ -198,12 +228,17 @@ module ActiveRecord
reset
end
- # Can be overwritten by associations that might have the foreign key available for an association without
- # having the object itself (and still being a new record). Currently, only belongs_to presents this scenario.
+ # Can be overwritten by associations that might have the foreign key
+ # available for an association without having the object itself (and
+ # still being a new record). Currently, only +belongs_to+ presents
+ # this scenario (both vanilla and polymorphic).
def foreign_key_present
false
end
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
+ # the kind of the class of the associated objects. Meant to be used as
+ # a sanity check when you are about to assign an associated record.
def raise_on_type_mismatch(record)
unless record.is_a?(@reflection.klass)
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
@@ -211,11 +246,13 @@ module ActiveRecord
end
end
- # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
+ # Array#flatten has problems with recursive arrays. Going one level
+ # deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
end
+ # Returns the ID of the owner, quoted if needed.
def owner_quoted_id
@owner.quoted_id
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 7c28cbdd07..f05c6be075 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -2,11 +2,11 @@ module ActiveRecord
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
def create(attributes = {})
- replace(@reflection.klass.create(attributes))
+ replace(@reflection.create_association(attributes))
end
def build(attributes = {})
- replace(@reflection.klass.new(attributes))
+ replace(@reflection.build_association(attributes))
end
def replace(record)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index e7e433b6b6..3d689098b5 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -78,6 +78,16 @@ module ActiveRecord
end
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
+
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ else
+ @counter_sql = @finder_sql
+ end
end
def construct_scope
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index ce62127505..3b2f306637 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -1,23 +1,10 @@
module ActiveRecord
module Associations
+ # This is the proxy that handles a has many association.
+ #
+ # If the association has a <tt>:through</tt> option further specialization
+ # is provided by its child HasManyThroughAssociation.
class HasManyAssociation < AssociationCollection #:nodoc:
- # Count the number of associated records. All arguments are optional.
- def count(*args)
- if @reflection.options[:counter_sql]
- @reflection.klass.count_by_sql(@counter_sql)
- elsif @reflection.options[:finder_sql]
- @reflection.klass.count_by_sql(@finder_sql)
- else
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- options[:conditions] = options[:conditions].blank? ?
- @finder_sql :
- @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
- options[:include] ||= @reflection.options[:include]
-
- @reflection.klass.count(column_name, options)
- end
- end
-
protected
def owner_quoted_id
if @reflection.options[:primary_key]
@@ -27,6 +14,19 @@ module ActiveRecord
end
end
+ # Returns the number of records in this collection.
+ #
+ # If the association has a counter cache it gets that value. Otherwise
+ # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
+ # there's one. Some configuration options like :group make it impossible
+ # to do a SQL count, in those cases the array count will be used.
+ #
+ # That does not depend on whether the collection has already been loaded
+ # or not. The +size+ method is the one that takes the loaded flag into
+ # account and delegates to +count_records+ if needed.
+ #
+ # If the collection is empty the target is set to an empty array and
+ # the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name)
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 24b02efc35..ebd2bf768c 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -10,14 +10,14 @@ module ActiveRecord
def create!(attrs = nil)
@reflection.klass.transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
object
end
end
def create(attrs = nil)
@reflection.klass.transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
object
end
end
@@ -31,16 +31,6 @@ module ActiveRecord
return count
end
- def count(*args)
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- if @reflection.options[:uniq]
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
- column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
- options.merge!(:distinct => true)
- end
- @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
- end
-
protected
def construct_find_options!(options)
options[:select] = construct_select(options[:select])
@@ -57,8 +47,9 @@ module ActiveRecord
return false unless record.save
end
end
- klass = @reflection.through_reflection.klass
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
+ through_reflection = @reflection.through_reflection
+ klass = through_reflection.klass
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
end
# TODO - add dependent option support
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 18733255d2..c92ef5c2c9 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -7,15 +7,21 @@ module ActiveRecord
end
def create(attrs = {}, replace_existing = true)
- new_record(replace_existing) { |klass| klass.create(attrs) }
+ new_record(replace_existing) do |reflection|
+ reflection.create_association(attrs)
+ end
end
def create!(attrs = {}, replace_existing = true)
- new_record(replace_existing) { |klass| klass.create!(attrs) }
+ new_record(replace_existing) do |reflection|
+ reflection.create_association!(attrs)
+ end
end
def build(attrs = {}, replace_existing = true)
- new_record(replace_existing) { |klass| klass.new(attrs) }
+ new_record(replace_existing) do |reflection|
+ reflection.build_association(attrs)
+ end
end
def replace(obj, dont_save = false)
@@ -91,7 +97,9 @@ module ActiveRecord
# instance. Otherwise, if the target has not previously been loaded
# elsewhere, the instance we create will get orphaned.
load_target if replace_existing
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
+ yield @reflection
+ end
if replace_existing
replace(record, true)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index c846956e1f..b78bd5d931 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -22,6 +22,10 @@ module ActiveRecord
def find_target
super.first
+ end
+
+ def reset_target!
+ @target = nil
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index fab16a4446..020da01871 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -10,7 +10,7 @@ module ActiveRecord
base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
base.time_zone_aware_attributes = false
- base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
base.skip_time_zone_conversion_for_attributes = []
end
@@ -214,7 +214,7 @@ module ActiveRecord
if logger
logger.warn "Exception occurred during reader method compilation."
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
- logger.warn "#{err.message}"
+ logger.warn err.message
end
end
end
@@ -330,8 +330,8 @@ module ActiveRecord
end
end
- # A Person object with a name attribute can ask <tt>person.respond_to?("name")</tt>,
- # <tt>person.respond_to?("name=")</tt>, and <tt>person.respond_to?("name?")</tt>
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
# which will all return +true+.
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_priv = false)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 5c30f80555..b20da512eb 100644..100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -274,7 +274,7 @@ module ActiveRecord #:nodoc:
# == Dynamic attribute-based finders
#
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
- # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
+ # appending the name of an attribute to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
# <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
# <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
# And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
@@ -287,6 +287,7 @@ module ActiveRecord #:nodoc:
# It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
# is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
# actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
+ # Also you may call <tt>Payment.find_last_by_amount(amount, options)</tt> returning the last record matching that amount and options.
#
# The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
# <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
@@ -452,13 +453,6 @@ module ActiveRecord #:nodoc:
cattr_accessor :default_timezone, :instance_writer => false
@@default_timezone = :local
- # Determines whether to use a connection for each thread, or a single shared connection for all threads.
- # Defaults to false. If you're writing a threaded application, set to true
- # and periodically call verify_active_connections! to clear out connections
- # assigned to stale threads.
- cattr_accessor :allow_concurrency, :instance_writer => false
- @@allow_concurrency = false
-
# Specifies the format to use when dumping the database schema with Rails'
# Rakefile. If :sql, the schema is dumped as (potentially database-
# specific) SQL statements. If :ruby, the schema is dumped as an
@@ -507,7 +501,7 @@ module ActiveRecord #:nodoc:
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
# * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
- # include the joined columns.
+ # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
# of a database view).
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
@@ -752,13 +746,15 @@ module ActiveRecord #:nodoc:
end
# Updates all records with details given if they match a set of conditions supplied, limits and order can
- # also be supplied.
+ # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
+ # database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
#
# ==== Attributes
#
- # * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
+ # * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
+ # What goes into the SET clause.
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
- # * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
+ # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
#
@@ -773,15 +769,29 @@ module ActiveRecord #:nodoc:
# :order => 'created_at', :limit => 5 )
def update_all(updates, conditions = nil, options = {})
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
+
scope = scope(:find)
- add_conditions!(sql, conditions, scope)
- add_order!(sql, options[:order], nil)
- add_limit!(sql, options, nil)
+
+ select_sql = ""
+ add_conditions!(select_sql, conditions, scope)
+
+ if options.has_key?(:limit) || (scope && scope[:limit])
+ # Only take order from scope if limit is also provided by scope, this
+ # is useful for updating a has_many association with a limit.
+ add_order!(select_sql, options[:order], scope)
+
+ add_limit!(select_sql, options, scope)
+ sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
+ else
+ add_order!(select_sql, options[:order], nil)
+ sql.concat(select_sql)
+ end
+
connection.update(sql, "#{name} Update")
end
- # Destroys the records matching +conditions+ by instantiating each record and calling the destroy method.
- # This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting
+ # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
+ # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
# many records. If you want to simply delete records without worrying about dependent associations or
# callbacks, use the much faster +delete_all+ method instead.
#
@@ -800,8 +810,9 @@ module ActiveRecord #:nodoc:
end
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
- # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
- # than destroy_all.
+ # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
+ # goes straight to the database, much more efficient than +destroy_all+. Careful with relations
+ # though, in particular <tt>:dependent</tt> is not taken into account.
#
# ==== Attributes
#
@@ -811,8 +822,8 @@ module ActiveRecord #:nodoc:
#
# Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
#
- # This deletes the affected posts all at once with a single DELETE query. If you need to destroy dependent
- # associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead.
+ # This deletes the affected posts all at once with a single DELETE statement. If you need to destroy dependent
+ # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
sql = "DELETE FROM #{quoted_table_name} "
add_conditions!(sql, conditions, scope(:find))
@@ -927,12 +938,12 @@ module ActiveRecord #:nodoc:
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
def attr_protected(*attributes)
- write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
end
# Returns an array of all the attributes that have been protected from mass-assignment.
def protected_attributes # :nodoc:
- read_inheritable_attribute("attr_protected")
+ read_inheritable_attribute(:attr_protected)
end
# Specifies a white list of model attributes that can be set via
@@ -960,22 +971,22 @@ module ActiveRecord #:nodoc:
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
def attr_accessible(*attributes)
- write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
+ write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
end
# Returns an array of all the attributes that have been made accessible to mass-assignment.
def accessible_attributes # :nodoc:
- read_inheritable_attribute("attr_accessible")
+ read_inheritable_attribute(:attr_accessible)
end
# Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
def attr_readonly(*attributes)
- write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
- read_inheritable_attribute("attr_readonly")
+ read_inheritable_attribute(:attr_readonly)
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -999,7 +1010,7 @@ module ActiveRecord #:nodoc:
# Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
def serialized_attributes
- read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
+ read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
end
@@ -1317,7 +1328,7 @@ module ActiveRecord #:nodoc:
if logger && logger.level <= log_level
result = nil
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
+ logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
result
else
yield
@@ -1549,7 +1560,7 @@ module ActiveRecord #:nodoc:
sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
@@ -1565,6 +1576,22 @@ module ActiveRecord #:nodoc:
(safe_to_array(first) + safe_to_array(second)).uniq
end
+ def merge_joins(first, second)
+ if first.is_a?(String) && second.is_a?(String)
+ "#{first} #{second}"
+ elsif first.is_a?(String) || second.is_a?(String)
+ if first.is_a?(String)
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, second, nil)
+ "#{first} #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join}"
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, first, nil)
+ "#{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} #{second}"
+ end
+ else
+ (safe_to_array(first) + safe_to_array(second)).uniq
+ end
+ end
+
# Object#to_a is deprecated, though it does have the desired behavior
def safe_to_array(o)
case o
@@ -1620,16 +1647,15 @@ module ActiveRecord #:nodoc:
end
# The optional scope argument is for the current <tt>:find</tt> scope.
- def add_joins!(sql, options, scope = :auto)
+ def add_joins!(sql, joins, scope = :auto)
scope = scope(:find) if :auto == scope
- [(scope && scope[:joins]), options[:joins]].each do |join|
- case join
- when Symbol, Hash, Array
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
- sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
- else
- sql << " #{join} "
- end
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ case merged_joins
+ when Symbol, Hash, Array
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
+ when String
+ sql << " #{merged_joins} "
end
end
@@ -1879,6 +1905,8 @@ module ActiveRecord #:nodoc:
hash[method][key] = merge_conditions(params[key], hash[method][key])
elsif key == :include && merge
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
+ elsif key == :joins && merge
+ hash[method][key] = merge_joins(params[key], hash[method][key])
else
hash[method][key] = hash[method][key] || params[key]
end
@@ -1926,22 +1954,11 @@ module ActiveRecord #:nodoc:
end
end
- def thread_safe_scoped_methods #:nodoc:
+ def scoped_methods #:nodoc:
scoped_methods = (Thread.current[:scoped_methods] ||= {})
scoped_methods[self] ||= []
end
- def single_threaded_scoped_methods #:nodoc:
- @scoped_methods ||= []
- end
-
- # pick up the correct scoped_methods version from @@allow_concurrency
- if @@allow_concurrency
- alias_method :scoped_methods, :thread_safe_scoped_methods
- else
- alias_method :scoped_methods, :single_threaded_scoped_methods
- end
-
def current_scoped_methods #:nodoc:
scoped_methods.last
end
@@ -2135,7 +2152,7 @@ module ActiveRecord #:nodoc:
end
def quote_bound_value(value) #:nodoc:
- if value.respond_to?(:map) && !value.is_a?(String)
+ if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
connection.quote(nil)
else
@@ -2249,20 +2266,40 @@ module ActiveRecord #:nodoc:
defined?(@new_record) && @new_record
end
- # * No record exists: Creates a new record with values matching those of the object attributes.
- # * A record does exist: Updates the record with values matching those of the object attributes.
+ # :call-seq:
+ # save(perform_validation = true)
#
- # Note: If your model specifies any validations then the method declaration dynamically
- # changes to:
- # save(perform_validation=true)
- # Calling save(false) saves the model without running validations.
- # See ActiveRecord::Validations for more information.
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # If +perform_validation+ is true validations run. If any of them fail
+ # the action is cancelled and +save+ returns +false+. If the flag is
+ # false validations are bypassed altogether. See
+ # ActiveRecord::Validations for more information.
+ #
+ # There's a series of callbacks associated with +save+. If any of the
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
+ # details.
def save
create_or_update
end
- # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
- # RecordNotSaved exception
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # With <tt>save!</tt> validations always run. If any of them fail
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
+ # for more information.
+ #
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
+ # ActiveRecord::Callbacks for further details.
def save!
create_or_update || raise(RecordNotSaved)
end
@@ -2693,7 +2730,7 @@ module ActiveRecord #:nodoc:
end
def instantiate_time_object(name, values)
- if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
Time.zone.local(*values)
else
Time.time_with_datetime_fallback(@@default_timezone, *values)
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 246f87b7a9..80992dd34b 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -14,7 +14,7 @@ module ActiveRecord
#
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
# or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
@@ -98,7 +98,7 @@ module ActiveRecord
# end
#
# Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
# * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
# * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
# The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
@@ -186,11 +186,17 @@ module ActiveRecord
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "
end
+
+ joins = ""
+ add_joins!(joins, options[:joins], scope)
+
if merged_includes.any?
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
end
- add_joins!(sql, options, scope)
+
+ sql << joins unless joins.blank?
+
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -211,7 +217,7 @@ module ActiveRecord
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
- sql << ') AS #{aggregate_alias}_subquery' if use_workaround
+ sql << ") AS #{aggregate_alias}_subquery" if use_workaround
sql
end
@@ -260,7 +266,14 @@ module ActiveRecord
# column_alias_for("count(*)") # => "count_all"
# column_alias_for("count", "id") # => "count_id"
def column_alias_for(*keys)
- connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
+ table_name = keys.join(' ')
+ table_name.downcase!
+ table_name.gsub!(/\*/, 'all')
+ table_name.gsub!(/\W+/, ' ')
+ table_name.strip!
+ table_name.gsub!(/ +/, '_')
+
+ connection.table_alias_for(table_name)
end
def column_for(field)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index eec531c514..dd7ae51096 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -3,7 +3,7 @@ require 'observer'
module ActiveRecord
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
# before or after an alteration of the object state. This can be used to make sure that associated and
- # dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes
+ # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
# the <tt>Base#save</tt> call:
#
@@ -161,7 +161,7 @@ module ActiveRecord
# == <tt>before_validation*</tt> returning statements
#
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
- # If Base#save! is called it will raise a RecordNotSaved exception.
+ # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
@@ -209,6 +209,8 @@ module ActiveRecord
def before_save() end
# Is called _after_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
+ # invoke an external indexer at this point it won't see the changes in the database.
#
# class Contact < ActiveRecord::Base
# after_save { logger.info( 'New contact saved!' ) }
@@ -226,6 +228,8 @@ module ActiveRecord
def before_create() end
# Is called _after_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
+ # invoke an external indexer at this point it won't see the changes in the database.
def after_create() end
def create_with_callbacks #:nodoc:
return false if callback(:before_create) == false
@@ -239,6 +243,8 @@ module ActiveRecord
def before_update() end
# Is called _after_ <tt>Base.save</tt> on existing objects that have a record.
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
+ # invoke an external indexer at this point it won't see the changes in the database.
def after_update() end
def update_with_callbacks(*args) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
new file mode 100644
index 0000000000..54a39db1eb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -0,0 +1,278 @@
+require 'monitor'
+require 'set'
+
+module ActiveRecord
+ # Raised when a connection could not be obtained within the connection
+ # acquisition timeout period.
+ class ConnectionTimeoutError < ConnectionNotEstablished
+ end
+
+ module ConnectionAdapters
+ # Connection pool base class for managing ActiveRecord database
+ # connections.
+ #
+ # Connections can be obtained and used from a connection pool in several
+ # ways:
+ #
+ # 1. Simply use ActiveRecord::Base.connection as with ActiveRecord 2.1 and
+ # earlier (pre-connection-pooling). Eventually, when you're done with
+ # the connection(s) and wish it to be returned to the pool, you call
+ # ActiveRecord::Base.clear_active_connections!. This will be the
+ # default behavior for ActiveRecord when used in conjunction with
+ # ActionPack's request handling cycle.
+ # 2. Manually check out a connection from the pool with
+ # ActiveRecord::Base.connection_pool.checkout. You are responsible for
+ # returning this connection to the pool when finished by calling
+ # ActiveRecord::Base.connection_pool.checkin(connection).
+ # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
+ # obtains a connection, yields it as the sole argument to the block,
+ # and returns it to the pool after the block completes.
+ #
+ # There are two connection-pooling-related options that you can add to
+ # your database connection configuration:
+ #
+ # * +pool+: number indicating size of connection pool (default 5)
+ # * +wait_timeout+: number of seconds to block and wait for a connection
+ # before giving up and raising a timeout error (default 5 seconds).
+ class ConnectionPool
+ attr_reader :spec
+
+ def initialize(spec)
+ @spec = spec
+ # The cache of reserved connections mapped to threads
+ @reserved_connections = {}
+ # The mutex used to synchronize pool access
+ @connection_mutex = Monitor.new
+ @queue = @connection_mutex.new_cond
+ # default 5 second timeout
+ @timeout = spec.config[:wait_timeout] || 5
+ # default max pool size to 5
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @connections = []
+ @checked_out = []
+ end
+
+ # Retrieve the connection associated with the current thread, or call
+ # #checkout to obtain one if necessary.
+ #
+ # #connection can be called any number of times; the connection is
+ # held in a hash keyed by the thread id.
+ def connection
+ if conn = @reserved_connections[current_connection_id]
+ conn
+ else
+ @reserved_connections[current_connection_id] = checkout
+ end
+ end
+
+ # Signal that the thread is finished with the current connection.
+ # #release_connection releases the connection-thread association
+ # and returns the connection to the pool.
+ def release_connection
+ conn = @reserved_connections.delete(current_connection_id)
+ checkin conn if conn
+ end
+
+ # Reserve a connection, and yield it to a block. Ensure the connection is
+ # checked back in when finished.
+ def with_connection
+ conn = checkout
+ yield conn
+ ensure
+ checkin conn
+ end
+
+ # Returns true if a connection has already been opened.
+ def connected?
+ !@connections.empty?
+ end
+
+ # Disconnect all connections in the pool.
+ def disconnect!
+ @reserved_connections.each do |name,conn|
+ checkin conn
+ end
+ @reserved_connections = {}
+ @connections.each do |conn|
+ conn.disconnect!
+ end
+ @connections = []
+ end
+
+ # Clears the cache which maps classes
+ def clear_reloadable_connections!
+ @reserved_connections.each do |name, conn|
+ checkin conn
+ end
+ @reserved_connections = {}
+ @connections.each do |conn|
+ conn.disconnect! if conn.requires_reloading?
+ end
+ @connections = []
+ end
+
+ # Verify active connections and remove and disconnect connections
+ # associated with stale threads.
+ def verify_active_connections! #:nodoc:
+ clear_stale_cached_connections!
+ @connections.each do |connection|
+ connection.verify!
+ end
+ end
+
+ # Return any checked-out connections back to the pool by threads that
+ # are no longer alive.
+ def clear_stale_cached_connections!
+ remove_stale_cached_threads!(@reserved_connections) do |name, conn|
+ checkin conn
+ end
+ end
+
+ # Check-out a database connection from the pool.
+ def checkout
+ # Checkout an available connection
+ conn = @connection_mutex.synchronize do
+ if @checked_out.size < @connections.size
+ checkout_existing_connection
+ elsif @connections.size < @size
+ checkout_new_connection
+ end
+ end
+ return conn if conn
+
+ # No connections available; wait for one
+ @connection_mutex.synchronize do
+ if @queue.wait(@timeout)
+ checkout_existing_connection
+ else
+ raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?"
+ end
+ end
+ end
+
+ # Check-in a database connection back into the pool.
+ def checkin(conn)
+ @connection_mutex.synchronize do
+ conn.run_callbacks :checkin
+ @checked_out.delete conn
+ @queue.signal
+ end
+ end
+
+ synchronize :clear_reloadable_connections!, :verify_active_connections!,
+ :connected?, :disconnect!, :with => :@connection_mutex
+
+ private
+ def new_connection
+ ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ end
+
+ def current_connection_id #:nodoc:
+ Thread.current.object_id
+ end
+
+ # Remove stale threads from the cache.
+ def remove_stale_cached_threads!(cache, &block)
+ keys = Set.new(cache.keys)
+
+ Thread.list.each do |thread|
+ keys.delete(thread.object_id) if thread.alive?
+ end
+ keys.each do |key|
+ next unless cache.has_key?(key)
+ block.call(key, cache[key])
+ cache.delete(key)
+ end
+ end
+
+ def checkout_new_connection
+ c = new_connection
+ @connections << c
+ checkout_and_verify(c)
+ end
+
+ def checkout_existing_connection
+ c = (@connections - @checked_out).first
+ checkout_and_verify(c)
+ end
+
+ def checkout_and_verify(c)
+ c.verify!
+ c.run_callbacks :checkout
+ @checked_out << c
+ c
+ end
+ end
+
+ class ConnectionHandler
+ def initialize(pools = {})
+ @connection_pools = pools
+ end
+
+ def connection_pools
+ @connection_pools ||= {}
+ end
+
+ def establish_connection(name, spec)
+ @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
+ end
+
+ # Returns any connections in use by the current thread back to the pool,
+ # and also returns connections to the pool cached by threads that are no
+ # longer alive.
+ def clear_active_connections!
+ @connection_pools.each_value do |pool|
+ pool.release_connection
+ pool.clear_stale_cached_connections!
+ end
+ end
+
+ # Clears the cache which maps classes
+ def clear_reloadable_connections!
+ @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
+ end
+
+ def clear_all_connections!
+ @connection_pools.each_value {|pool| pool.disconnect! }
+ end
+
+ # Verify active connections.
+ def verify_active_connections! #:nodoc:
+ @connection_pools.each_value {|pool| pool.verify_active_connections! }
+ end
+
+ # Locate the connection of the nearest super class. This can be an
+ # active or defined connection: if it is the latter, it will be
+ # opened and set as the active connection for the class it was defined
+ # for (not necessarily the current class).
+ def retrieve_connection(klass) #:nodoc:
+ pool = retrieve_connection_pool(klass)
+ (pool && pool.connection) or raise ConnectionNotEstablished
+ end
+
+ # Returns true if a connection that's accessible to this class has
+ # already been opened.
+ def connected?(klass)
+ retrieve_connection_pool(klass).connected?
+ end
+
+ # Remove the connection for this class. This will close the active
+ # connection and the defined connection (if they exist). The result
+ # can be used as an argument for establish_connection, for easily
+ # re-establishing the connection.
+ def remove_connection(klass)
+ pool = @connection_pools[klass.name]
+ @connection_pools.delete_if { |key, value| value == pool }
+ pool.disconnect! if pool
+ pool.spec.config if pool
+ end
+
+ def retrieve_connection_pool(klass)
+ pool = @connection_pools[klass.name]
+ return pool if pool
+ return nil if ActiveRecord::Base == klass
+ retrieve_connection_pool klass.superclass
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 2a8807fb78..a968fc0fd3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -1,5 +1,3 @@
-require 'set'
-
module ActiveRecord
class Base
class ConnectionSpecification #:nodoc:
@@ -9,163 +7,9 @@ module ActiveRecord
end
end
- # Check for activity after at least +verification_timeout+ seconds.
- # Defaults to 0 (always check.)
- cattr_accessor :verification_timeout, :instance_writer => false
- @@verification_timeout = 0
-
- # The class -> [adapter_method, config] map
- @@defined_connections = {}
-
- # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
- @@active_connections = {}
-
- class << self
- # Retrieve the connection cache.
- def thread_safe_active_connections #:nodoc:
- @@active_connections[Thread.current.object_id] ||= {}
- end
-
- def single_threaded_active_connections #:nodoc:
- @@active_connections
- end
-
- # pick up the right active_connection method from @@allow_concurrency
- if @@allow_concurrency
- alias_method :active_connections, :thread_safe_active_connections
- else
- alias_method :active_connections, :single_threaded_active_connections
- end
-
- # set concurrency support flag (not thread safe, like most of the methods in this file)
- def allow_concurrency=(threaded) #:nodoc:
- logger.debug "allow_concurrency=#{threaded}" if logger
- return if @@allow_concurrency == threaded
- clear_all_cached_connections!
- @@allow_concurrency = threaded
- method_prefix = threaded ? "thread_safe" : "single_threaded"
- sing = (class << self; self; end)
- [:active_connections, :scoped_methods].each do |method|
- sing.send(:alias_method, method, "#{method_prefix}_#{method}")
- end
- log_connections if logger
- end
-
- def active_connection_name #:nodoc:
- @active_connection_name ||=
- if active_connections[name] || @@defined_connections[name]
- name
- elsif self == ActiveRecord::Base
- nil
- else
- superclass.active_connection_name
- end
- end
-
- def clear_active_connection_name #:nodoc:
- @active_connection_name = nil
- subclasses.each { |klass| klass.clear_active_connection_name }
- end
-
- # Returns the connection currently associated with the class. This can
- # also be used to "borrow" the connection to do database work unrelated
- # to any of the specific Active Records.
- def connection
- if defined?(@active_connection_name) && (conn = active_connections[@active_connection_name])
- conn
- else
- # retrieve_connection sets the cache key.
- conn = retrieve_connection
- active_connections[@active_connection_name] = conn
- end
- end
-
- # Clears the cache which maps classes to connections.
- def clear_active_connections!
- clear_cache!(@@active_connections) do |name, conn|
- conn.disconnect!
- end
- end
-
- # Clears the cache which maps classes
- def clear_reloadable_connections!
- if @@allow_concurrency
- # With concurrent connections @@active_connections is
- # a hash keyed by thread id.
- @@active_connections.each do |thread_id, conns|
- conns.each do |name, conn|
- if conn.requires_reloading?
- conn.disconnect!
- @@active_connections[thread_id].delete(name)
- end
- end
- end
- else
- @@active_connections.each do |name, conn|
- if conn.requires_reloading?
- conn.disconnect!
- @@active_connections.delete(name)
- end
- end
- end
- end
-
- # Verify active connections.
- def verify_active_connections! #:nodoc:
- if @@allow_concurrency
- remove_stale_cached_threads!(@@active_connections) do |name, conn|
- conn.disconnect!
- end
- end
-
- active_connections.each_value do |connection|
- connection.verify!(@@verification_timeout)
- end
- end
-
- private
- def clear_cache!(cache, thread_id = nil, &block)
- if cache
- if @@allow_concurrency
- thread_id ||= Thread.current.object_id
- thread_cache, cache = cache, cache[thread_id]
- return unless cache
- end
-
- cache.each(&block) if block_given?
- cache.clear
- end
- ensure
- if thread_cache && @@allow_concurrency
- thread_cache.delete(thread_id)
- end
- end
-
- # Remove stale threads from the cache.
- def remove_stale_cached_threads!(cache, &block)
- stale = Set.new(cache.keys)
-
- Thread.list.each do |thread|
- stale.delete(thread.object_id) if thread.alive?
- end
-
- stale.each do |thread_id|
- clear_cache!(cache, thread_id, &block)
- end
- end
-
- def clear_all_cached_connections!
- if @@allow_concurrency
- @@active_connections.each_value do |connection_hash_for_thread|
- connection_hash_for_thread.each_value {|conn| conn.disconnect! }
- connection_hash_for_thread.clear
- end
- else
- @@active_connections.each_value {|conn| conn.disconnect! }
- end
- @@active_connections.clear
- end
- end
+ # The connection handler
+ cattr_accessor :connection_handler, :instance_writer => false
+ @@connection_handler = ConnectionAdapters::ConnectionHandler.new
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work that isn't
@@ -208,9 +52,7 @@ module ActiveRecord
raise AdapterNotSpecified unless defined? RAILS_ENV
establish_connection(RAILS_ENV)
when ConnectionSpecification
- clear_active_connection_name
- @active_connection_name = name
- @@defined_connections[name] = spec
+ @@connection_handler.establish_connection(name, spec)
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
@@ -243,67 +85,52 @@ module ActiveRecord
end
end
- # Locate the connection of the nearest super class. This can be an
- # active or defined connection: if it is the latter, it will be
- # opened and set as the active connection for the class it was defined
- # for (not necessarily the current class).
- def self.retrieve_connection #:nodoc:
- # Name is nil if establish_connection hasn't been called for
- # some class along the inheritance chain up to AR::Base yet.
- if name = active_connection_name
- if conn = active_connections[name]
- # Verify the connection.
- conn.verify!(@@verification_timeout)
- elsif spec = @@defined_connections[name]
- # Activate this connection specification.
- klass = name.constantize
- klass.connection = spec
- conn = active_connections[name]
- end
+ class << self
+ # Deprecated and no longer has any effect.
+ def allow_concurrency
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency has been deprecated and no longer has any effect. Please remove all references to allow_concurrency.")
end
- conn or raise ConnectionNotEstablished
- end
+ # Deprecated and no longer has any effect.
+ def allow_concurrency=(flag)
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency= has been deprecated and no longer has any effect. Please remove all references to allow_concurrency=.")
+ end
- # Returns true if a connection that's accessible to this class has already been opened.
- def self.connected?
- active_connections[active_connection_name] ? true : false
- end
+ # Deprecated and no longer has any effect.
+ def verification_timeout
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout has been deprecated and no longer has any effect. Please remove all references to verification_timeout.")
+ end
- # Remove the connection for this class. This will close the active
- # connection and the defined connection (if they exist). The result
- # can be used as an argument for establish_connection, for easily
- # re-establishing the connection.
- def self.remove_connection(klass=self)
- spec = @@defined_connections[klass.name]
- konn = active_connections[klass.name]
- @@defined_connections.delete_if { |key, value| value == spec }
- active_connections.delete_if { |key, value| value == konn }
- konn.disconnect! if konn
- spec.config if spec
- end
+ # Deprecated and no longer has any effect.
+ def verification_timeout=(flag)
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout= has been deprecated and no longer has any effect. Please remove all references to verification_timeout=.")
+ end
- # Set the connection for the class.
- def self.connection=(spec) #:nodoc:
- if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
- active_connections[name] = spec
- elsif spec.kind_of?(ConnectionSpecification)
- config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
- self.connection = self.send(spec.adapter_method, config)
- elsif spec.nil?
- raise ConnectionNotEstablished
- else
- establish_connection spec
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work unrelated
+ # to any of the specific Active Records.
+ def connection
+ retrieve_connection
end
- end
- # connection state logging
- def self.log_connections #:nodoc:
- if logger
- logger.info "Defined connections: #{@@defined_connections.inspect}"
- logger.info "Active connections: #{active_connections.inspect}"
- logger.info "Active connection name: #{@active_connection_name}"
+ def connection_pool
+ connection_handler.retrieve_connection_pool(self)
end
+
+ def retrieve_connection
+ connection_handler.retrieve_connection(self)
+ end
+
+ def connected?
+ connection_handler.connected?(self)
+ end
+
+ def remove_connection(klass = self)
+ connection_handler.remove_connection(klass)
+ end
+
+ delegate :clear_active_connections!, :clear_reloadable_connections!,
+ :clear_all_connections!,:verify_active_connections!, :to => :connection_handler
end
end
end
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 aaf9e2e73f..8fc89de22b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -153,6 +153,10 @@ module ActiveRecord
"="
end
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
+ end
+
protected
# Returns an array of record hashes with the column names as keys and
# column values as values.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 2afd6064ad..2fc50b9bfa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -4,7 +4,6 @@ module ActiveRecord
class << self
def included(base)
base.class_eval do
- attr_accessor :query_cache_enabled
alias_method_chain :columns, :query_cache
alias_method_chain :select_all, :query_cache
end
@@ -16,7 +15,7 @@ module ActiveRecord
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__
def #{method_name}_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled
+ clear_query_cache if query_cache_enabled
#{method_name}_without_query_dirty(*args)
end
@@ -26,22 +25,38 @@ module ActiveRecord
end
end
+ def query_cache_enabled
+ Thread.current['query_cache_enabled']
+ end
+
+ def query_cache_enabled=(flag)
+ Thread.current['query_cache_enabled'] = flag
+ end
+
+ def query_cache
+ Thread.current['query_cache']
+ end
+
+ def query_cache=(cache)
+ Thread.current['query_cache'] = cache
+ end
+
# Enable the query cache within the block.
def cache
- old, @query_cache_enabled = @query_cache_enabled, true
- @query_cache ||= {}
+ old, self.query_cache_enabled = query_cache_enabled, true
+ self.query_cache ||= {}
yield
ensure
clear_query_cache
- @query_cache_enabled = old
+ self.query_cache_enabled = old
end
# Disable the query cache within the block.
def uncached
- old, @query_cache_enabled = @query_cache_enabled, false
+ old, self.query_cache_enabled = query_cache_enabled, false
yield
ensure
- @query_cache_enabled = old
+ self.query_cache_enabled = old
end
# Clears the query cache.
@@ -51,11 +66,11 @@ module ActiveRecord
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
- @query_cache.clear if @query_cache
+ query_cache.clear if query_cache
end
def select_all_with_query_cache(*args)
- if @query_cache_enabled
+ if query_cache_enabled
cache_sql(args.first) { select_all_without_query_cache(*args) }
else
select_all_without_query_cache(*args)
@@ -63,8 +78,8 @@ module ActiveRecord
end
def columns_with_query_cache(*args)
- if @query_cache_enabled
- @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
+ if query_cache_enabled
+ query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
else
columns_without_query_cache(*args)
end
@@ -73,11 +88,11 @@ module ActiveRecord
private
def cache_sql(sql)
result =
- if @query_cache.has_key?(sql)
+ if query_cache.has_key?(sql)
log_info(sql, "CACHE", 0.0)
- @query_cache[sql]
+ query_cache[sql]
else
- @query_cache[sql] = yield
+ query_cache[sql] = yield
end
if Array === result
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 75032efe57..58992f91da 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -40,6 +40,10 @@ module ActiveRecord
type == :integer || type == :float || type == :decimal
end
+ def has_default?
+ !default.nil?
+ end
+
# Returns the Ruby class that corresponds to the abstract data type.
def klass
case type
@@ -252,6 +256,10 @@ module ActiveRecord
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
end
+ # Abstract representation of a column definition. Instances of this type
+ # are typically created by methods in TableDefinition, and added to the
+ # +columns+ attribute of said TableDefinition object, in order to be used
+ # for generating a number of table creation or table changing SQL statements.
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
def sql_type
@@ -275,9 +283,29 @@ module ActiveRecord
end
end
- # Represents a SQL table in an abstract way.
- # Columns are stored as a ColumnDefinition in the +columns+ attribute.
+ # Represents the schema of an SQL table in an abstract way. This class
+ # provides methods for manipulating the schema representation.
+ #
+ # Inside migration files, the +t+ object in +create_table+ and
+ # +change_table+ is actually of this type:
+ #
+ # class SomeMigration < ActiveRecord::Migration
+ # def self.up
+ # create_table :foo do |t|
+ # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
+ # end
+ # end
+ #
+ # def self.down
+ # ...
+ # end
+ # end
+ #
+ # The table definitions
+ # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
class TableDefinition
+ # An array of ColumnDefinition objects, representing the column changes
+ # that have been defined.
attr_accessor :columns
def initialize(base)
@@ -321,6 +349,12 @@ module ActiveRecord
# * <tt>:scale</tt> -
# Specifies the scale for a <tt>:decimal</tt> column.
#
+ # For clarity's sake: the precision is the number of significant digits,
+ # while the scale is the number of digits that can be stored following
+ # the decimal point. For example, the number 123.45 has a precision of 5
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
+ # range from -999.99 to 999.99.
+ #
# Please be aware of different RDBMS implementations behavior with
# <tt>:decimal</tt> columns:
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
@@ -374,6 +408,10 @@ module ActiveRecord
# td.column(:huge_integer, :decimal, :precision => 30)
# # => huge_integer DECIMAL(30)
#
+ # # Defines a column with a database-specific type.
+ # td.column(:foo, 'polygon')
+ # # => foo polygon
+ #
# == Short-hand examples
#
# Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 6924bb7e6f..7c37916367 100644..100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -7,6 +7,7 @@ require 'active_record/connection_adapters/abstract/schema_definitions'
require 'active_record/connection_adapters/abstract/schema_statements'
require 'active_record/connection_adapters/abstract/database_statements'
require 'active_record/connection_adapters/abstract/quoting'
+require 'active_record/connection_adapters/abstract/connection_pool'
require 'active_record/connection_adapters/abstract/connection_specification'
require 'active_record/connection_adapters/abstract/query_cache'
@@ -24,6 +25,9 @@ module ActiveRecord
class AbstractAdapter
include Quoting, DatabaseStatements, SchemaStatements
include QueryCache
+ include ActiveSupport::Callbacks
+ define_callbacks :checkout, :checkin
+ checkout :reset!
@@row_even = true
def initialize(connection, logger = nil) #:nodoc:
@@ -102,20 +106,25 @@ module ActiveRecord
@active = false
end
+ # Reset the state of this connection, directing the DBMS to clear
+ # transactions and other connection-related server-side state. Usually a
+ # database-dependent operation; the default method simply executes a
+ # ROLLBACK and swallows any exceptions which is probably not enough to
+ # ensure the connection is clean.
+ def reset!
+ # this should be overridden by concrete adapters
+ end
+
# Returns true if its safe to reload the connection between requests for development mode.
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
false
end
- # Lazily verify this connection, calling <tt>active?</tt> only if it hasn't
- # been called for +timeout+ seconds.
- def verify!(timeout)
- now = Time.now.to_i
- if (now - @last_verification) > timeout
- reconnect! unless active?
- @last_verification = now
- end
+ # Verify this connection by calling <tt>active?</tt> and reconnecting if
+ # the connection is no longer active.
+ def verify!(*ignored)
+ reconnect! unless active?
end
# Provides access to the underlying database connection. Useful for
@@ -138,10 +147,10 @@ module ActiveRecord
@open_transactions -= 1
end
- def log_info(sql, name, runtime)
+ def log_info(sql, name, seconds)
if @logger && @logger.debug?
- name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
- @logger.debug format_log_entry(name, sql.squeeze(' '))
+ name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
+ @logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 204ebaa2e2..a26fd02b90 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -80,7 +80,7 @@ module ActiveRecord
def extract_default(default)
if type == :binary || type == :text
if default.blank?
- nil
+ return null ? nil : ''
else
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
end
@@ -91,6 +91,11 @@ module ActiveRecord
end
end
+ def has_default?
+ return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
private
def simplified_type(field_type)
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
@@ -280,6 +285,14 @@ module ActiveRecord
@connection.close rescue nil
end
+ def reset!
+ if @connection.respond_to?(:change_user)
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
+ # reset the connection is to change the user to the same user.
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
+ configure_connection
+ end
+ end
# DATABASE STATEMENTS ======================================
@@ -515,6 +528,10 @@ module ActiveRecord
"= BINARY"
end
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
private
def connect
@connection.reconnect = true if @connection.respond_to?(:reconnect=)
@@ -529,7 +546,11 @@ module ActiveRecord
end
@connection.real_connect(*@connection_options)
+ configure_connection
+ end
+ def configure_connection
+ encoding = @config[:encoding]
execute("SET NAMES '#{encoding}'") if encoding
# By default, MySQL 'where id is null' selects the last inserted id.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 55c7da5b4f..bebab5d05d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -380,7 +380,7 @@ module ActiveRecord
# There are some incorrectly compiled postgres drivers out there
# that don't define PGconn.escape.
self.class.instance_eval do
- undef_method(:quote_string)
+ remove_method(:quote_string)
end
end
quote_string(s)
@@ -518,6 +518,45 @@ module ActiveRecord
execute "ROLLBACK"
end
+ # ruby-pg defines Ruby constants for transaction status,
+ # ruby-postgres does not.
+ PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
+
+ # Check whether a transaction is active.
+ def transaction_active?
+ @connection.transaction_status != PQTRANS_IDLE
+ end
+
+ # Wrap a block in a transaction. Returns result of block.
+ def transaction(start_db_transaction = true)
+ transaction_open = false
+ begin
+ if block_given?
+ if start_db_transaction
+ begin_db_transaction
+ transaction_open = true
+ end
+ yield
+ end
+ rescue Exception => database_transaction_rollback
+ if transaction_open && transaction_active?
+ transaction_open = false
+ rollback_db_transaction
+ end
+ raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
+ end
+ ensure
+ if transaction_open && transaction_active?
+ begin
+ commit_db_transaction
+ rescue Exception => database_transaction_rollback
+ rollback_db_transaction
+ raise
+ end
+ end
+ end
+
+
# SCHEMA STATEMENTS ========================================
def recreate_database(name) #:nodoc:
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index 63bf8c8f5b..ae573799ae 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -34,8 +34,10 @@ module ActiveRecord
# person.name << 'by'
# person.name_change # => ['uncle bob', 'uncle bobby']
module Dirty
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
+
def self.included(base)
- base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ base.attribute_method_suffix *DIRTY_SUFFIXES
base.alias_method_chain :write_attribute, :dirty
base.alias_method_chain :save, :dirty
base.alias_method_chain :save!, :dirty
@@ -44,6 +46,8 @@ module ActiveRecord
base.superclass_delegating_accessor :partial_updates
base.partial_updates = true
+
+ base.send(:extend, ClassMethods)
end
# Do any attributes have unsaved changes?
@@ -123,7 +127,10 @@ module ActiveRecord
attr = attr.to_s
# The attribute already has an unsaved change.
- unless changed_attributes.include?(attr)
+ if changed_attributes.include?(attr)
+ old = changed_attributes[attr]
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ else
old = clone_attribute_value(:read_attribute, attr)
changed_attributes[attr] = old if field_changed?(attr, old, value)
end
@@ -158,5 +165,19 @@ module ActiveRecord
old != value
end
+ module ClassMethods
+ def self.extended(base)
+ base.metaclass.alias_method_chain(:alias_attribute, :dirty)
+ end
+
+ def alias_attribute_with_dirty(new_name, old_name)
+ alias_attribute_without_dirty(new_name, old_name)
+ DIRTY_SUFFIXES.each do |suffix|
+ module_eval <<-STR, __FILE__, __LINE__+1
+ def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end
+ STR
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index b105b919f5..f4a5712981 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -8,7 +8,8 @@ module ActiveRecord
def initialize(method)
@finder = :find_initial
case method.to_s
- when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
+ when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
+ @finder = :find_last if $1 == 'last_by'
@finder = :find_every if $1 == 'all_by'
names = $2
when /^find_by_([_a-zA-Z]\w*)\!$/
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 622cfc3c3f..114141a646 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -955,7 +955,7 @@ module Test #:nodoc:
ActiveRecord::Base.connection.rollback_db_transaction
ActiveRecord::Base.connection.decrement_open_transactions
end
- ActiveRecord::Base.verify_active_connections!
+ ActiveRecord::Base.clear_active_connections!
end
private
diff --git a/activerecord/lib/active_record/i18n_interpolation_deprecation.rb b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb
new file mode 100644
index 0000000000..cd634e1b8d
--- /dev/null
+++ b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb
@@ -0,0 +1,26 @@
+# Deprecates the use of the former message interpolation syntax in activerecord
+# as in "must have %d characters". The new syntax uses explicit variable names
+# as in "{{value}} must have {{count}} characters".
+
+require 'i18n/backend/simple'
+module I18n
+ module Backend
+ class Simple
+ DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' }
+
+ protected
+ def interpolate_with_deprecated_syntax(locale, string, values = {})
+ return string unless string.is_a?(String)
+
+ string = string.gsub(/%d|%s/) do |s|
+ instead = DEPRECATED_INTERPOLATORS[s]
+ ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead."
+ instead
+ end
+
+ interpolate_without_deprecated_syntax(locale, string, values)
+ end
+ alias_method_chain :interpolate, :deprecated_syntax
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/locale/en-US.yml b/activerecord/lib/active_record/locale/en-US.yml
index 8148f31a81..421f0ebd60 100644
--- a/activerecord/lib/active_record/locale/en-US.yml
+++ b/activerecord/lib/active_record/locale/en-US.yml
@@ -25,9 +25,30 @@ en-US:
even: "must be even"
# Append your own errors here or at the model/attributes scope.
+ # You can define own errors for models or model attributes.
+ # The values :model, :attribute and :value are always available for interpolation.
+ #
+ # For example,
+ # models:
+ # user:
+ # blank: "This is a custom blank message for {{model}}: {{attribute}}"
+ # attributes:
+ # login:
+ # blank: "This is a custom blank message for User login"
+ # Will define custom blank validation message for User model and
+ # custom blank validation message for login attribute of User model.
models:
- # Overrides default messages
-
- attributes:
- # Overrides model and default messages.
+
+ # Translate model names. Used in Model.human_name().
+ #models:
+ # For example,
+ # user: "Dude"
+ # will translate User model name to "Dude"
+
+ # Translate model attribute names. Used in Model.human_attribute_name(attribute).
+ #attributes:
+ # For example,
+ # user:
+ # login: "Handle"
+ # will translate User attribute "login" as "Handle"
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index c99c4beca9..83043c2c22 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -1,10 +1,10 @@
module ActiveRecord
module NamedScope
- # All subclasses of ActiveRecord::Base have two named_scopes:
- # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
- # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
+ # All subclasses of ActiveRecord::Base have two named \scopes:
+ # * <tt>all</tt> - which is similar to a <tt>find(:all)</tt> query, and
+ # * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
#
- # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
+ # These anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
# intermediate values (scopes) around as first-class objects is convenient.
def self.included(base)
base.class_eval do
@@ -26,20 +26,20 @@ module ActiveRecord
# named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
# end
#
- # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
+ # The above calls to <tt>named_scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
#
- # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
- # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
- # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
+ # as with the association objects, named \scopes act like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really was an Array.
#
- # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
+ # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
- # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
+ # All \scopes are available as class methods on the ActiveRecord::Base descendent upon which the \scopes were defined. But they are also available to
# <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
@@ -49,7 +49,7 @@ module ActiveRecord
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
# only shirts.
#
- # Named scopes can also be procedural.
+ # Named \scopes can also be procedural:
#
# class Shirt < ActiveRecord::Base
# named_scope :colored, lambda { |color|
@@ -59,7 +59,7 @@ module ActiveRecord
#
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
- # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
+ # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
# named_scope :red, :conditions => {:color => 'red'} do
@@ -70,7 +70,7 @@ module ActiveRecord
# end
#
#
- # For testing complex named scopes, you can examine the scoping options using the
+ # For testing complex named \scopes, you can examine the scoping options using the
# <tt>proxy_options</tt> method on the proxy itself.
#
# class Shirt < ActiveRecord::Base
@@ -101,9 +101,9 @@ module ActiveRecord
class Scope
attr_reader :proxy_scope, :proxy_options
-
+ NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set
[].methods.each do |m|
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/
+ unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
delegate m, :to => :proxy_found
end
end
@@ -136,6 +136,10 @@ module ActiveRecord
end
end
+ def size
+ @found ? @found.length : count
+ end
+
def empty?
@found ? @found.empty? : count.zero?
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 935b1939d8..a1b498eceb 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -129,10 +129,45 @@ module ActiveRecord
# Holds all the meta-data about an association as it was specified in the Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
+ # Returns the target association's class:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :books
+ # end
+ #
+ # Author.reflect_on_association(:books).klass
+ # # => Book
+ #
+ # <b>Note:</b> do not call +klass.new+ or +klass.create+ to instantiate
+ # a new association object. Use +build_association+ or +create_association+
+ # instead. This allows plugins to hook into association object creation.
def klass
@klass ||= active_record.send(:compute_type, class_name)
end
+ # Returns a new, unsaved instance of the associated class. +options+ will
+ # be passed to the class's constructor.
+ def build_association(*options)
+ klass.new(*options)
+ end
+
+ # Creates a new instance of the associated class, and immediates saves it
+ # with ActiveRecord::Base#save. +options+ will be passed to the class's
+ # creation method. Returns the newly created object.
+ def create_association(*options)
+ klass.create(*options)
+ end
+
+ # Creates a new instance of the associated class, and immediates saves it
+ # with ActiveRecord::Base#save!. +options+ will be passed to the class's
+ # creation method. If the created record doesn't pass validations, then an
+ # exception will be raised.
+ #
+ # Returns the newly created object.
+ def create_association!(*options)
+ klass.create!(*options)
+ end
+
def table_name
@table_name ||= klass.table_name
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index b90ed88c6b..4f96e225c1 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -102,7 +102,7 @@ HEADER
spec[:precision] = column.precision.inspect if !column.precision.nil?
spec[:scale] = column.scale.inspect if !column.scale.nil?
spec[:null] = 'false' if !column.null
- spec[:default] = default_string(column.default) if !column.default.nil?
+ spec[:default] = default_string(column.default) if column.has_default?
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
spec
end.compact
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index ffaa41282f..eabf06fc3b 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -43,5 +43,20 @@ module ActiveRecord
def assert_no_queries(&block)
assert_queries(0, &block)
end
+
+ def self.use_concurrent_connections
+ setup :connection_allow_concurrency_setup
+ teardown :connection_allow_concurrency_teardown
+ end
+
+ def connection_allow_concurrency_setup
+ @connection = ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(@connection.merge({:allow_concurrency => true}))
+ end
+
+ def connection_allow_concurrency_teardown
+ ActiveRecord::Base.clear_all_connections!
+ ActiveRecord::Base.establish_connection(@connection)
+ end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 81462a2917..970da701c7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -64,7 +64,7 @@ module ActiveRecord
#
# Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
# will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
- # depends on or you can raise exceptions in the callbacks to rollback.
+ # depends on or you can raise exceptions in the callbacks to rollback, including <tt>after_*</tt> callbacks.
#
# == Exception handling and rolling back
#
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 8fe4336bbc..518b59e433 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -54,7 +54,7 @@ module ActiveRecord
def add_on_empty(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- is_empty = value.respond_to?("empty?") ? value.empty? : false
+ is_empty = value.respond_to?(:empty?) ? value.empty? : false
add(attr, :empty, :default => custom_message) unless !value.nil? && !is_empty
end
end
@@ -87,6 +87,8 @@ module ActiveRecord
# </ol>
def generate_message(attribute, message = :invalid, options = {})
+ message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
+
defaults = @base.class.self_and_descendents_from_active_record.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
@@ -95,7 +97,6 @@ module ActiveRecord
defaults << options.delete(:default)
defaults = defaults.compact.flatten << :"messages.#{message}"
- model_name = @base.class.name
key = defaults.shift
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
@@ -258,6 +259,8 @@ module ActiveRecord
end
+ # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
+ #
# Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
# +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
# that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
@@ -296,8 +299,6 @@ module ActiveRecord
# person.save # => true (and person is now saved in the database)
#
# An Errors object is automatically created for every Active Record.
- #
- # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
module Validations
VALIDATIONS = %w( validate validate_on_create validate_on_update )
@@ -312,9 +313,50 @@ module ActiveRecord
base.define_callbacks *VALIDATIONS
end
- # All of the following validations are defined in the class scope of the model that you're interested in validating.
- # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
- # these over the low-level calls to +validate+ and +validate_on_create+ when possible.
+ # Active Record classes can implement validations in several ways. The highest level, easiest to read,
+ # and recommended approach is to use the declarative <tt>validates_..._of</tt> class methods (and
+ # +validates_associated+) documented below. These are sufficient for most model validations.
+ #
+ # Slightly lower level is +validates_each+. It provides some of the same options as the purely declarative
+ # validation methods, but like all the lower-level approaches it requires manually adding to the errors collection
+ # when the record is invalid.
+ #
+ # At a yet lower level, a model can use the class methods +validate+, +validate_on_create+ and +validate_on_update+
+ # to add validation methods or blocks. These are ActiveSupport::Callbacks and follow the same rules of inheritance
+ # and chaining.
+ #
+ # The lowest level style is to define the instance methods +validate+, +validate_on_create+ and +validate_on_update+
+ # as documented in ActiveRecord::Validations.
+ #
+ # == +validate+, +validate_on_create+ and +validate_on_update+ Class Methods
+ #
+ # Calls to these methods add a validation method or block to the class. Again, this approach is recommended
+ # only when the higher-level methods documented below (<tt>validates_..._of</tt> and +validates_associated+) are
+ # insufficient to handle the required validation.
+ #
+ # This can be done with a symbol pointing to a method:
+ #
+ # class Comment < ActiveRecord::Base
+ # validate :must_be_friends
+ #
+ # def must_be_friends
+ # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # end
+ # end
+ #
+ # Or with a block which is passed the current record to be validated:
+ #
+ # class Comment < ActiveRecord::Base
+ # validate do |comment|
+ # comment.must_be_friends
+ # end
+ #
+ # def must_be_friends
+ # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # end
+ # end
+ #
+ # This usage applies to +validate_on_create+ and +validate_on_update+ as well.
module ClassMethods
DEFAULT_VALIDATION_OPTIONS = {
:on => :save,
@@ -328,34 +370,6 @@ module ActiveRecord
:equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
:odd => 'odd?', :even => 'even?' }.freeze
- # Adds a validation method or block to the class. This is useful when
- # overriding the +validate+ instance method becomes too unwieldy and
- # you're looking for more descriptive declaration of your validations.
- #
- # This can be done with a symbol pointing to a method:
- #
- # class Comment < ActiveRecord::Base
- # validate :must_be_friends
- #
- # def must_be_friends
- # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
- # end
- # end
- #
- # Or with a block which is passed the current record to be validated:
- #
- # class Comment < ActiveRecord::Base
- # validate do |comment|
- # comment.must_be_friends
- # end
- #
- # def must_be_friends
- # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
- # end
- # end
- #
- # This usage applies to +validate_on_create+ and +validate_on_update+ as well.
-
# Validates each attribute against a block.
#
# class Person < ActiveRecord::Base
@@ -508,13 +522,13 @@ module ActiveRecord
#
# class Person < ActiveRecord::Base
# validates_length_of :first_name, :maximum=>30
- # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
+ # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
+ # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
@@ -525,9 +539,9 @@ module ActiveRecord
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
+ # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
+ # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -618,7 +632,7 @@ module ActiveRecord
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+false+ by default).
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -664,7 +678,7 @@ module ActiveRecord
condition_params = [value]
else
condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
- condition_params = [value.downcase]
+ condition_params = [value.chars.downcase]
end
if scope = configuration[:scope]
@@ -730,7 +744,7 @@ module ActiveRecord
# class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!"
# validates_inclusion_of :age, :in => 0..99
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list"
+ # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
# end
#
# Configuration options:
@@ -750,7 +764,7 @@ module ActiveRecord
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
unless enum.include?(value)
@@ -764,7 +778,7 @@ module ActiveRecord
# class Person < ActiveRecord::Base
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed"
+ # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
# end
#
# Configuration options:
@@ -784,7 +798,7 @@ module ActiveRecord
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
if enum.include?(value)
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 75d1f27e07..4e0e1c7f15 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -107,6 +107,45 @@ class AggregationsTest < ActiveRecord::TestCase
customers(:david).gps_location = nil
assert_equal nil, customers(:david).gps_location
end
+
+ def test_custom_constructor
+ assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s
+ assert_kind_of Fullname, customers(:barney).fullname
+ end
+
+ def test_custom_converter
+ customers(:barney).fullname = 'Barnoit Gumbleau'
+ assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
+ assert_kind_of Fullname, customers(:barney).fullname
+ end
+end
+
+class DeprecatedAggregationsTest < ActiveRecord::TestCase
+ class Person < ActiveRecord::Base; end
+
+ def test_conversion_block_is_deprecated
+ assert_deprecated 'conversion block has been deprecated' do
+ Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money }
+ end
+ end
+
+ def test_conversion_block_used_when_converter_option_is_nil
+ assert_deprecated 'conversion block has been deprecated' do
+ Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money }
+ end
+ assert_raise(NoMethodError) { Person.new.balance = 5 }
+ end
+
+ def test_converter_option_overrides_conversion_block
+ assert_deprecated 'conversion block has been deprecated' do
+ Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| Money.new(balance) }) { |balance| balance.to_money }
+ end
+
+ person = Person.new
+ assert_nothing_raised { person.balance = 5 }
+ assert_equal 5, person.balance.amount
+ assert_kind_of Money, person.balance
+ end
end
class OverridingAggregationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 9c718c4fef..37b6836a89 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -47,6 +47,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_foreign_key_assignment
+ # Test using an existing record
+ signals37 = accounts(:signals37)
+ assert_equal companies(:first_firm), signals37.firm
+ signals37.firm_id = companies(:another_firm).id
+ assert_equal companies(:another_firm), signals37.firm
+
+ # Test using a new record
+ account = Account.new
+ account.firm_id = companies(:another_firm).id
+ assert_equal companies(:another_firm), account.firm
+ end
+
def test_no_unexpected_aliasing
first_firm = companies(:first_firm)
another_firm = companies(:another_firm)
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 0572418e3b..9981f4c5d5 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -223,10 +223,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
devel = Developer.find(1)
proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
assert !devel.projects.loaded?
-
+
assert_equal devel.projects.last, proj
assert devel.projects.loaded?
-
+
assert proj.new_record?
devel.save
assert !proj.new_record?
@@ -251,10 +251,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
devel = Developer.find(1)
proj = devel.projects.create("name" => "Projekt")
assert !devel.projects.loaded?
-
+
assert_equal devel.projects.last, proj
assert devel.projects.loaded?
-
+
assert !proj.new_record?
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -274,10 +274,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_creation_respects_hash_condition
post = categories(:general).post_with_conditions.build(:body => '')
-
+
assert post.save
assert_equal 'Yet Another Testing Title', post.title
-
+
another_post = categories(:general).post_with_conditions.create(:body => '')
assert !another_post.new_record?
@@ -288,7 +288,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
dev = developers(:jamis)
dev.projects << projects(:active_record)
dev.projects << projects(:active_record)
-
+
assert_equal 3, dev.projects.size
assert_equal 1, dev.projects.uniq.size
end
@@ -415,13 +415,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project.developers.class # force load target
developer = project.developers.first
-
+
assert_no_queries do
assert project.developers.loaded?
assert project.developers.include?(developer)
end
end
-
+
def test_include_checks_if_record_exists_if_target_not_loaded
project = projects(:active_record)
developer = project.developers.first
@@ -636,11 +636,39 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size
end
+ def test_find_grouped
+ all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories)
+ grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories)
+ assert_equal 4, all_posts_from_category1.size
+ assert_equal 1, grouped_posts_of_category1.size
+ end
+
+ def test_find_scoped_grouped
+ assert_equal 4, categories(:general).posts_gruoped_by_title.size
+ assert_equal 1, categories(:technology).posts_gruoped_by_title.size
+ end
+
def test_get_ids
assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort
assert_equal [projects(:active_record).id], developers(:jamis).project_ids
end
+ def test_get_ids_for_loaded_associations
+ developer = developers(:david)
+ developer.projects(true)
+ assert_queries(0) do
+ developer.project_ids
+ developer.project_ids
+ end
+ end
+
+ def test_get_ids_for_unloaded_associations_does_not_load_them
+ developer = developers(:david)
+ assert !developer.projects.loaded?
+ assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort
+ assert !developer.projects.loaded?
+ end
+
def test_assign_ids
developer = Developer.new("name" => "Joe")
developer.project_ids = projects(:active_record, :action_controller).map(&:id)
@@ -703,4 +731,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# due to Unknown column 'authors.id'
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
end
+
+ def test_counting_on_habtm_association_and_not_array
+ david = Developer.find(1)
+ # Extra parameter just to make sure we aren't falling back to
+ # Array#count in Ruby >=1.8.7, which would raise an ArgumentError
+ assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') }
+ 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 da3c8fb28e..ba750b266c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -48,6 +48,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, Firm.find(:first).plain_clients.count(:name)
end
+ def test_counting_with_association_limit
+ firm = companies(:first_firm)
+ assert_equal firm.limited_clients.length, firm.limited_clients.size
+ assert_equal firm.limited_clients.length, firm.limited_clients.count
+ end
+
def test_finding
assert_equal 2, Firm.find(:first).clients.length
end
@@ -242,6 +248,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, grouped_clients_of_firm1.size
end
+ def test_find_scoped_grouped
+ assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size
+ assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length
+ assert_equal 2, companies(:first_firm).clients_grouped_by_name.size
+ assert_equal 2, companies(:first_firm).clients_grouped_by_name.length
+ end
+
def test_adding
force_signal37_to_load_all_clients_of_firm
natural = Client.new("name" => "Natural Company")
@@ -378,7 +391,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert !company.clients_of_firm.loaded?
-
+
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
@@ -410,7 +423,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
-
+
assert_equal 2, new_clients.size
company.name += '-changed'
assert_queries(3) { assert company.save }
@@ -649,10 +662,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_creation_respects_hash_condition
ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
-
+
assert ms_client.save
assert_equal 'Microsoft', ms_client.name
-
+
another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create
assert !another_ms_client.new_record?
@@ -824,6 +837,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
end
+ def test_get_ids_for_loaded_associations
+ company = companies(:first_firm)
+ company.clients(true)
+ assert_queries(0) do
+ company.client_ids
+ company.client_ids
+ end
+ end
+
+ def test_get_ids_for_unloaded_associations_does_not_load_them
+ company = companies(:first_firm)
+ assert !company.clients.loaded?
+ assert_equal [companies(:first_client).id, companies(:second_client).id], company.client_ids
+ assert !company.clients.loaded?
+ end
+
def test_assign_ids
firm = Firm.new("name" => "Apple")
firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
@@ -894,7 +923,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length
assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
end
-
+
def test_find_all_include_over_the_same_table_for_through
assert_equal 2, people(:michael).posts.find(:all, :include => :people).length
end
@@ -931,13 +960,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_include_loads_collection_if_target_uses_finder_sql
firm = companies(:first_firm)
client = firm.clients_using_sql.first
-
+
firm.reload
assert ! firm.clients_using_sql.loaded?
assert firm.clients_using_sql.include?(client)
assert firm.clients_using_sql.loaded?
end
-
+
def test_include_returns_false_for_non_matching_record_to_verify_scoping
firm = companies(:first_firm)
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index d51a3c7e1c..0be050ec81 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -196,4 +196,28 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
# due to Unknown column 'comments.id'
assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog')
end
+
+ def test_count_with_include_should_alias_join_table
+ assert_equal 2, people(:michael).posts.count(:include => :readers)
+ end
+
+ def test_get_ids
+ assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort
+ end
+
+ def test_get_ids_for_loaded_associations
+ person = people(:michael)
+ person.posts(true)
+ assert_queries(0) do
+ person.post_ids
+ person.post_ids
+ end
+ end
+
+ def test_get_ids_for_unloaded_associations_does_not_load_them
+ person = people(:michael)
+ assert !person.posts.loaded?
+ assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort
+ assert !person.posts.loaded?
+ 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 b61a3711e3..77e3cb1776 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -101,4 +101,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:crazy_club), members[0].sponsor_club
end
+ def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record
+ assert_nil Member.new.club
+ end
+
+ def test_assigning_association_correctly_assigns_target
+ new_member = Member.create(:name => "Chris")
+ new_member.club = new_club = Club.create(:name => "LRUG")
+ assert_equal new_club, new_member.club.target
+ end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 0b2731ecd7..cde451de0e 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -182,119 +182,31 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_nil p.author.reset
end
- def test_reload_returns_assocition
- david = developers(:david)
- assert_nothing_raised do
- assert_equal david.projects, david.projects.reload.reload
- end
- end
-
- def test_belongs_to_mass_assignment
- post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
- author_attributes = { :name => 'David Dollar' }
-
- assert_no_difference 'Author.count' do
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- Post.create(post_attributes.merge({:author => author_attributes}))
- end
- end
-
- assert_difference 'Author.count' do
- post = Post.create(post_attributes.merge({:creatable_author => author_attributes}))
- assert_equal post.creatable_author.name, author_attributes[:name]
- end
- end
-
- def test_has_one_mass_assignment
- post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
- comment_attributes = { :body => 'Setter Takes Hash' }
-
- assert_no_difference 'Comment.count' do
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- Post.create(post_attributes.merge({:uncreatable_comment => comment_attributes}))
- end
- end
+ def test_reset_loads_association_next_time
+ welcome = posts(:welcome)
+ david = authors(:david)
+ author_assoc = welcome.author
- assert_difference 'Comment.count' do
- post = Post.create(post_attributes.merge({:creatable_comment => comment_attributes}))
- assert_equal post.creatable_comment.body, comment_attributes[:body]
- end
+ assert_equal david, welcome.author # So we can be sure the test works correctly
+ author_assoc.reset
+ assert !author_assoc.loaded?
+ assert_nil author_assoc.target
+ assert_equal david, welcome.author
end
- def test_has_many_mass_assignment
- post = posts(:welcome)
- post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
- comment_attributes = { :body => 'Setter Takes Hash' }
-
- assert_no_difference 'Comment.count' do
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- Post.create(post_attributes.merge({:comments => [comment_attributes]}))
- end
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- post.comments << comment_attributes
- end
- end
-
- assert_difference 'Comment.count' do
- post = Post.create(post_attributes.merge({:creatable_comments => [comment_attributes]}))
- assert_equal post.creatable_comments.last.body, comment_attributes[:body]
- end
-
- assert_difference 'Comment.count' do
- post.creatable_comments << comment_attributes
- assert_equal post.comments.last.body, comment_attributes[:body]
+ def test_assigning_association_id_after_reload
+ welcome = posts(:welcome)
+ welcome.reload
+ assert_nothing_raised do
+ welcome.author_id = authors(:david).id
end
-
- post.creatable_comments = [comment_attributes, comment_attributes]
- assert_equal post.creatable_comments.count, 2
end
- def test_has_and_belongs_to_many_mass_assignment
- post = posts(:welcome)
- post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
- category_attributes = { :name => 'Accessible Association', :type => 'Category' }
-
- assert_no_difference 'Category.count' do
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- Post.create(post_attributes.merge({:categories => [category_attributes]}))
- end
- assert_raise(ActiveRecord::AssociationTypeMismatch) do
- post.categories << category_attributes
- end
- end
-
- assert_difference 'Category.count' do
- post = Post.create(post_attributes.merge({:creatable_categories => [category_attributes]}))
- assert_equal post.creatable_categories.last.name, category_attributes[:name]
- end
-
- assert_difference 'Category.count' do
- post.creatable_categories << category_attributes
- assert_equal post.creatable_categories.last.name, category_attributes[:name]
+ def test_reload_returns_assocition
+ david = developers(:david)
+ assert_nothing_raised do
+ assert_equal david.projects, david.projects.reload.reload
end
-
- post.creatable_categories = [category_attributes, category_attributes]
- assert_equal post.creatable_categories.count, 2
- end
-
- def test_association_proxy_setter_can_take_hash
- special_comment_attributes = { :body => 'Setter Takes Hash' }
-
- post = posts(:welcome)
- post.creatable_comment = { :body => 'Setter Takes Hash' }
-
- assert_equal post.creatable_comment.body, special_comment_attributes[:body]
- end
-
- def test_association_collection_can_take_hash
- post_attributes = { :title => 'Setter Takes', :body => 'Hash' }
- david = authors(:david)
-
- post = (david.posts << post_attributes).last
- assert_equal post.title, post_attributes[:title]
-
- david.posts = [post_attributes, post_attributes]
- assert_equal david.posts.count, 2
end
def setup_dangling_association
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 7999e29264..ce293a469e 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/topic'
+require 'models/minimalistic'
class AttributeMethodsTest < ActiveRecord::TestCase
fixtures :topics
@@ -219,6 +220,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
+ Topic.skip_time_zone_conversion_for_attributes = [:field_a]
+ Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
+
+ assert_equal [:field_a], Topic.skip_time_zone_conversion_for_attributes
+ assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes
+ end
+
private
def time_related_columns_on_topic
Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index c8111358e3..aebcca634c 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -76,7 +76,7 @@ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
end
class BasicsTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
def test_table_exists
assert !NonExistentTable.table_exists?
@@ -664,10 +664,21 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_update_all_ignores_order_limit_from_association
- author = Author.find(1)
+ def test_update_all_ignores_order_without_limit_from_association
+ author = authors(:david)
assert_nothing_raised do
- assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all("body = 'bulk update!'")
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
+ end
+ end
+
+ def test_update_all_with_order_and_limit_updates_subset_only
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal 1, author.posts_sorted_by_id_limited.size
+ assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
+ assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
end
end
@@ -880,7 +891,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_mass_assignment_protection_against_class_attribute_writers
[:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging,
- :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method|
+ :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
assert Task.respond_to?(method)
assert Task.respond_to?("#{method}=")
assert Task.new.respond_to?(method)
@@ -1073,6 +1084,24 @@ class BasicsTest < ActiveRecord::TestCase
Time.zone = nil
Topic.skip_time_zone_conversion_for_attributes = []
end
+
+ def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = ActiveSupport::TimeZone[-28800]
+ attributes = {
+ "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
+ "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
+ assert topic.bonus_time.utc?
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
+ end
def test_multiparameter_attributes_on_time_with_empty_seconds
attributes = {
diff --git a/activerecord/test/cases/connection_test_mysql.rb b/activerecord/test/cases/connection_test_mysql.rb
index 1adbf18e73..40ddcf5420 100644
--- a/activerecord/test/cases/connection_test_mysql.rb
+++ b/activerecord/test/cases/connection_test_mysql.rb
@@ -24,7 +24,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert @connection.active?
@connection.update('set @@wait_timeout=1')
sleep 2
- @connection.verify!(0)
+ @connection.verify!
assert @connection.active?
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 3473b846a0..ee84cb8af8 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -19,6 +19,37 @@ class DefaultTest < ActiveRecord::TestCase
end
if current_adapter?(:MysqlAdapter)
+
+ #MySQL 5 and higher is quirky with not null text/blob columns.
+ #With MySQL Text/blob columns cannot have defaults. If the column is not null MySQL will report that the column has a null default
+ #but it behaves as though the column had a default of ''
+ def test_mysql_text_not_null_defaults
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = 'test_mysql_text_not_null_defaults'
+ klass.connection.create_table klass.table_name do |t|
+ t.column :non_null_text, :text, :null => false
+ t.column :non_null_blob, :blob, :null => false
+ t.column :null_text, :text, :null => true
+ t.column :null_blob, :blob, :null => true
+ end
+ assert_equal '', klass.columns_hash['non_null_blob'].default
+ assert_equal '', klass.columns_hash['non_null_text'].default
+
+ assert_equal nil, klass.columns_hash['null_blob'].default
+ assert_equal nil, klass.columns_hash['null_text'].default
+
+ assert_nothing_raised do
+ instance = klass.create!
+ assert_equal '', instance.non_null_text
+ assert_equal '', instance.non_null_blob
+ assert_nil instance.null_text
+ assert_nil instance.null_blob
+ end
+ ensure
+ klass.connection.drop_table(klass.table_name) rescue nil
+ end
+
+
# MySQL uses an implicit default 0 rather than NULL unless in strict mode.
# We use an implicit NULL so schema.rb is compatible with other databases.
def test_mysql_integer_not_null_defaults
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index feb47a15a8..39d38c4e1e 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -45,6 +45,19 @@ class DirtyTest < ActiveRecord::TestCase
assert_nil pirate.catchphrase_change
end
+ def test_aliased_attribute_changes
+ # the actual attribute here is name, title is an
+ # alias setup via alias_attribute
+ parrot = Parrot.new
+ assert !parrot.title_changed?
+ assert_nil parrot.title_change
+
+ parrot.name = 'Sam'
+ assert parrot.title_changed?
+ assert_nil parrot.title_was
+ assert_equal parrot.name_change, parrot.title_change
+ end
+
def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
pirate = Pirate.new
@@ -191,6 +204,42 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.changed?
end
+ def test_reverted_changes_are_not_dirty
+ phrase = "shiver me timbers"
+ pirate = Pirate.create!(:catchphrase => phrase)
+ pirate.catchphrase = "*hic*"
+ assert pirate.changed?
+ pirate.catchphrase = phrase
+ assert !pirate.changed?
+ end
+
+ def test_reverted_changes_are_not_dirty_after_multiple_changes
+ phrase = "shiver me timbers"
+ pirate = Pirate.create!(:catchphrase => phrase)
+ 10.times do |i|
+ pirate.catchphrase = "*hic*" * i
+ assert pirate.changed?
+ end
+ assert pirate.changed?
+ pirate.catchphrase = phrase
+ assert !pirate.changed?
+ end
+
+
+ def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
+ pirate = Pirate.create!(:catchphrase => "Yar!")
+
+ pirate.parrot_id = 1
+ assert pirate.changed?
+ assert pirate.parrot_id_changed?
+ assert !pirate.catchphrase_changed?
+
+ pirate.parrot_id = nil
+ assert !pirate.changed?
+ assert !pirate.parrot_id_changed?
+ assert !pirate.catchphrase_changed?
+ end
+
def test_save_should_store_serialized_attributes_even_with_partial_updates
with_partial_updates(Topic) do
topic = Topic.create!(:content => {:a => "a"})
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 2ce49ed76f..cbdff382fe 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -169,6 +169,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal("fixture_3", developers.first.name)
end
+ def test_find_with_group
+ developers = Developer.find(:all, :group => "salary", :select => "salary")
+ assert_equal 4, developers.size
+ assert_equal 4, developers.map(&:salary).uniq.size
+ end
+
def test_find_with_entire_select_statement
topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
@@ -197,11 +203,11 @@ class FinderTest < ActiveRecord::TestCase
first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
assert_nil(first)
end
-
+
def test_first
assert_equal topics(:second).title, Topic.first(:conditions => "title = 'The Second Topic of the day'").title
end
-
+
def test_first_failing
assert_nil Topic.first(:conditions => "title = 'The Second Topic of the day!'")
end
@@ -297,7 +303,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
end
-
def test_condition_interpolation
assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
@@ -392,7 +397,7 @@ class FinderTest < ActiveRecord::TestCase
Company.find(:first, :conditions => ["id=? AND name = ?", 2])
}
assert_raises(ActiveRecord::PreparedStatementInvalid) {
- Company.find(:first, :conditions => ["id=?", 2, 3, 4])
+ Company.find(:first, :conditions => ["id=?", 2, 3, 4])
}
end
@@ -418,7 +423,7 @@ class FinderTest < ActiveRecord::TestCase
def test_named_bind_variables
assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
-
+
assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
@@ -455,6 +460,15 @@ class FinderTest < ActiveRecord::TestCase
assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
end
+ def test_bind_chars
+ quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
+ quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi")
+ assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".chars)
+ assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".chars)
+ end
+
def test_bind_record
o = Struct.new(:quoted_id).new(1)
assert_equal '1', bind('?', o)
@@ -589,6 +603,38 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
end
+ def test_find_last_by_one_attribute
+ assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
+ assert_nil Topic.find_last_by_title("A title with no matches")
+ end
+
+ def test_find_last_by_one_attribute_caches_dynamic_finder
+ # ensure this test can run independently of order
+ class << Topic; self; end.send(:remove_method, :find_last_by_title) if Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' }
+ assert !Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' }
+ t = Topic.find_last_by_title(Topic.last.title)
+ assert Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' }
+ end
+
+ def test_find_last_by_invalid_method_syntax
+ assert_raises(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
+ assert_raises(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
+ end
+
+ def test_find_last_by_one_attribute_with_several_options
+ assert_equal accounts(:signals37), Account.find_last_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
+ end
+
+ def test_find_last_by_one_missing_attribute
+ assert_raises(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
+ end
+
+ def test_find_last_by_two_attributes
+ topic = Topic.last
+ assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
+ assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
+ end
+
def test_find_all_by_one_attribute
topics = Topic.find_all_by_content("Have a nice day")
assert_equal 2, topics.size
@@ -782,7 +828,7 @@ class FinderTest < ActiveRecord::TestCase
assert c.valid?
assert !c.new_record?
end
-
+
def test_dynamic_find_or_initialize_from_one_attribute_caches_method
class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' }
assert !Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' }
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index f30d58546e..f7bdac8013 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -46,3 +46,17 @@ end
class << ActiveRecord::Base
public :with_scope, :with_exclusive_scope
end
+
+unless ENV['FIXTURE_DEBUG']
+ module Test #:nodoc:
+ module Unit #:nodoc:
+ class << TestCase #:nodoc:
+ def try_to_load_dependency_with_silence(*args)
+ ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)}
+ end
+
+ alias_method_chain :try_to_load_dependency, :silence
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index 06036733f5..ea06e377e3 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -5,42 +5,37 @@ require 'models/reply'
class ActiveRecordI18nTests < Test::Unit::TestCase
def setup
- reset_translations
+ I18n.backend = I18n::Backend::Simple.new
end
def test_translated_model_attributes
- I18n.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
+ I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Topic.human_attribute_name('title')
end
def test_translated_model_attributes_with_sti
- I18n.store_translations 'en-US', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
+ I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
assert_equal 'reply title attribute', Reply.human_attribute_name('title')
end
def test_translated_model_attributes_with_sti_fallback
- I18n.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
+ I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Reply.human_attribute_name('title')
end
def test_translated_model_names
- I18n.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
+ I18n.backend.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
assert_equal 'topic model', Topic.human_name
end
def test_translated_model_names_with_sti
- I18n.store_translations 'en-US', :activerecord => {:models => {:reply => 'reply model'} }
+ I18n.backend.store_translations 'en-US', :activerecord => {:models => {:reply => 'reply model'} }
assert_equal 'reply model', Reply.human_name
end
def test_translated_model_names_with_sti_fallback
- I18n.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
+ I18n.backend.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
assert_equal 'topic model', Reply.human_name
end
-
- private
- def reset_translations
- I18n.backend = I18n::Backend::Simple.new
- end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 4fd38bfbc9..a39415618d 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -193,7 +193,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
- assert_sql(/\(#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)\)/) do
+ assert_sql(/\(#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1\)/) do
Account.find(1, :include => :firm)
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 701187223f..0a14b1d906 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -210,13 +210,6 @@ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
def setup
# Avoid introspection queries during tests.
Person.columns; Reader.columns
-
- @allow_concurrency = ActiveRecord::Base.allow_concurrency
- ActiveRecord::Base.allow_concurrency = true
- end
-
- def teardown
- ActiveRecord::Base.allow_concurrency = @allow_concurrency
end
# Test typical find.
@@ -264,6 +257,8 @@ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
end
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
+ use_concurrent_connections
+
def test_no_locks_no_wait
first, second = duel { Person.find 1 }
assert first.end > second.end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index ee66ac948d..af6fcd32ad 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/author'
require 'models/developer'
require 'models/project'
require 'models/comment'
@@ -6,7 +7,7 @@ require 'models/post'
require 'models/category'
class MethodScopingTest < ActiveRecord::TestCase
- fixtures :developers, :projects, :comments, :posts, :developers_projects
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
def test_set_conditions
Developer.with_scope(:find => { :conditions => 'just a test...' }) do
@@ -97,6 +98,46 @@ class MethodScopingTest < ActiveRecord::TestCase
assert_equal developers(:david).attributes, scoped_developers.first.attributes
end
+ def test_scoped_find_using_new_style_joins
+ scoped_developers = Developer.with_scope(:find => { :joins => :projects }) do
+ Developer.find(:all, :conditions => 'projects.id = 2')
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
+ def test_scoped_find_merges_old_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
+ def test_scoped_find_merges_new_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1')
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
+ def test_scoped_find_merges_new_and_old_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
def test_scoped_count_include
# with the include, will retrieve only developers for the given project
Developer.with_scope(:find => { :include => :projects }) do
@@ -152,7 +193,7 @@ class MethodScopingTest < ActiveRecord::TestCase
end
class NestedScopingTest < ActiveRecord::TestCase
- fixtures :developers, :projects, :comments, :posts
+ fixtures :authors, :developers, :projects, :comments, :posts
def test_merge_options
Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
@@ -357,6 +398,42 @@ class NestedScopingTest < ActiveRecord::TestCase
assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
end
end
+
+ def test_nested_scoped_find_merges_old_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do
+ Author.with_scope(:find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
+ end
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
+ def test_nested_scoped_find_merges_new_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
+ Author.with_scope(:find => { :joins => :comments }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
+ end
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
+ def test_nested_scoped_find_merges_new_and_old_style_joins
+ scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
+ Author.with_scope(:find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1')
+ end
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
end
class HasManyScopingTest< ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index c1a8da2270..ac44dd7ffe 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1133,7 +1133,11 @@ if ActiveRecord::Base.connection.supports_migrations?
columns = Person.connection.columns(:binary_testings)
data_column = columns.detect { |c| c.name == "data" }
- assert_nil data_column.default
+ if current_adapter?(:MysqlAdapter)
+ assert_equal '', data_column.default
+ else
+ assert_nil data_column.default
+ end
Person.connection.drop_table :binary_testings rescue nil
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 6f6ea1cbe9..444debd255 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -249,7 +249,26 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved)
end
+ def test_rand_should_select_a_random_object_from_proxy
+ assert Topic.approved.rand.is_a?(Topic)
+ end
+
def test_should_use_where_in_query_for_named_scope
assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises)
end
+
+ def test_size_should_use_count_when_results_are_not_loaded
+ topics = Topic.base
+ assert_queries(1) do
+ assert_sql(/COUNT/i) { topics.size }
+ end
+ end
+
+ def test_size_should_use_length_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_no_queries do
+ topics.size # use loaded (no query)
+ end
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
new file mode 100644
index 0000000000..078ca1d679
--- /dev/null
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -0,0 +1,87 @@
+require "cases/helper"
+
+class PooledConnectionsTest < ActiveRecord::TestCase
+ def setup
+ super
+ @connection = ActiveRecord::Base.remove_connection
+ end
+
+ def teardown
+ ActiveRecord::Base.clear_all_connections!
+ ActiveRecord::Base.establish_connection(@connection)
+ super
+ end
+
+ def checkout_connections
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
+ @connections = []
+ @timed_out = 0
+
+ 4.times do
+ Thread.new do
+ begin
+ @connections << ActiveRecord::Base.connection_pool.checkout
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
+ end
+ end.join
+ end
+ end
+
+ def test_pooled_connection_checkout
+ checkout_connections
+ assert_equal @connections.length, 2
+ assert_equal @timed_out, 2
+ end
+
+ def checkout_checkin_connections(pool_size, threads)
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5}))
+ @connection_count = 0
+ @timed_out = 0
+ threads.times do
+ Thread.new do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ sleep 0.1
+ ActiveRecord::Base.connection_pool.checkin conn
+ @connection_count += 1
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
+ end
+ end.join
+ end
+ end
+
+ def test_pooled_connection_checkin_one
+ checkout_checkin_connections 1, 2
+ assert_equal 2, @connection_count
+ assert_equal 0, @timed_out
+ end
+
+ def test_pooled_connection_checkin_two
+ checkout_checkin_connections 2, 3
+ assert_equal 3, @connection_count
+ assert_equal 0, @timed_out
+ end
+
+ def test_pooled_connection_checkout_existing_first
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1}))
+ conn_pool = ActiveRecord::Base.connection_pool
+ conn = conn_pool.checkout
+ conn_pool.checkin(conn)
+ conn = conn_pool.checkout
+ assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn
+ conn_pool.checkin(conn)
+ end
+end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
+
+class AllowConcurrencyDeprecatedTest < ActiveRecord::TestCase
+ def test_allow_concurrency_is_deprecated
+ assert_deprecated('ActiveRecord::Base.allow_concurrency') do
+ ActiveRecord::Base.allow_concurrency
+ end
+ assert_deprecated('ActiveRecord::Base.allow_concurrency=') do
+ ActiveRecord::Base.allow_concurrency = true
+ end
+ end
+end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 4b86e32dbf..e339ef41ab 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -160,8 +160,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 24, Firm.reflect_on_all_associations.size
- assert_equal 18, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 26, Firm.reflect_on_all_associations.size
+ assert_equal 20, Firm.reflect_on_all_associations(:has_many).size
assert_equal 6, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
new file mode 100644
index 0000000000..0106572ced
--- /dev/null
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require 'models/binary'
+
+class SanitizeTest < ActiveRecord::TestCase
+ def setup
+ end
+
+ def test_sanitize_sql_array_handles_string_interpolation
+ quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
+ assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"])
+ assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".chars])
+ quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".chars])
+ end
+
+ def test_sanitize_sql_array_handles_bind_variables
+ quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
+ assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"])
+ assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".chars])
+ quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".chars])
+ end
+end
diff --git a/activerecord/test/cases/schema_authorization_test_postgresql.rb b/activerecord/test/cases/schema_authorization_test_postgresql.rb
index 7a0796ef53..ba7754513d 100644
--- a/activerecord/test/cases/schema_authorization_test_postgresql.rb
+++ b/activerecord/test/cases/schema_authorization_test_postgresql.rb
@@ -18,8 +18,8 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
@connection.execute "SET search_path TO '$user',public"
set_session_auth
USERS.each do |u|
- @connection.execute "CREATE USER #{u}"
- @connection.execute "CREATE SCHEMA AUTHORIZATION #{u}"
+ @connection.execute "CREATE USER #{u}" rescue nil
+ @connection.execute "CREATE SCHEMA AUTHORIZATION #{u}" rescue nil
set_session_auth u
@connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
@connection.execute "INSERT INTO #{TABLE_NAME} (name) VALUES ('#{u}')"
diff --git a/activerecord/test/cases/threaded_connections_test.rb b/activerecord/test/cases/threaded_connections_test.rb
deleted file mode 100644
index 28f8302367..0000000000
--- a/activerecord/test/cases/threaded_connections_test.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-require 'models/reply'
-
-unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
- class ThreadedConnectionsTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- fixtures :topics
-
- def setup
- @connection = ActiveRecord::Base.remove_connection
- @connections = []
- @allow_concurrency = ActiveRecord::Base.allow_concurrency
- end
-
- def teardown
- # clear the connection cache
- ActiveRecord::Base.send(:clear_all_cached_connections!)
- # set allow_concurrency to saved value
- ActiveRecord::Base.allow_concurrency = @allow_concurrency
- # reestablish old connection
- ActiveRecord::Base.establish_connection(@connection)
- end
-
- def gather_connections(use_threaded_connections)
- ActiveRecord::Base.allow_concurrency = use_threaded_connections
- ActiveRecord::Base.establish_connection(@connection)
-
- 5.times do
- Thread.new do
- Topic.find :first
- @connections << ActiveRecord::Base.active_connections.values.first
- end.join
- end
- end
-
- def test_threaded_connections
- gather_connections(true)
- assert_equal @connections.uniq.length, 5
- end
-
- def test_unthreaded_connections
- gather_connections(false)
- assert_equal @connections.uniq.length, 1
- end
- end
-end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index af3ee6ddba..b12ec36455 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -216,6 +216,7 @@ class TransactionTest < ActiveRecord::TestCase
uses_mocha 'mocking connection.commit_db_transaction' do
def test_rollback_when_commit_raises
Topic.connection.expects(:begin_db_transaction)
+ Topic.connection.expects(:transaction_active?).returns(true) if current_adapter?(:PostgreSQLAdapter)
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
Topic.connection.expects(:rollback_db_transaction)
@@ -283,16 +284,7 @@ end
if current_adapter?(:PostgreSQLAdapter)
class ConcurrentTransactionTest < TransactionTest
- def setup
- @allow_concurrency = ActiveRecord::Base.allow_concurrency
- ActiveRecord::Base.allow_concurrency = true
- super
- end
-
- def teardown
- super
- ActiveRecord::Base.allow_concurrency = @allow_concurrency
- end
+ use_concurrent_connections
# This will cause transactions to overlap and fail unless they are performed on
# separate database connections.
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 43592bcee3..42246f18b6 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -6,12 +6,16 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
+ @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations('en-US', :activerecord => {:errors => {:messages => {:custom => nil}}})
end
def teardown
reset_callbacks Topic
- I18n.load_translations File.dirname(__FILE__) + '/../../lib/active_record/locale/en-US.yml'
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
end
def unique_topic
@@ -40,6 +44,32 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
end
+ def test_percent_s_interpolation_syntax_in_error_messages_still_works
+ ActiveSupport::Deprecation.silence do
+ result = I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this'
+ assert_equal result, "this interpolation syntax is deprecated"
+ end
+ end
+
+ def test_percent_s_interpolation_syntax_in_error_messages_is_deprecated
+ assert_deprecated('using %s in messages') do
+ I18n.t :does_not_exist, :default => "%s interpolation syntax is deprected", :value => 'this'
+ end
+ end
+
+ def test_percent_d_interpolation_syntax_in_error_messages_still_works
+ ActiveSupport::Deprecation.silence do
+ result = I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprecated", :count => 2
+ assert_equal result, "2 interpolation syntaxes are deprecated"
+ end
+ end
+
+ def test_percent_d_interpolation_syntax_in_error_messages_is_deprecated
+ assert_deprecated('using %d in messages') do
+ I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected", :count => 2
+ end
+ end
+
# ActiveRecord::Errors
uses_mocha 'ActiveRecord::Errors' do
@@ -675,6 +705,38 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
replied_topic.valid?
assert_equal 'global message', replied_topic.errors.on(:replies)
end
+
+ def test_validations_with_message_symbol_must_translate
+ I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
+ Topic.validates_presence_of :title, :message => :custom_error
+ @topic.title = nil
+ @topic.valid?
+ assert_equal "I am a custom error", @topic.errors.on(:title)
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_attribute
+ I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
+ Topic.validates_presence_of :title, :message => :custom_error
+ @topic.title = nil
+ @topic.valid?
+ assert_equal "I am a custom error", @topic.errors.on(:title)
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_model
+ I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}}
+ Topic.validates_presence_of :title, :message => :custom_error
+ @topic.title = nil
+ @topic.valid?
+ assert_equal "I am a custom error", @topic.errors.on(:title)
+ end
+
+ def test_validates_with_message_string
+ Topic.validates_presence_of :title, :message => "I am a custom error"
+ @topic.title = nil
+ @topic.valid?
+ assert_equal "I am a custom error", @topic.errors.on(:title)
+ end
+
end
class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase
@@ -855,4 +917,5 @@ class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase
def test_generate_message_even_with_default_message
assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
end
+
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a40bda2533..c049659327 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -364,6 +364,13 @@ class ValidationsTest < ActiveRecord::TestCase
assert t2.save, "Should now save t2 as unique"
end
+ def test_validates_uniquness_with_newline_chars
+ Topic.validates_uniqueness_of(:title, :case_sensitive => false)
+
+ t = Topic.new("title" => "new\nline")
+ assert t.save, "Should save t as unique"
+ end
+
def test_validate_uniqueness_with_scope
Reply.validates_uniqueness_of(:content, :scope => "parent_id")
@@ -451,6 +458,18 @@ class ValidationsTest < ActiveRecord::TestCase
t2.title = nil
assert t2.valid?, "should validate with nil"
assert t2.save, "should save with nil"
+
+ with_kcode('UTF8') do
+ t_utf8 = Topic.new("title" => "Я тоже уникальный!")
+ assert t_utf8.save, "Should save t_utf8 as unique"
+
+ # If database hasn't UTF-8 character set, this test fails
+ if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
+ t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
+ assert !t2_utf8.valid?, "Shouldn't be valid"
+ assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
+ end
+ end
end
def test_validate_case_sensitive_uniqueness
@@ -593,7 +612,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validate_format_with_formatted_message
- Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %s")
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be {{value}}")
t = Topic.create(:title => 'Invalid title')
assert_equal "can't be Invalid title", t.errors.on(:title)
end
@@ -654,7 +673,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_inclusion_of_with_formatted_message
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %s is not in the list" )
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option {{value}} is not in the list" )
assert Topic.create("title" => "a", "content" => "abc").valid?
@@ -679,7 +698,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_exclusion_of_with_formatted_message
- Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %s is restricted" )
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option {{value}} is restricted" )
assert Topic.create("title" => "something", "content" => "abc")
@@ -779,7 +798,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_optionally_validates_length_of_using_within_on_create
- Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d"
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: {{count}}"
t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
assert !t.save
@@ -800,7 +819,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_optionally_validates_length_of_using_within_on_update
- Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d"
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: {{count}}"
t = Topic.create("title" => "vali", "content" => "whatever")
assert !t.save
@@ -863,7 +882,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_validates_length_with_globally_modified_error_message
ActiveSupport::Deprecation.silence do
- ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
end
Topic.validates_length_of :title, :minimum => 10
t = Topic.create(:title => 'too short')
@@ -907,7 +926,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_message
- Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" )
+ Topic.validates_length_of( :title, :minimum=>5, :message=>"boo {{count}}" )
t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -915,7 +934,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
- Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" )
+ Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo {{count}}" )
t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -923,7 +942,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_message
- Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" )
+ Topic.validates_length_of( :title, :maximum=>5, :message=>"boo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -931,7 +950,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" )
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -939,7 +958,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_message
- Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" )
+ Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -947,7 +966,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
- Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" )
+ Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -1013,7 +1032,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_optionally_validates_length_of_using_within_on_create_utf8
with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: %d"
+ Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: {{count}}"
t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
assert !t.save
@@ -1036,7 +1055,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_optionally_validates_length_of_using_within_on_update_utf8
with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: %d"
+ Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: {{count}}"
t = Topic.create("title" => "一二三4", "content" => "whatever")
assert !t.save
@@ -1071,7 +1090,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_of_with_block
- Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %d 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.create!(:content => "this content should be long enough")
assert t.valid?
@@ -1222,7 +1241,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -1231,7 +1250,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true )
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
@@ -1239,7 +1258,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :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.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
@@ -1247,7 +1266,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :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.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -1256,7 +1275,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -1265,7 +1284,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "a = 1; a == 1" )
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
@@ -1273,7 +1292,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
@@ -1281,7 +1300,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "false")
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
@@ -1290,7 +1309,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
:if => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
@@ -1300,7 +1319,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
:unless => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
@@ -1309,7 +1328,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
:if => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
@@ -1318,7 +1337,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
:unless => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
@@ -1495,13 +1514,13 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
end
def test_validates_numericality_with_numeric_message
- Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %d"
+ Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}"
topic = Topic.new("title" => "numeric test", "approved" => 10)
assert !topic.valid?
assert_equal "smaller than 4", topic.errors.on(:approved)
- Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %d"
+ Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than {{count}}"
topic = Topic.new("title" => "numeric test", "approved" => 1)
assert !topic.valid?
diff --git a/activerecord/test/fixtures/customers.yml b/activerecord/test/fixtures/customers.yml
index f802aac5ea..0399ff83b9 100644
--- a/activerecord/test/fixtures/customers.yml
+++ b/activerecord/test/fixtures/customers.yml
@@ -6,7 +6,7 @@ david:
address_city: Scary Town
address_country: Loony Land
gps_location: 35.544623640962634x-105.9309951055148
-
+
zaphod:
id: 2
name: Zaphod
@@ -14,4 +14,13 @@ zaphod:
address_street: Avenue Road
address_city: Hamlet Town
address_country: Nation Land
+ gps_location: NULL
+
+barney:
+ id: 3
+ name: Barney Gumble
+ balance: 1
+ address_street: Quiet Road
+ address_city: Peaceful Town
+ address_country: Tranquil Land
gps_location: NULL \ No newline at end of file
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index c6aa0293c2..37551c8157 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -1,7 +1,8 @@
class Author < ActiveRecord::Base
- has_many :posts, :accessible => true
+ has_many :posts
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
+ has_many :posts_sorted_by_id_limited, :class_name => "Post", :order => 'posts.id', :limit => 1
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
has_many :posts_containing_the_letter_a, :class_name => "Post"
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 1660c61682..4e9d247a4e 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -13,6 +13,9 @@ class Category < ActiveRecord::Base
has_and_belongs_to_many :post_with_conditions,
:class_name => 'Post',
:conditions => { :title => 'Yet Another Testing Title' }
+
+ has_and_belongs_to_many :posts_gruoped_by_title, :class_name => "Post", :group => "title", :select => "title"
+
def self.what_are_you
'a category...'
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index cd435948a1..0eb8ae0a15 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -55,6 +55,8 @@ class Firm < Company
has_many :readonly_clients, :class_name => 'Client', :readonly => true
has_many :clients_using_primary_key, :class_name => 'Client',
:primary_key => 'name', :foreign_key => 'firm_name'
+ has_many :clients_grouped_by_firm_id, :class_name => "Client", :group => "firm_id", :select => "firm_id"
+ has_many :clients_grouped_by_name, :class_name => "Client", :group => "name", :select => "name"
has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 030bbc6237..e258ccdb6c 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -1,7 +1,8 @@
class Customer < ActiveRecord::Base
composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true
- composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money }
+ composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
composed_of :gps_location, :allow_nil => true
+ composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse
end
class Address
@@ -53,3 +54,20 @@ class GpsLocation
self.latitude == other.latitude && self.longitude == other.longitude
end
end
+
+class Fullname
+ attr_reader :first, :last
+
+ def self.parse(str)
+ return nil unless str
+ new(*str.to_s.split)
+ end
+
+ def initialize(first, last = nil)
+ @first, @last = first, last
+ end
+
+ def to_s
+ "#{first} #{last.upcase}"
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 65191c1aa5..b9431fd1c0 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -3,6 +3,7 @@ class Parrot < ActiveRecord::Base
has_and_belongs_to_many :pirates
has_and_belongs_to_many :treasures
has_many :loots, :as => :looter
+ alias_attribute :title, :name
end
class LiveParrot < Parrot
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index e23818eb33..3adbc0ce1f 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -33,12 +33,6 @@ class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'
- belongs_to :creatable_author, :class_name => 'Author', :accessible => true
- has_one :uncreatable_comment, :class_name => 'Comment', :accessible => false, :order => 'id desc'
- has_one :creatable_comment, :class_name => 'Comment', :accessible => true, :order => 'id desc'
- has_many :creatable_comments, :class_name => 'Comment', :accessible => true, :dependent => :destroy
- has_and_belongs_to_many :creatable_categories, :class_name => 'Category', :accessible => true
-
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings do
def add_joins_and_select
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 4af30ea13f..d966062c7f 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -54,7 +54,7 @@ module ActiveResource
#
# Since simple CRUD/lifecycle methods can't accomplish every task, Active Resource also supports
# defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
- # <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods where you can specify a custom REST method
+ # <tt>post</tt>, <tt>put</tt> and <tt>\delete</tt> methods where you can specify a custom REST method
# name to invoke.
#
# # POST to the custom 'register' REST method, i.e. POST /people/new/register.xml.
@@ -129,13 +129,19 @@ module ActiveResource
#
# <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
# following HTTP response codes will also result in these exceptions:
- #
- # * 200..399 - Valid response, no exception
+ #
+ # * 200..399 - Valid response, no exception (other than 301, 302)
+ # * 301, 302 - ActiveResource::Redirection
+ # * 400 - ActiveResource::BadRequest
+ # * 401 - ActiveResource::UnauthorizedAccess
+ # * 403 - ActiveResource::ForbiddenAccess
# * 404 - ActiveResource::ResourceNotFound
+ # * 405 - ActiveResource::MethodNotAllowed
# * 409 - ActiveResource::ResourceConflict
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
# * 401..499 - ActiveResource::ClientError
# * 500..599 - ActiveResource::ServerError
+ # * Other - ActiveResource::ConnectionError
#
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
# rather than returning a general HTTP error. For example:
@@ -175,7 +181,7 @@ module ActiveResource
# === Timeouts
#
# Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
- # unresponsive servers. In such cases, your Active Resource method calls could timeout. You can control the
+ # unresponsive servers. In such cases, your Active Resource method calls could \timeout. You can control the
# amount of time before Active Resource times out with the +timeout+ variable.
#
# class Person < ActiveResource::Base
@@ -189,7 +195,7 @@ module ActiveResource
# http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
# server.
#
- # When a timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
+ # When a \timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
# ActiveResource::TimeoutError in your Active Resource method calls.
#
# Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
@@ -238,7 +244,7 @@ module ActiveResource
end
end
- # Gets the user for REST HTTP authentication.
+ # Gets the \user for REST HTTP authentication.
def user
# Not using superclass_delegating_reader. See +site+ for explanation
if defined?(@user)
@@ -248,13 +254,13 @@ module ActiveResource
end
end
- # Sets the user for REST HTTP authentication.
+ # Sets the \user for REST HTTP authentication.
def user=(user)
@connection = nil
@user = user
end
- # Gets the password for REST HTTP authentication.
+ # Gets the \password for REST HTTP authentication.
def password
# Not using superclass_delegating_reader. See +site+ for explanation
if defined?(@password)
@@ -264,7 +270,7 @@ module ActiveResource
end
end
- # Sets the password for REST HTTP authentication.
+ # Sets the \password for REST HTTP authentication.
def password=(password)
@connection = nil
@password = password
@@ -283,13 +289,13 @@ module ActiveResource
format = mime_type_reference_or_format.is_a?(Symbol) ?
ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
- write_inheritable_attribute("format", format)
+ write_inheritable_attribute(:format, format)
connection.format = format if site
end
# Returns the current format, default is ActiveResource::Formats::XmlFormat.
def format
- read_inheritable_attribute("format") || ActiveResource::Formats[:xml]
+ read_inheritable_attribute(:format) || ActiveResource::Formats[:xml]
end
# Sets the number of seconds after which requests to the REST API should time out.
@@ -307,8 +313,8 @@ module ActiveResource
end
end
- # An instance of ActiveResource::Connection that is the base connection to the remote service.
- # The +refresh+ parameter toggles whether or not the connection is refreshed at every request
+ # An instance of ActiveResource::Connection that is the base \connection to the remote service.
+ # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
# or not (defaults to <tt>false</tt>).
def connection(refresh = false)
if defined?(@connection) || superclass == Object
@@ -332,9 +338,9 @@ module ActiveResource
attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
-
- # Gets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
- # This method is regenerated at runtime based on what the prefix is set to.
+
+ # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
+ # This method is regenerated at runtime based on what the \prefix is set to.
def prefix(options={})
default = site.path
default << '/' unless default[-1..-1] == '/'
@@ -343,14 +349,14 @@ module ActiveResource
prefix(options)
end
- # An attribute reader for the source string for the resource path prefix. This
- # method is regenerated at runtime based on what the prefix is set to.
+ # An attribute reader for the source string for the resource path \prefix. This
+ # method is regenerated at runtime based on what the \prefix is set to.
def prefix_source
prefix # generate #prefix and #prefix_source methods first
prefix_source
end
- # Sets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>).
+ # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>).
# Default value is <tt>site.path</tt>.
def prefix=(value = '/')
# Replace :placeholders with '#{embedded options[:lookups]}'
@@ -376,12 +382,12 @@ module ActiveResource
alias_method :set_collection_name, :collection_name= #:nodoc:
# Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
- # will split from the prefix options.
+ # will split from the \prefix options.
#
# ==== Options
- # +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
- # +query_options+ - A hash to add items to the query string for the request.
+ # +query_options+ - A \hash to add items to the query string for the request.
#
# ==== Examples
# Post.element_path(1)
@@ -471,7 +477,7 @@ module ActiveResource
# ==== Options
#
# * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
- # * <tt>:params</tt> - Sets query and prefix (nested URL) parameters.
+ # * <tt>:params</tt> - Sets query and \prefix (nested URL) parameters.
#
# ==== Examples
# Person.find(1)
@@ -519,7 +525,7 @@ module ActiveResource
# Deletes the resources with the ID in the +id+ parameter.
#
# ==== Options
- # All options specify prefix and query parameters.
+ # All options specify \prefix and query parameters.
#
# ==== Examples
# Event.delete(2) # sends DELETE /events/2
@@ -630,8 +636,8 @@ module ActiveResource
attr_accessor :attributes #:nodoc:
attr_accessor :prefix_options #:nodoc:
- # Constructor method for new resources; the optional +attributes+ parameter takes a hash
- # of attributes for the new resource.
+ # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
+ # of attributes for the \new resource.
#
# ==== Examples
# my_course = Course.new
@@ -647,8 +653,8 @@ module ActiveResource
load(attributes)
end
- # Returns a clone of the resource that hasn't been assigned an +id+ yet and
- # is treated as a new resource.
+ # Returns a \clone of the resource that hasn't been assigned an +id+ yet and
+ # is treated as a \new resource.
#
# ryan = Person.find(1)
# not_ryan = ryan.clone
@@ -683,7 +689,7 @@ module ActiveResource
end
- # A method to determine if the resource a new object (i.e., it has not been POSTed to the remote service yet).
+ # A method to determine if the resource a \new object (i.e., it has not been POSTed to the remote service yet).
#
# ==== Examples
# not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
@@ -699,12 +705,12 @@ module ActiveResource
id.nil?
end
- # Get the +id+ attribute of the resource.
+ # Gets the <tt>\id</tt> attribute of the resource.
def id
attributes[self.class.primary_key]
end
- # Set the +id+ attribute of the resource.
+ # Sets the <tt>\id</tt> attribute of the resource.
def id=(id)
attributes[self.class.primary_key] = id
end
@@ -745,7 +751,7 @@ module ActiveResource
self == other
end
- # Delegates to id in order to allow two resources of the same type and id to work with something like:
+ # Delegates to id in order to allow two resources of the same type and \id to work with something like:
# [Person.find(1), Person.find(2)] & [Person.find(1), Person.find(4)] # => [Person.find(1)]
def hash
id.hash
@@ -770,9 +776,9 @@ module ActiveResource
end
end
- # A method to save (+POST+) or update (+PUT+) a resource. It delegates to +create+ if a new object,
- # +update+ if it is existing. If the response to the save includes a body, it will be assumed that this body
- # is XML for the final object as it looked after the save (which would include attributes like +created_at+
+ # A method to \save (+POST+) or \update (+PUT+) a resource. It delegates to +create+ if a \new object,
+ # +update+ if it is existing. If the response to the \save includes a body, it will be assumed that this body
+ # is XML for the final object as it looked after the \save (which would include attributes like +created_at+
# that weren't part of the original submit).
#
# ==== Examples
@@ -848,11 +854,16 @@ module ActiveResource
#
# my_group.to_xml(:skip_instruct => true)
# # => <subsidiary_group> [...] </subsidiary_group>
- def to_xml(options={})
- attributes.to_xml({:root => self.class.element_name}.merge(options))
+ def encode(options={})
+ case self.class.format
+ when ActiveResource::Formats[:xml]
+ self.class.format.encode(attributes, {:root => self.class.element_name}.merge(options))
+ else
+ self.class.format.encode(attributes, options)
+ end
end
- # A method to reload the attributes of this object from the remote web service.
+ # A method to \reload the attributes of this object from the remote web service.
#
# ==== Examples
# my_branch = Branch.find(:first)
@@ -867,8 +878,8 @@ module ActiveResource
self.load(self.class.find(to_param, :params => @prefix_options).attributes)
end
- # A method to manually load attributes from a hash. Recursively loads collections of
- # resources. This method is called in +initialize+ and +create+ when a hash of attributes
+ # A method to manually load attributes from a \hash. Recursively loads collections of
+ # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
# is provided.
#
# ==== Examples
@@ -910,8 +921,8 @@ module ActiveResource
alias_method :respond_to_without_attributes?, :respond_to?
# A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
- # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?("name")</tt>, <tt>my_person.respond_to?("name=")</tt>, and
- # <tt>my_person.respond_to?("name?")</tt>.
+ # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
+ # <tt>my_person.respond_to?(:name?)</tt>.
def respond_to?(method, include_priv = false)
method_name = method.to_s
if attributes.nil?
@@ -934,14 +945,14 @@ module ActiveResource
# Update the resource on the remote service.
def update
- returning connection.put(element_path(prefix_options), to_xml, self.class.headers) do |response|
+ returning connection.put(element_path(prefix_options), encode, self.class.headers) do |response|
load_attributes_from_response(response)
end
end
- # Create (i.e., save to the remote service) the new resource.
+ # Create (i.e., \save to the remote service) the \new resource.
def create
- returning connection.post(collection_path, to_xml, self.class.headers) do |response|
+ returning connection.post(collection_path, encode, self.class.headers) do |response|
self.id = id_from_response(response)
load_attributes_from_response(response)
end
@@ -1007,7 +1018,7 @@ module ActiveResource
end
def split_options(options = {})
- self.class.send!(:split_options, options)
+ self.class.__send__(:split_options, options)
end
def method_missing(method_symbol, *arguments) #:nodoc:
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index d64fb79f1e..273fee3286 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -63,6 +63,13 @@ module ActiveResource
# This class is used by ActiveResource::Base to interface with REST
# services.
class Connection
+
+ HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
+ :put => 'Content-Type',
+ :post => 'Content-Type',
+ :delete => 'Accept'
+ }
+
attr_reader :site, :user, :password, :timeout
attr_accessor :format
@@ -106,25 +113,25 @@ module ActiveResource
# Execute a GET request.
# Used to get (find) resources.
def get(path, headers = {})
- format.decode(request(:get, path, build_request_headers(headers)).body)
+ format.decode(request(:get, path, build_request_headers(headers, :get)).body)
end
# Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
# Used to delete resources.
def delete(path, headers = {})
- request(:delete, path, build_request_headers(headers))
+ request(:delete, path, build_request_headers(headers, :delete))
end
# Execute a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})
- request(:put, path, body.to_s, build_request_headers(headers))
+ request(:put, path, body.to_s, build_request_headers(headers, :put))
end
# Execute a POST request.
# Used to create new resources.
def post(path, body = '', headers = {})
- request(:post, path, body.to_s, build_request_headers(headers))
+ request(:post, path, body.to_s, build_request_headers(headers, :post))
end
# Execute a HEAD request.
@@ -187,12 +194,12 @@ module ActiveResource
end
def default_header
- @default_header ||= { 'Content-Type' => format.mime_type }
+ @default_header ||= {}
end
# Builds headers for request to remote service.
- def build_request_headers(headers)
- authorization_header.update(default_header).update(headers)
+ def build_request_headers(headers, http_method=nil)
+ authorization_header.update(default_header).update(http_format_header(http_method)).update(headers)
end
# Sets authorization header
@@ -200,6 +207,10 @@ module ActiveResource
(@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
end
+ def http_format_header(http_method)
+ {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
+ end
+
def logger #:nodoc:
Base.logger
end
diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb
index 770116ceb7..24306f251d 100644
--- a/activeresource/lib/active_resource/custom_methods.rb
+++ b/activeresource/lib/active_resource/custom_methods.rb
@@ -30,7 +30,7 @@ module ActiveResource
# Person.get(:active) # GET /people/active.xml
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
#
- module CustomMethods
+ module CustomMethods
def self.included(base)
base.class_eval do
extend ActiveResource::CustomMethods::ClassMethods
@@ -83,24 +83,25 @@ module ActiveResource
"#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
end
end
-
+
module InstanceMethods
def get(method_name, options = {})
connection.get(custom_method_element_url(method_name, options), self.class.headers)
end
-
- def post(method_name, options = {}, body = '')
+
+ def post(method_name, options = {}, body = nil)
+ request_body = body.nil? ? encode : body
if new?
- connection.post(custom_method_new_element_url(method_name, options), (body.nil? ? to_xml : body), self.class.headers)
+ connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
else
- connection.post(custom_method_element_url(method_name, options), body, self.class.headers)
+ connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
end
end
-
+
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
-
+
def delete(method_name, options = {})
connection.delete(custom_method_element_url(method_name, options), self.class.headers)
end
@@ -108,11 +109,11 @@ module ActiveResource
private
def custom_method_element_url(method_name, options = {})
- "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}"
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
end
-
+
def custom_method_new_element_url(method_name, options = {})
- "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}"
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
end
end
end
diff --git a/activeresource/lib/active_resource/formats/json_format.rb b/activeresource/lib/active_resource/formats/json_format.rb
index df0d6ca372..9e269d4ded 100644
--- a/activeresource/lib/active_resource/formats/json_format.rb
+++ b/activeresource/lib/active_resource/formats/json_format.rb
@@ -2,22 +2,22 @@ module ActiveResource
module Formats
module JsonFormat
extend self
-
+
def extension
"json"
end
-
+
def mime_type
"application/json"
end
-
- def encode(hash)
+
+ def encode(hash, options={})
hash.to_json
end
-
+
def decode(json)
ActiveSupport::JSON.decode(json)
end
end
end
-end \ No newline at end of file
+end
diff --git a/activeresource/lib/active_resource/formats/xml_format.rb b/activeresource/lib/active_resource/formats/xml_format.rb
index 5e97ffa776..86c6cec745 100644
--- a/activeresource/lib/active_resource/formats/xml_format.rb
+++ b/activeresource/lib/active_resource/formats/xml_format.rb
@@ -2,23 +2,23 @@ module ActiveResource
module Formats
module XmlFormat
extend self
-
+
def extension
"xml"
end
-
+
def mime_type
"application/xml"
end
-
- def encode(hash)
- hash.to_xml
+
+ def encode(hash, options={})
+ hash.to_xml(options)
end
-
+
def decode(xml)
from_xml_data(Hash.from_xml(xml))
end
-
+
private
# Manipulate from_xml Hash, because xml_simple is not exactly what we
# want for Active Resource.
@@ -28,7 +28,7 @@ module ActiveResource
else
data
end
- end
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 554fc3bcfc..9ed532b48c 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -146,7 +146,7 @@ module ActiveResource
attr_accessor :path, :method, :body, :headers
def initialize(method, path, body = nil, headers = {})
- @method, @path, @body, @headers = method, path, body, headers.reverse_merge('Content-Type' => 'application/xml')
+ @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml')
end
def ==(other_request)
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index a7c624f309..4bc906d291 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -216,39 +216,25 @@ module ActiveResource
end
end
- # Module to allow validation of Active Resource objects, which creates an Errors instance for every resource.
- # Methods are implemented by overriding Base#validate or its variants Each of these methods can inspect
- # the state of the object, which usually means ensuring that a number of attributes have a certain value
- # (such as not empty, within a given range, matching a certain regular expression and so on).
+ # Module to support validation and errors with Active Resource objects. The module overrides
+ # Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
+ # in the web service response. The module also adds an +errors+ collection that mimics the interface
+ # of the errors provided by ActiveRecord::Errors.
#
# ==== Example
#
- # class Person < ActiveResource::Base
- # self.site = "http://www.localhost.com:3000/"
- # protected
- # def validate
- # errors.add_on_empty %w( first_name last_name )
- # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
- # end
+ # Consider a Person resource on the server requiring both a +first_name+ and a +last_name+ with a
+ # <tt>validates_presence_of :first_name, :last_name</tt> declaration in the model:
#
- # def validate_on_create # is only run the first time a new object is saved
- # unless valid_member?(self)
- # errors.add("membership_discount", "has expired")
- # end
- # end
- #
- # def validate_on_update
- # errors.add_to_base("No changes have occurred") if unchanged_attributes?
- # end
- # end
- #
- # person = Person.new("first_name" => "Jim", "phone_number" => "I will not tell you.")
- # person.save # => false (and doesn't do the save)
- # person.errors.empty? # => false
- # person.errors.count # => 2
- # person.errors.on "last_name" # => "can't be empty"
- # person.attributes = { "last_name" => "Halpert", "phone_number" => "555-5555" }
- # person.save # => true (and person is now saved to the remote service)
+ # person = Person.new(:first_name => "Jim", :last_name => "")
+ # person.save # => false (server returns an HTTP 422 status code and errors)
+ # person.valid? # => false
+ # person.errors.empty? # => false
+ # person.errors.count # => 1
+ # person.errors.full_messages # => ["Last name can't be empty"]
+ # person.errors.on(:last_name) # => "can't be empty"
+ # person.last_name = "Halpert"
+ # person.save # => true (and person is now saved to the remote service)
#
module Validations
def self.included(base) # :nodoc:
diff --git a/activeresource/test/authorization_test.rb b/activeresource/test/authorization_test.rb
index 9215227620..ead7f5c12f 100644
--- a/activeresource/test/authorization_test.rb
+++ b/activeresource/test/authorization_test.rb
@@ -19,7 +19,7 @@ class AuthorizationTest < Test::Unit::TestCase
end
def test_authorization_header
- authorization_header = @authenticated_conn.send!(:authorization_header)
+ authorization_header = @authenticated_conn.__send__(:authorization_header)
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
@@ -29,7 +29,7 @@ class AuthorizationTest < Test::Unit::TestCase
def test_authorization_header_with_username_but_no_password
@conn = ActiveResource::Connection.new("http://david:@localhost")
- authorization_header = @conn.send!(:authorization_header)
+ authorization_header = @conn.__send__(:authorization_header)
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
@@ -38,7 +38,7 @@ class AuthorizationTest < Test::Unit::TestCase
def test_authorization_header_with_password_but_no_username
@conn = ActiveResource::Connection.new("http://:test123@localhost")
- authorization_header = @conn.send!(:authorization_header)
+ authorization_header = @conn.__send__(:authorization_header)
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
@@ -47,7 +47,7 @@ class AuthorizationTest < Test::Unit::TestCase
def test_authorization_header_with_decoded_credentials_from_url
@conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost")
- authorization_header = @conn.send!(:authorization_header)
+ authorization_header = @conn.__send__(:authorization_header)
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
@@ -58,7 +58,7 @@ class AuthorizationTest < Test::Unit::TestCase
@authenticated_conn = ActiveResource::Connection.new("http://@localhost")
@authenticated_conn.user = 'david'
@authenticated_conn.password = 'test123'
- authorization_header = @authenticated_conn.send!(:authorization_header)
+ authorization_header = @authenticated_conn.__send__(:authorization_header)
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
@@ -69,7 +69,7 @@ class AuthorizationTest < Test::Unit::TestCase
def test_authorization_header_explicitly_setting_username_but_no_password
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.user = "david"
- authorization_header = @conn.send!(:authorization_header)
+ authorization_header = @conn.__send__(:authorization_header)
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
@@ -79,7 +79,7 @@ class AuthorizationTest < Test::Unit::TestCase
def test_authorization_header_explicitly_setting_password_but_no_username
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.password = "test123"
- authorization_header = @conn.send!(:authorization_header)
+ authorization_header = @conn.__send__(:authorization_header)
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
@@ -116,7 +116,7 @@ class AuthorizationTest < Test::Unit::TestCase
protected
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
- @conn.send!(:handle_response, Response.new(code))
+ @conn.__send__(:handle_response, Response.new(code))
end
end
end
diff --git a/activeresource/test/base/custom_methods_test.rb b/activeresource/test/base/custom_methods_test.rb
index 62c33ef9bc..ba5799edfb 100644
--- a/activeresource/test/base/custom_methods_test.rb
+++ b/activeresource/test/base/custom_methods_test.rb
@@ -10,8 +10,7 @@ class CustomMethodsTest < Test::Unit::TestCase
@ryan = { :name => 'Ryan' }.to_xml(:root => 'person')
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
@addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address')
- @default_request_headers = { 'Content-Type' => 'application/xml' }
-
+
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.xml", {}, @matz
mock.get "/people/1/shallow.xml", {}, @matz
diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/base/load_test.rb
index 737afb1748..a475fab34e 100644
--- a/activeresource/test/base/load_test.rb
+++ b/activeresource/test/base/load_test.rb
@@ -84,7 +84,7 @@ class BaseLoadTest < Test::Unit::TestCase
end
def test_load_collection_with_unknown_resource
- Person.send!(:remove_const, :Address) if Person.const_defined?(:Address)
+ Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
addresses = silence_warnings { @person.load(:addresses => @addresses).addresses }
assert Person.const_defined?(:Address), "Address should have been autocreated"
@@ -100,7 +100,7 @@ class BaseLoadTest < Test::Unit::TestCase
end
def test_load_collection_with_single_unknown_resource
- Person.send!(:remove_const, :Address) if Person.const_defined?(:Address)
+ Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
addresses = silence_warnings { @person.load(:addresses => [ @first_address ]).addresses }
assert Person.const_defined?(:Address), "Address should have been autocreated"
diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb
index 9a0789e0b4..7460fd45b0 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/base_test.rb
@@ -468,7 +468,7 @@ class BaseTest < Test::Unit::TestCase
def test_prefix
assert_equal "/", Person.prefix
- assert_equal Set.new, Person.send!(:prefix_parameters)
+ assert_equal Set.new, Person.__send__(:prefix_parameters)
end
def test_set_prefix
@@ -504,7 +504,7 @@ class BaseTest < Test::Unit::TestCase
def test_custom_prefix
assert_equal '/people//', StreetAddress.prefix
assert_equal '/people/1/', StreetAddress.prefix(:person_id => 1)
- assert_equal [:person_id].to_set, StreetAddress.send!(:prefix_parameters)
+ assert_equal [:person_id].to_set, StreetAddress.__send__(:prefix_parameters)
end
def test_find_by_id
@@ -607,10 +607,10 @@ class BaseTest < Test::Unit::TestCase
def test_id_from_response
p = Person.new
resp = {'Location' => '/foo/bar/1'}
- assert_equal '1', p.send!(:id_from_response, resp)
+ assert_equal '1', p.__send__(:id_from_response, resp)
resp['Location'] << '.xml'
- assert_equal '1', p.send!(:id_from_response, resp)
+ assert_equal '1', p.__send__(:id_from_response, resp)
end
def test_create_with_custom_prefix
@@ -825,7 +825,7 @@ class BaseTest < Test::Unit::TestCase
def test_to_xml
matz = Person.find(1)
- xml = matz.to_xml
+ xml = matz.encode
assert xml.starts_with?('<?xml version="1.0" encoding="UTF-8"?>')
assert xml.include?('<name>Matz</name>')
assert xml.include?('<id type="integer">1</id>')
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 8e43e451ff..84bcf69219 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -168,12 +168,20 @@ class ConnectionTest < Test::Unit::TestCase
assert_equal 200, response.code
end
- uses_mocha('test_timeout') do
+ uses_mocha('test_timeout, test_accept_http_header') do
def test_timeout
@http = mock('new Net::HTTP')
@conn.expects(:http).returns(@http)
@http.expects(:get).raises(Timeout::Error, 'execution expired')
- assert_raises(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') }
+ assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') }
+ end
+
+ def test_accept_http_header
+ @http = mock('new Net::HTTP')
+ @conn.expects(:http).returns(@http)
+ path = '/people/1.xml'
+ @http.expects(:get).with(path, {'Accept' => 'application/xhtml+xml'}).returns(ActiveResource::Response.new(@matz, 200, {'Content-Type' => 'text/xhtml'}))
+ assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, {'Accept' => 'application/xhtml+xml'}) }
end
end
@@ -185,6 +193,6 @@ class ConnectionTest < Test::Unit::TestCase
end
def handle_response(response)
- @conn.send!(:handle_response, response)
+ @conn.__send__(:handle_response, response)
end
end
diff --git a/activeresource/test/format_test.rb b/activeresource/test/format_test.rb
index 3c81803fce..365576a092 100644
--- a/activeresource/test/format_test.rb
+++ b/activeresource/test/format_test.rb
@@ -5,14 +5,22 @@ class FormatTest < Test::Unit::TestCase
def setup
@matz = { :id => 1, :name => 'Matz' }
@david = { :id => 2, :name => 'David' }
-
+
@programmers = [ @matz, @david ]
end
-
+
+ def test_http_format_header_name
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
+ assert_equal 'Accept', header_name
+
+ headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
+ headers_names.each{|header_name| assert_equal 'Content-Type', header_name}
+ end
+
def test_formats_on_single_element
for format in [ :json, :xml ]
using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {}, ActiveResource::Formats[format].encode(@david)
+ ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
assert_equal @david[:name], Person.find(1).name
end
end
@@ -21,7 +29,7 @@ class FormatTest < Test::Unit::TestCase
def test_formats_on_collection
for format in [ :json, :xml ]
using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people.#{format}", {}, ActiveResource::Formats[format].encode(@programmers)
+ ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers)
remote_programmers = Person.find(:all)
assert_equal 2, remote_programmers.size
assert remote_programmers.select { |p| p.name == 'David' }
@@ -32,7 +40,7 @@ class FormatTest < Test::Unit::TestCase
def test_formats_on_custom_collection_method
for format in [ :json, :xml ]
using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {}, ActiveResource::Formats[format].encode([@david])
+ ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david])
remote_programmers = Person.get(:retrieve, :name => 'David')
assert_equal 1, remote_programmers.size
assert_equal @david[:id], remote_programmers[0]['id']
@@ -40,13 +48,13 @@ class FormatTest < Test::Unit::TestCase
end
end
end
-
+
def test_formats_on_custom_element_method
for format in [ :json, :xml ]
using_format(Person, format) do
ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.#{format}", {}, ActiveResource::Formats[format].encode(@david)
- mock.get "/people/2/shallow.#{format}", {}, ActiveResource::Formats[format].encode(@david)
+ mock.get "/people/2.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
+ mock.get "/people/2/shallow.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
end
remote_programmer = Person.find(2).get(:shallow)
assert_equal @david[:id], remote_programmer['id']
@@ -57,20 +65,24 @@ class FormatTest < Test::Unit::TestCase
for format in [ :json, :xml ]
ryan = ActiveResource::Formats[format].encode({ :name => 'Ryan' })
using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", {}, ryan, 201, 'Location' => "/people/5.#{format}"
remote_ryan = Person.new(:name => 'Ryan')
+ ActiveResource::HttpMock.respond_to.post "/people.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, {'Location' => "/people/5.#{format}"}
+ remote_ryan.save
+
+ remote_ryan = Person.new(:name => 'Ryan')
+ ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, {'Location' => "/people/5.#{format}"}
assert_equal ActiveResource::Response.new(ryan, 201, {'Location' => "/people/5.#{format}"}), remote_ryan.post(:register)
end
end
end
-
+
def test_setting_format_before_site
resource = Class.new(ActiveResource::Base)
resource.format = :json
resource.site = 'http://37s.sunrise.i:3000'
assert_equal ActiveResource::Formats[:json], resource.connection.format
end
-
+
private
def using_format(klass, mime_type_reference)
previous_format = klass.format
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 8d3b136d80..00da2a2284 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,17 @@
*Edge*
+* TimeWithZone #wday, #yday and #to_date avoid trip through #method_missing [Geoff Buesing]
+
+* Added Time, Date, DateTime and TimeWithZone #past?, #future? and #today? #720 [Clemens Kofler, Geoff Buesing]
+
+* Fixed Sri Jayawardenepura time zone to map to Asia/Colombo [Jamis Buck]
+
+* Added Inflector#parameterize for easy slug generation ("Donald E. Knuth".parameterize => "donald-e-knuth") #713 [Matt Darby]
+
+* Changed cache benchmarking to be reported in milliseconds [DHH]
+
+* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing]
+
* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
a = (1..10).to_a
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index ae3e2360cd..b30faff06d 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -28,7 +28,6 @@ require 'active_support/callbacks'
require 'active_support/core_ext'
-require 'active_support/clean_logger'
require 'active_support/buffered_logger'
require 'active_support/gzip'
@@ -55,9 +54,9 @@ require 'active_support/base64'
require 'active_support/time_with_zone'
-I18n.populate do
- I18n.load_translations File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
-end
+require 'active_support/secure_random'
+
+I18n.load_path << File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies')
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 6553d72b4f..77e0b1d33f 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -116,7 +116,7 @@ module ActiveSupport
end
def clear_buffer
- @buffer[Thread.current] = []
+ @buffer.delete(Thread.current)
end
end
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 95eae3a77e..51a6309dce 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -62,7 +62,7 @@ module ActiveSupport
write(key, value, options)
@logger_off = false
- log("write (will save #{'%.5f' % seconds})", key, nil)
+ log("write (will save #{'%.2f' % (seconds * 1000)}ms)", key, nil)
value
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 659bde64f0..ef533633b2 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -65,6 +65,6 @@ module ActiveSupport
end
end
end
- end
+ end
end
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index f3e4b8c13b..c1a713b4c5 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -3,63 +3,35 @@ module ActiveSupport
class MemoryStore < Store
def initialize
@data = {}
- @guard = Monitor.new
- end
-
- def fetch(key, options = {})
- @guard.synchronize do
- super
- end
end
def read(name, options = nil)
- @guard.synchronize do
- super
- @data[name]
- end
+ super
+ @data[name]
end
def write(name, value, options = nil)
- @guard.synchronize do
- super
- @data[name] = value.freeze
- end
+ super
+ @data[name] = value.freeze
end
def delete(name, options = nil)
- @guard.synchronize do
- @data.delete(name)
- end
+ super
+ @data.delete(name)
end
def delete_matched(matcher, options = nil)
- @guard.synchronize do
- @data.delete_if { |k,v| k =~ matcher }
- end
+ super
+ @data.delete_if { |k,v| k =~ matcher }
end
def exist?(name,options = nil)
- @guard.synchronize do
- @data.has_key?(name)
- end
- end
-
- def increment(key, amount = 1)
- @guard.synchronize do
- super
- end
- end
-
- def decrement(key, amount = 1)
- @guard.synchronize do
- super
- end
+ super
+ @data.has_key?(name)
end
def clear
- @guard.synchronize do
- @data.clear
- end
+ @data.clear
end
end
end
diff --git a/activesupport/lib/active_support/cache/synchronized_memory_store.rb b/activesupport/lib/active_support/cache/synchronized_memory_store.rb
new file mode 100644
index 0000000000..d2ff28768f
--- /dev/null
+++ b/activesupport/lib/active_support/cache/synchronized_memory_store.rb
@@ -0,0 +1,46 @@
+module ActiveSupport
+ module Cache
+ class SynchronizedMemoryStore < MemoryStore
+ def initialize
+ super
+ @guard = Monitor.new
+ end
+
+ def fetch(key, options = {})
+ @guard.synchronize { super }
+ end
+
+ def read(name, options = nil)
+ @guard.synchronize { super }
+ end
+
+ def write(name, value, options = nil)
+ @guard.synchronize { super }
+ end
+
+ def delete(name, options = nil)
+ @guard.synchronize { super }
+ end
+
+ def delete_matched(matcher, options = nil)
+ @guard.synchronize { super }
+ end
+
+ def exist?(name,options = nil)
+ @guard.synchronize { super }
+ end
+
+ def increment(key, amount = 1)
+ @guard.synchronize { super }
+ end
+
+ def decrement(key, amount = 1)
+ @guard.synchronize { super }
+ end
+
+ def clear
+ @guard.synchronize { super }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 7b905930bb..5cdcaf5ad1 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -188,7 +188,7 @@ module ActiveSupport
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
"a block to be invoked, or an object responding to the callback method."
end
- end
+ end
end
def should_run_callback?(*args)
diff --git a/activesupport/lib/active_support/clean_logger.rb b/activesupport/lib/active_support/clean_logger.rb
deleted file mode 100644
index b4c27ebc9d..0000000000
--- a/activesupport/lib/active_support/clean_logger.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-require 'logger'
-require 'active_support/core_ext/class/attribute_accessors'
-
-# Extensions to the built in Ruby logger.
-#
-# If you want to use the default log formatter as defined in the Ruby core, then you
-# will need to set the formatter for the logger as in:
-#
-# logger.formatter = Formatter.new
-#
-# You can then specify the datetime format, for example:
-#
-# logger.datetime_format = "%Y-%m-%d"
-#
-# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
-class Logger
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = Logger::ERROR)
- if silencer
- begin
- old_logger_level, self.level = level, temporary_level
- yield self
- ensure
- self.level = old_logger_level
- end
- else
- yield self
- end
- end
-
- alias :old_datetime_format= :datetime_format=
- # Logging date-time format (string passed to +strftime+). Ignored if the formatter
- # does not respond to datetime_format=.
- def datetime_format=(datetime_format)
- formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
- end
-
- alias :old_datetime_format :datetime_format
- # Get the logging datetime format. Returns nil if the formatter does not support
- # datetime formatting.
- def datetime_format
- formatter.datetime_format if formatter.respond_to?(:datetime_format)
- end
-
- alias :old_formatter :formatter if method_defined?(:formatter)
- # Get the current formatter. The default formatter is a SimpleFormatter which only
- # displays the log message
- def formatter
- @formatter ||= SimpleFormatter.new
- end
-
- unless const_defined? :Formatter
- class Formatter
- Format = "%s, [%s#%d] %5s -- %s: %s\n"
-
- attr_accessor :datetime_format
-
- def initialize
- @datetime_format = nil
- end
-
- def call(severity, time, progname, msg)
- Format % [severity[0..0], format_datetime(time), $$, severity, progname,
- msg2str(msg)]
- end
-
- private
- def format_datetime(time)
- if @datetime_format.nil?
- time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
- else
- time.strftime(@datetime_format)
- end
- end
-
- def msg2str(msg)
- case msg
- when ::String
- msg
- when ::Exception
- "#{ msg.message } (#{ msg.class })\n" <<
- (msg.backtrace || []).join("\n")
- else
- msg.inspect
- end
- end
- end
- end
-
- # Simple formatter which only displays the message.
- class SimpleFormatter < Logger::Formatter
- # This method is invoked when a log event occurs
- def call(severity, timestamp, progname, msg)
- "#{String === msg ? msg : msg.inspect}\n"
- end
- end
-
- private
- alias old_format_message format_message
-
- # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
- # We can't test RUBY_VERSION because some distributions don't keep Ruby
- # and its standard library in sync, leading to installations of Ruby 1.8.2
- # with Logger from 1.8.3 and vice versa.
- if method_defined?(:formatter=)
- def format_message(severity, timestamp, progname, msg)
- formatter.call(severity, timestamp, progname, msg)
- end
- else
- def format_message(severity, timestamp, msg, progname)
- formatter.call(severity, timestamp, progname, msg)
- end
-
- attr_writer :formatter
- public :formatter=
-
- alias old_format_datetime format_datetime
- def format_datetime(datetime) datetime end
-
- alias old_msg2str msg2str
- def msg2str(msg) msg end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index dd1484f8fa..f782f8facf 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -7,16 +7,16 @@ module ActiveSupport #:nodoc:
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
- # %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
+ # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
# ["7", nil, nil]
#
- # %w(1 2 3).in_groups_of(2, '&nbsp;') {|g| p g}
+ # %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
# ["1", "2"]
# ["3", "&nbsp;"]
#
- # %w(1 2 3).in_groups_of(2, false) {|g| p g}
+ # %w(1 2 3).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
# ["3"]
def in_groups_of(number, fill_with = nil)
@@ -42,17 +42,17 @@ module ActiveSupport #:nodoc:
# Splits or iterates over the array in +number+ of groups, padding any
# remaining slots with +fill_with+ unless it is +false+.
#
- # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|g| p g}
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
# ["1", "2", "3", "4"]
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
- # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|g| p g}
+ # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "&nbsp;"]
# ["6", "7", "&nbsp;"]
#
- # %w(1 2 3 4 5 6 7).in_groups(3, false) {|g| p g}
+ # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
# ["4", "5"]
# ["6", "7"]
diff --git a/activesupport/lib/active_support/core_ext/bigdecimal/conversions.rb b/activesupport/lib/active_support/core_ext/bigdecimal/conversions.rb
index 94c7c779f7..bc9d578f38 100644
--- a/activesupport/lib/active_support/core_ext/bigdecimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/bigdecimal/conversions.rb
@@ -4,35 +4,31 @@ module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module BigDecimal #:nodoc:
module Conversions
+ DEFAULT_STRING_FORMAT = 'F'.freeze
+ YAML_TAG = 'tag:yaml.org,2002:float'.freeze
+ YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
+
def self.included(base) #:nodoc:
- base.instance_eval do
+ base.class_eval do
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
+
+ yaml_as YAML_TAG
end
end
-
- def to_formatted_s(format="F")
+
+ def to_formatted_s(format = DEFAULT_STRING_FORMAT)
_original_to_s(format)
end
-
- yaml_as "tag:yaml.org,2002:float"
- def to_yaml( opts = {} )
- YAML::quick_emit( nil, opts ) do |out|
- # This emits the number without any scientific notation.
- # I prefer it to using self.to_f.to_s, which would lose precision.
- #
- # Note that YAML allows that when reconstituting floats
- # to native types, some precision may get lost.
- # There is no full precision real YAML tag that I am aware of.
- str = self.to_s
- if str == "Infinity"
- str = ".Inf"
- elsif str == "-Infinity"
- str = "-.Inf"
- elsif str == "NaN"
- str = ".NaN"
- end
- out.scalar( "tag:yaml.org,2002:float", str, :plain )
+
+ # This emits the number without any scientific notation.
+ # This is better than self.to_f.to_s since it doesn't lose precision.
+ #
+ # Note that reconstituting YAML floats to native floats may lose precision.
+ def to_yaml(opts = {})
+ YAML.quick_emit(nil, opts) do |out|
+ string = to_s
+ out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index 8724a492bf..e6143a274b 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -91,14 +91,14 @@ class Class # :nodoc:
def inheritable_attributes
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
end
-
+
def write_inheritable_attribute(key, value)
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
@inheritable_attributes = {}
end
inheritable_attributes[key] = value
end
-
+
def write_inheritable_array(key, elements)
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
@@ -112,7 +112,7 @@ class Class # :nodoc:
def read_inheritable_attribute(key)
inheritable_attributes[key]
end
-
+
def reset_inheritable_attributes
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
end
@@ -123,7 +123,7 @@ class Class # :nodoc:
def inherited_with_inheritable_attributes(child)
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
-
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
else
@@ -131,7 +131,7 @@ class Class # :nodoc:
memo.update(key => value.duplicable? ? value.dup : value)
end
end
-
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
end
diff --git a/activesupport/lib/active_support/core_ext/date/behavior.rb b/activesupport/lib/active_support/core_ext/date/behavior.rb
index 6f8f7c6a82..bd378eb375 100644
--- a/activesupport/lib/active_support/core_ext/date/behavior.rb
+++ b/activesupport/lib/active_support/core_ext/date/behavior.rb
@@ -1,3 +1,5 @@
+require 'date'
+
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Date #:nodoc:
@@ -10,18 +12,29 @@ module ActiveSupport #:nodoc:
# Date memoizes some instance methods using metaprogramming to wrap
# the methods with one that caches the result in an instance variable.
+ #
# If a Date is frozen but the memoized method hasn't been called, the
# first call will result in a frozen object error since the memo
- # instance variable is uninitialized. Work around by eagerly memoizing
- # before freezing.
- def freeze #:nodoc:
- self.class.private_instance_methods(false).each do |m|
- if m.to_s =~ /\A__\d+__\Z/
- instance_variable_set(:"@#{m}", [send(m)])
+ # instance variable is uninitialized.
+ #
+ # Work around by eagerly memoizing before freezing.
+ #
+ # Ruby 1.9 uses a preinitialized instance variable so it's unaffected.
+ # This hack is as close as we can get to feature detection:
+ begin
+ ::Date.today.freeze.jd
+ rescue => frozen_object_error
+ if frozen_object_error.message =~ /frozen/
+ def freeze #:nodoc:
+ self.class.private_instance_methods(false).each do |m|
+ if m.to_s =~ /\A__\d+__\Z/
+ instance_variable_set(:"@#{m}", [send(m)])
+ end
+ end
+
+ super
end
end
-
- super
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 b5180c9592..43d70c7013 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -20,18 +20,33 @@ module ActiveSupport #:nodoc:
def yesterday
::Date.today.yesterday
end
-
+
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
def tomorrow
::Date.today.tomorrow
end
-
+
# Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today.
def current
::Time.zone_default ? ::Time.zone.today : ::Date.today
end
end
-
+
+ # Tells whether the Date object's date lies in the past
+ def past?
+ self < ::Date.current
+ end
+
+ # Tells whether the Date object's date is today
+ def today?
+ self.to_date == ::Date.current # we need the to_date because of DateTime
+ end
+
+ # Tells whether the Date object's date lies in the future
+ def future?
+ self > ::Date.current
+ end
+
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then subtracts the specified number of seconds
def ago(seconds)
@@ -57,7 +72,7 @@ module ActiveSupport #:nodoc:
def end_of_day
to_time.end_of_day
end
-
+
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
@@ -65,7 +80,7 @@ module ActiveSupport #:nodoc:
plus_without_duration(other)
end
end
-
+
def minus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
plus_with_duration(-other)
@@ -73,8 +88,8 @@ module ActiveSupport #:nodoc:
minus_without_duration(other)
end
end
-
- # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
+
+ # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
def advance(options)
d = self
@@ -98,7 +113,7 @@ module ActiveSupport #:nodoc:
options[:day] || self.day
)
end
-
+
# Returns a new Date/DateTime representing the time a number of specified months ago
def months_ago(months)
advance(:months => -months)
@@ -161,7 +176,7 @@ module ActiveSupport #:nodoc:
days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
result = (self + 7).beginning_of_week + days_into_week[day]
self.acts_like?(:time) ? result.change(:hour => 0) : result
- end
+ end
# 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
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 155c961a91..0099431e9d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -7,7 +7,7 @@ module ActiveSupport #:nodoc:
module Calculations
def self.included(base) #:nodoc:
base.extend ClassMethods
-
+
base.class_eval do
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
@@ -19,6 +19,20 @@ module ActiveSupport #:nodoc:
def local_offset
::Time.local(2007).utc_offset.to_r / 86400
end
+
+ def current
+ ::Time.zone_default ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
+ end
+ end
+
+ # Tells whether the DateTime object's datetime lies in the past
+ def past?
+ self < ::DateTime.current
+ end
+
+ # Tells whether the DateTime object's datetime lies in the future
+ def future?
+ self > ::DateTime.current
end
# Seconds since midnight: DateTime.now.seconds_since_midnight
@@ -78,7 +92,7 @@ module ActiveSupport #:nodoc:
def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
-
+
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
# Example:
@@ -89,17 +103,17 @@ module ActiveSupport #:nodoc:
new_offset(0)
end
alias_method :getutc, :utc
-
+
# Returns true if offset == 0
def utc?
offset == 0
end
-
+
# Returns the offset value in seconds
def utc_offset
(offset * 86400).to_i
end
-
+
# Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
def compare_with_coercion(other)
other = other.comparable_time if other.respond_to?(:comparable_time)
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index e451e9933a..788f3a7e9e 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -64,8 +64,28 @@ module Enumerable
end
end
+ # Iterates over a collection, passing the current element *and* the
+ # +memo+ to the block. Handy for building up hashes or
+ # reducing collections down to one object. Examples:
+ #
+ # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } #=> {'foo' => 'FOO', 'bar' => 'BAR'}
+ #
+ # *Note* that you can't use immutable objects like numbers, true or false as
+ # the memo. You would think the following returns 120, but since the memo is
+ # never changed, it does not.
+ #
+ # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
+ #
+ def each_with_object(memo, &block)
+ returning memo do |m|
+ each do |element|
+ block.call(element, m)
+ end
+ end
+ end unless [].respond_to?(:each_with_object)
+
# Convert an enumerable to a hash. Examples:
- #
+ #
# people.index_by(&:login)
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 4d3cf5423f..f988eff3d9 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -28,7 +28,7 @@ module ActiveSupport #:nodoc:
rescue Errno::ENOENT
# No old permissions, write a temp file to determine the defaults
check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
- new(check_name, "w")
+ open(check_name, "w") { }
old_stat = stat(check_name)
unlink(check_name)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2c606b401b..50dc7c61fc 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -156,7 +156,7 @@ module ActiveSupport #:nodoc:
XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
attributes
)
- end
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index f26d01553d..949976d741 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -10,7 +10,7 @@ module ActiveSupport #:nodoc:
module Except
# Returns a new hash without the given keys.
def except(*keys)
- clone.except!(*keys)
+ dup.except!(*keys)
end
# Replaces the hash without the given keys.
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index 9c1fd274ac..c622554860 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -12,5 +12,132 @@ class Logger
end_eval
end
[:debug, :info, :error, :fatal].each {|level| define_around_helper(level) }
+end
-end \ No newline at end of file
+
+require 'logger'
+
+# Extensions to the built in Ruby logger.
+#
+# If you want to use the default log formatter as defined in the Ruby core, then you
+# will need to set the formatter for the logger as in:
+#
+# logger.formatter = Formatter.new
+#
+# You can then specify the datetime format, for example:
+#
+# logger.datetime_format = "%Y-%m-%d"
+#
+# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
+class Logger
+ # Set to false to disable the silencer
+ cattr_accessor :silencer
+ self.silencer = true
+
+ # Silences the logger for the duration of the block.
+ def silence(temporary_level = Logger::ERROR)
+ if silencer
+ begin
+ old_logger_level, self.level = level, temporary_level
+ yield self
+ ensure
+ self.level = old_logger_level
+ end
+ else
+ yield self
+ end
+ end
+
+ alias :old_datetime_format= :datetime_format=
+ # Logging date-time format (string passed to +strftime+). Ignored if the formatter
+ # does not respond to datetime_format=.
+ def datetime_format=(datetime_format)
+ formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
+ end
+
+ alias :old_datetime_format :datetime_format
+ # Get the logging datetime format. Returns nil if the formatter does not support
+ # datetime formatting.
+ def datetime_format
+ formatter.datetime_format if formatter.respond_to?(:datetime_format)
+ end
+
+ alias :old_formatter :formatter if method_defined?(:formatter)
+ # Get the current formatter. The default formatter is a SimpleFormatter which only
+ # displays the log message
+ def formatter
+ @formatter ||= SimpleFormatter.new
+ end
+
+ unless const_defined? :Formatter
+ class Formatter
+ Format = "%s, [%s#%d] %5s -- %s: %s\n"
+
+ attr_accessor :datetime_format
+
+ def initialize
+ @datetime_format = nil
+ end
+
+ def call(severity, time, progname, msg)
+ Format % [severity[0..0], format_datetime(time), $$, severity, progname,
+ msg2str(msg)]
+ end
+
+ private
+ def format_datetime(time)
+ if @datetime_format.nil?
+ time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
+ else
+ time.strftime(@datetime_format)
+ end
+ end
+
+ def msg2str(msg)
+ case msg
+ when ::String
+ msg
+ when ::Exception
+ "#{ msg.message } (#{ msg.class })\n" <<
+ (msg.backtrace || []).join("\n")
+ else
+ msg.inspect
+ end
+ end
+ end
+ end
+
+ # Simple formatter which only displays the message.
+ class SimpleFormatter < Logger::Formatter
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ "#{String === msg ? msg : msg.inspect}\n"
+ end
+ end
+
+ private
+ alias old_format_message format_message
+
+ # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
+ # We can't test RUBY_VERSION because some distributions don't keep Ruby
+ # and its standard library in sync, leading to installations of Ruby 1.8.2
+ # with Logger from 1.8.3 and vice versa.
+ if method_defined?(:formatter=)
+ def format_message(severity, timestamp, progname, msg)
+ formatter.call(severity, timestamp, progname, msg)
+ end
+ else
+ def format_message(severity, timestamp, msg, progname)
+ formatter.call(severity, timestamp, progname, msg)
+ end
+
+ attr_writer :formatter
+ public :formatter=
+
+ alias old_format_datetime format_datetime
+ def format_datetime(datetime) datetime end
+
+ alias old_msg2str msg2str
+ def msg2str(msg) msg end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 34fcbd124b..da8d28ec13 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -7,7 +7,17 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/loading'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/model_naming'
+require 'active_support/core_ext/module/synchronization'
+
+module ActiveSupport
+ module CoreExtensions
+ # Various extensions for the Ruby core Module class.
+ module Module
+ # Nothing here. Only defined for API documentation purposes.
+ end
+ end
+end
class Module
- include ActiveSupport::CoreExt::Module::ModelNaming
+ include ActiveSupport::CoreExtensions::Module
end
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index 1894e3b0a2..e640f64520 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,70 +1,74 @@
-class Module
- # Encapsulates the common pattern of:
- #
- # alias_method :foo_without_feature, :foo
- # alias_method :foo, :foo_with_feature
- #
- # With this, you simply do:
- #
- # alias_method_chain :foo, :feature
- #
- # And both aliases are set up for you.
- #
- # Query and bang methods (foo?, foo!) keep the same punctuation:
- #
- # alias_method_chain :foo?, :feature
- #
- # is equivalent to
- #
- # alias_method :foo_without_feature?, :foo?
- # alias_method :foo?, :foo_with_feature?
- #
- # so you can safely chain foo, foo?, and foo! with the same feature.
- def alias_method_chain(target, feature)
- # Strip out punctuation on predicates or bang methods since
- # e.g. target?_without_feature is not a valid method name.
- aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
- yield(aliased_target, punctuation) if block_given?
-
- with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
-
- alias_method without_method, target
- alias_method target, with_method
-
- case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
- end
- end
+module ActiveSupport
+ module CoreExtensions
+ module Module
+ # Encapsulates the common pattern of:
+ #
+ # alias_method :foo_without_feature, :foo
+ # alias_method :foo, :foo_with_feature
+ #
+ # With this, you simply do:
+ #
+ # alias_method_chain :foo, :feature
+ #
+ # And both aliases are set up for you.
+ #
+ # Query and bang methods (foo?, foo!) keep the same punctuation:
+ #
+ # alias_method_chain :foo?, :feature
+ #
+ # is equivalent to
+ #
+ # alias_method :foo_without_feature?, :foo?
+ # alias_method :foo?, :foo_with_feature?
+ #
+ # so you can safely chain foo, foo?, and foo! with the same feature.
+ def alias_method_chain(target, feature)
+ # Strip out punctuation on predicates or bang methods since
+ # e.g. target?_without_feature is not a valid method name.
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
+ yield(aliased_target, punctuation) if block_given?
+
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
- # Allows you to make aliases for attributes, which includes
- # getter, setter, and query methods.
- #
- # Example:
- #
- # class Content < ActiveRecord::Base
- # # has a title attribute
- # end
- #
- # class Email < Content
- # alias_attribute :subject, :title
- # end
- #
- # e = Email.find(1)
- # e.title # => "Superstars"
- # e.subject # => "Superstars"
- # e.subject? # => true
- # e.subject = "Megastars"
- # e.title # => "Megastars"
- def alias_attribute(new_name, old_name)
- module_eval <<-STR, __FILE__, __LINE__+1
- def #{new_name}; self.#{old_name}; end
- def #{new_name}?; self.#{old_name}?; end
- def #{new_name}=(v); self.#{old_name} = v; end
- STR
+ alias_method without_method, target
+ alias_method target, with_method
+
+ case
+ when public_method_defined?(without_method)
+ public target
+ when protected_method_defined?(without_method)
+ protected target
+ when private_method_defined?(without_method)
+ private target
+ end
+ end
+
+ # Allows you to make aliases for attributes, which includes
+ # getter, setter, and query methods.
+ #
+ # Example:
+ #
+ # class Content < ActiveRecord::Base
+ # # has a title attribute
+ # end
+ #
+ # class Email < Content
+ # alias_attribute :subject, :title
+ # end
+ #
+ # e = Email.find(1)
+ # e.title # => "Superstars"
+ # e.subject # => "Superstars"
+ # e.subject? # => true
+ # e.subject = "Megastars"
+ # e.title # => "Megastars"
+ def alias_attribute(new_name, old_name)
+ module_eval <<-STR, __FILE__, __LINE__+1
+ def #{new_name}; self.#{old_name}; end
+ def #{new_name}?; self.#{old_name}?; end
+ def #{new_name}=(v); self.#{old_name} = v; end
+ STR
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 45f3e4bf5c..8beaff4b82 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,86 +1,90 @@
-class Module
- # Returns the name of the module containing this one.
- #
- # p M::N.parent_name # => "M"
- def parent_name
- unless defined? @parent_name
- @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
- end
- @parent_name
- end
+module ActiveSupport
+ module CoreExtensions
+ module Module
+ # Returns the name of the module containing this one.
+ #
+ # p M::N.parent_name # => "M"
+ def parent_name
+ unless defined? @parent_name
+ @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ end
+ @parent_name
+ end
- # Returns the module which contains this one according to its name.
- #
- # module M
- # module N
- # end
- # end
- # X = M::N
- #
- # p M::N.parent # => M
- # p X.parent # => M
- #
- # The parent of top-level and anonymous modules is Object.
- #
- # p M.parent # => Object
- # p Module.new.parent # => Object
- #
- def parent
- parent_name ? parent_name.constantize : Object
- end
+ # Returns the module which contains this one according to its name.
+ #
+ # module M
+ # module N
+ # end
+ # end
+ # X = M::N
+ #
+ # p M::N.parent # => M
+ # p X.parent # => M
+ #
+ # The parent of top-level and anonymous modules is Object.
+ #
+ # p M.parent # => Object
+ # p Module.new.parent # => Object
+ #
+ def parent
+ parent_name ? parent_name.constantize : Object
+ end
- # Returns all the parents of this module according to its name, ordered from
- # nested outwards. The receiver is not contained within the result.
- #
- # module M
- # module N
- # end
- # end
- # X = M::N
- #
- # p M.parents # => [Object]
- # p M::N.parents # => [M, Object]
- # p X.parents # => [M, Object]
- #
- def parents
- parents = []
- if parent_name
- parts = parent_name.split('::')
- until parts.empty?
- parents << (parts * '::').constantize
- parts.pop
+ # Returns all the parents of this module according to its name, ordered from
+ # nested outwards. The receiver is not contained within the result.
+ #
+ # module M
+ # module N
+ # end
+ # end
+ # X = M::N
+ #
+ # p M.parents # => [Object]
+ # p M::N.parents # => [M, Object]
+ # p X.parents # => [M, Object]
+ #
+ def parents
+ parents = []
+ if parent_name
+ parts = parent_name.split('::')
+ until parts.empty?
+ parents << (parts * '::').constantize
+ parts.pop
+ end
+ end
+ parents << Object unless parents.include? Object
+ parents
end
- end
- parents << Object unless parents.include? Object
- parents
- end
- if RUBY_VERSION < '1.9'
- # Returns the constants that have been defined locally by this object and
- # not in an ancestor. This method is exact if running under Ruby 1.9. In
- # previous versions it may miss some constants if their definition in some
- # ancestor is identical to their definition in the receiver.
- def local_constants
- inherited = {}
+ if RUBY_VERSION < '1.9'
+ # Returns the constants that have been defined locally by this object and
+ # not in an ancestor. This method is exact if running under Ruby 1.9. In
+ # previous versions it may miss some constants if their definition in some
+ # ancestor is identical to their definition in the receiver.
+ def local_constants
+ inherited = {}
+
+ ancestors.each do |anc|
+ next if anc == self
+ anc.constants.each { |const| inherited[const] = anc.const_get(const) }
+ end
- ancestors.each do |anc|
- next if anc == self
- anc.constants.each { |const| inherited[const] = anc.const_get(const) }
+ constants.select do |const|
+ !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id
+ end
+ end
+ else
+ def local_constants #:nodoc:
+ constants(false)
+ end
end
- constants.select do |const|
- !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id
+ # Returns the names of the constants defined locally rather than the
+ # constants themselves. See <tt>local_constants</tt>.
+ def local_constant_names
+ local_constants.map { |c| c.to_s }
end
end
- else
- def local_constants #:nodoc:
- constants(false)
- end
- end
-
- # Returns the names of the constants defined locally rather than the
- # constants themselves. See <tt>local_constants</tt>.
- def local_constant_names
- local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/model_naming.rb b/activesupport/lib/active_support/core_ext/module/model_naming.rb
index 5518f5417b..3ec4f3ba11 100644
--- a/activesupport/lib/active_support/core_ext/module/model_naming.rb
+++ b/activesupport/lib/active_support/core_ext/module/model_naming.rb
@@ -11,12 +11,12 @@ module ActiveSupport
end
end
- module CoreExt
+ module CoreExtensions
module Module
- module ModelNaming
- def model_name
- @model_name ||= ModelName.new(name)
- end
+ # Returns an ActiveSupport::ModelName object for module. It can be
+ # used to retrieve all kinds of naming-related information.
+ def model_name
+ @model_name ||= ModelName.new(name)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
new file mode 100644
index 0000000000..251606024e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb
@@ -0,0 +1,39 @@
+class Module
+ # Synchronize access around a method, delegating synchronization to a
+ # particular mutex. A mutex (either a Mutex, or any object that responds to
+ # #synchronize and yields to a block) must be provided as a final :with option.
+ # The :with option should be a symbol or string, and can represent a method,
+ # constant, or instance or class variable.
+ # Example:
+ # class SharedCache
+ # @@lock = Mutex.new
+ # def expire
+ # ...
+ # end
+ # synchronize :expire, :with => :@@lock
+ # end
+ def synchronize(*methods)
+ options = methods.extract_options!
+ unless options.is_a?(Hash) && with = options[:with]
+ raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)."
+ end
+
+ methods.each do |method|
+ aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
+
+ if method_defined?("#{aliased_method}_without_synchronization#{punctuation}")
+ raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported."
+ end
+
+ module_eval(<<-EOS, __FILE__, __LINE__)
+ def #{aliased_method}_with_synchronization#{punctuation}(*args, &block)
+ #{with}.synchronize do
+ #{aliased_method}_without_synchronization#{punctuation}(*args, &block)
+ end
+ end
+ EOS
+
+ alias_method_chain method, :synchronization
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb
index 8384a12327..06a7d05702 100644
--- a/activesupport/lib/active_support/core_ext/object/misc.rb
+++ b/activesupport/lib/active_support/core_ext/object/misc.rb
@@ -1,9 +1,4 @@
class Object
- unless respond_to?(:send!)
- # Anticipating Ruby 1.9 neutering send
- alias send! send
- end
-
# A Ruby-ized realization of the K combinator, courtesy of Mikael Brockman.
#
# def foo
diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb
index 39dac85636..6fa1eb5bee 100644
--- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb
+++ b/activesupport/lib/active_support/core_ext/range/blockless_step.rb
@@ -8,7 +8,7 @@ module ActiveSupport #:nodoc:
end
if RUBY_VERSION < '1.9'
- def step_with_blockless(value, &block)
+ def step_with_blockless(value = 1, &block)
if block_given?
step_without_blockless(value, &block)
else
@@ -18,7 +18,7 @@ module ActiveSupport #:nodoc:
end
end
else
- def step_with_blockless(value, &block)
+ def step_with_blockless(value = 1, &block)
if block_given?
step_without_blockless(value, &block)
else
diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb
new file mode 100644
index 0000000000..187f3e0f5e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/rexml.rb
@@ -0,0 +1,36 @@
+require 'rexml/document'
+require 'rexml/entity'
+
+# Fixes the rexml vulnerability disclosed at:
+# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/
+# This fix is identical to rexml-expansion-fix version 1.0.1
+
+# Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION
+unless (defined?(REXML::VERSION) ? REXML::VERSION : REXML::Version) > "3.1.7.2"
+ module REXML
+ class Entity < Child
+ undef_method :unnormalized
+ def unnormalized
+ document.record_entity_expansion! if document
+ v = value()
+ return nil if v.nil?
+ @unnormalized = Text::unnormalize(v, parent)
+ @unnormalized
+ end
+ end
+ class Document < Element
+ @@entity_expansion_limit = 10_000
+ def self.entity_expansion_limit= val
+ @@entity_expansion_limit = val
+ end
+
+ def record_entity_expansion!
+ @number_of_expansions ||= 0
+ @number_of_expansions += 1
+ if @number_of_expansions > @@entity_expansion_limit
+ raise "Number of entity expansions exceeded, processing aborted."
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 25386af70a..7ff2f11eff 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/string/iterators'
require 'active_support/core_ext/string/unicode'
require 'active_support/core_ext/string/xchar'
require 'active_support/core_ext/string/filters'
+require 'active_support/core_ext/string/behavior'
class String #:nodoc:
include ActiveSupport::CoreExtensions::String::Access
@@ -15,4 +16,5 @@ class String #:nodoc:
include ActiveSupport::CoreExtensions::String::StartsEndsWith
include ActiveSupport::CoreExtensions::String::Iterators
include ActiveSupport::CoreExtensions::String::Unicode
+ include ActiveSupport::CoreExtensions::String::Behavior
end
diff --git a/activesupport/lib/active_support/core_ext/string/behavior.rb b/activesupport/lib/active_support/core_ext/string/behavior.rb
new file mode 100644
index 0000000000..a93ca3027f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/behavior.rb
@@ -0,0 +1,13 @@
+module ActiveSupport #:nodoc:
+ module CoreExtensions #:nodoc:
+ module String #:nodoc:
+ module Behavior
+ # Enable more predictable duck-typing on String-like classes. See
+ # Object#acts_like?.
+ def acts_like_string?
+ true
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 3bbad7dad8..de99fe5791 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -87,6 +87,25 @@ module ActiveSupport #:nodoc:
Inflector.demodulize(self)
end
+ # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
+ #
+ # ==== Examples
+ #
+ # class Person
+ # def to_param
+ # "#{id}-#{name.parameterize}"
+ # end
+ # end
+ #
+ # @person = Person.find(1)
+ # # => #<Person id: 1, name: "Donald E. Knuth">
+ #
+ # <%= link_to(@person.name, person_path %>
+ # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
+ def parameterize
+ Inflector.parameterize(self)
+ end
+
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index ea50511a96..78bbfc917c 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,11 +1,32 @@
require 'date'
require 'time'
-# Ruby 1.8-cvs and 1.9 define private Time#to_date
class Time
+ # Ruby 1.8-cvs and 1.9 define private Time#to_date
%w(to_date to_datetime).each do |method|
public method if private_instance_methods.include?(method)
end
+
+ # Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are
+ # unmarshaled in the local zone, instead of utc. We're layering behavior on the _dump and _load
+ # methods so that utc instances can be flagged on dump, and coerced back to utc on load.
+ if RUBY_VERSION < '1.9'
+ class << self
+ alias_method :_original_load, :_load
+ def _load(marshaled_time)
+ time = _original_load(marshaled_time)
+ utc = time.instance_variable_get('@marshal_with_utc_coercion')
+ utc ? time.utc : time
+ end
+ end
+
+ alias_method :_original_dump, :_dump
+ def _dump(*args)
+ obj = self.frozen? ? self.dup : self
+ obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
+ obj._original_dump(*args)
+ end
+ end
end
require 'active_support/core_ext/time/behavior'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index cd234c9b89..3cc6d59907 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -9,13 +9,13 @@ module ActiveSupport #:nodoc:
base.class_eval do
alias_method :plus_without_duration, :+
alias_method :+, :plus_with_duration
-
+
alias_method :minus_without_duration, :-
alias_method :-, :minus_with_duration
-
+
alias_method :minus_without_coercion, :-
alias_method :-, :minus_with_coercion
-
+
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
end
@@ -28,9 +28,9 @@ module ActiveSupport #:nodoc:
def ===(other)
other.is_a?(::Time)
end
-
- # Return the number of days in the given month.
- # If no year is specified, it will use the current year.
+
+ # Return the number of days in the given month.
+ # If no year is specified, it will use the current year.
def days_in_month(month, year = now.year)
return 29 if month == 2 && ::Date.gregorian_leap?(year)
COMMON_YEAR_DAYS_IN_MONTH[month]
@@ -57,6 +57,21 @@ module ActiveSupport #:nodoc:
end
end
+ # Tells whether the Time object's time lies in the past
+ def past?
+ self < ::Time.current
+ end
+
+ # Tells whether the Time object's time is today
+ def today?
+ self.to_date == ::Date.current
+ end
+
+ # Tells whether the Time object's time lies in the future
+ def future?
+ self > ::Time.current
+ end
+
# Seconds since midnight: Time.now.seconds_since_midnight
def seconds_since_midnight
self.to_i - self.change(:hour => 0).to_i + (self.usec/1.0e+6)
@@ -106,7 +121,7 @@ module ActiveSupport #:nodoc:
(seconds.abs >= 86400 && initial_dst != final_dst) ? f + (initial_dst - final_dst).hours : f
end
rescue
- self.to_datetime.since(seconds)
+ self.to_datetime.since(seconds)
end
alias :in :since
@@ -199,7 +214,7 @@ module ActiveSupport #:nodoc:
change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 0)
end
alias :at_end_of_month :end_of_month
-
+
# Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
def beginning_of_quarter
beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
@@ -208,7 +223,7 @@ module ActiveSupport #:nodoc:
# Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59)
def end_of_quarter
- change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
+ beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
@@ -249,7 +264,7 @@ module ActiveSupport #:nodoc:
minus_without_duration(other)
end
end
-
+
# Time#- can also be used to determine the number of seconds between two Time instances.
# We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
# are coerced into values that Time#- will recognize
@@ -257,7 +272,7 @@ module ActiveSupport #:nodoc:
other = other.comparable_time if other.respond_to?(:comparable_time)
minus_without_coercion(other)
end
-
+
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
# can be chronologically compared with a Time
def compare_with_coercion(other)
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 079ecdd48e..9d8eb73908 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -78,7 +78,7 @@ module ActiveSupport #:nodoc:
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.send!(:get_zone, zone))
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 01eb5df593..950bca60a6 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -109,7 +109,7 @@ module ActiveSupport
end
def deprecation_horizon
- '2.0'
+ '2.3'
end
end
@@ -162,6 +162,22 @@ module ActiveSupport
end
end
+ class DeprecatedObjectProxy < DeprecationProxy
+ def initialize(object, message)
+ @object = object
+ @message = message
+ end
+
+ private
+ def target
+ @object
+ end
+
+ def warn(callstack, called, args)
+ ActiveSupport::Deprecation.warn(@message, callstack)
+ end
+ end
+
# Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc.
# which emits deprecation warnings on any method call (except +inspect+).
class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc:
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 7ae9e0c6ab..8a917a9eb2 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -240,6 +240,25 @@ module ActiveSupport
def demodulize(class_name_in_module)
class_name_in_module.to_s.gsub(/^.*::/, '')
end
+
+ # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
+ #
+ # ==== Examples
+ #
+ # class Person
+ # def to_param
+ # "#{id}-#{name.parameterize}"
+ # end
+ # end
+ #
+ # @person = Person.find(1)
+ # # => #<Person id: 1, name: "Donald E. Knuth">
+ #
+ # <%= link_to(@person.name, person_path %>
+ # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
+ def parameterize(string, sep = '-')
+ string.chars.normalize(:kd).to_s.gsub(/[^\x00-\x7F]+/, '').gsub(/[^a-z0-9_\-]+/i, sep).downcase
+ end
# Create the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index 6506238ac0..4786fd6e0b 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -60,7 +60,7 @@ module ActiveSupport
#{memoized_ivar} ||= {} unless frozen?
reload = args.pop if args.last == true || args.last == :reload
- if #{memoized_ivar}
+ if defined?(#{memoized_ivar}) && #{memoized_ivar}
if !reload && #{memoized_ivar}.has_key?(args)
#{memoized_ivar}[args]
elsif #{memoized_ivar}
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 185d03020c..de2c83f8d1 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -49,6 +49,11 @@ module ActiveSupport::Multibyte #:nodoc:
false
end
+ # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
+ def acts_like_string?
+ true
+ end
+
# Create a new Chars instance.
def initialize(str)
@string = str.respond_to?(:string) ? str.string : str
diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index c77bca1ac9..b563b093ed 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -11,7 +11,7 @@ module ActiveSupport
private
def method_missing(method, *arguments, &block)
arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
- @context.send!(method, *arguments, &block)
+ @context.__send__(method, *arguments, &block)
end
end
end
diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb
new file mode 100644
index 0000000000..688165f9a3
--- /dev/null
+++ b/activesupport/lib/active_support/secure_random.rb
@@ -0,0 +1,197 @@
+begin
+ require 'openssl'
+rescue LoadError
+end
+
+begin
+ require 'securerandom'
+rescue LoadError
+end
+
+module ActiveSupport
+ if defined?(::SecureRandom)
+ # Use Ruby 1.9's SecureRandom library whenever possible.
+ 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
+ # SecureRandom.random_bytes 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
+
+ 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
+
+ if !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
+
+ # SecureRandom.hex 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
+
+ # SecureRandom.base64 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
+
+ # SecureRandom.random_number 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].ord
+ mask |= mask >> 1
+ mask |= mask >> 2
+ mask |= mask >> 4
+ begin
+ rnd = SecureRandom.random_bytes(bin.length)
+ rnd[0] = (rnd[0].ord & mask).chr
+ 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
+end
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index 65545748df..cd722a3cfb 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -1,4 +1,14 @@
module ActiveSupport
+ # Wrapping a string in this class gives you a prettier way to test
+ # for equality. The value returned by <tt>Rails.env</tt> is wrapped
+ # in a StringInquirer object so instead of calling this:
+ #
+ # Rails.env == "production"
+ #
+ # you can call this:
+ #
+ # Rails.env.production?
+ #
class StringInquirer < String
def method_missing(method_name, *arguments)
if method_name.to_s.ends_with?("?")
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 0f531b0c79..197e73b3e8 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -12,7 +12,13 @@ module ActiveSupport
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
defined = instance_method(test_name) rescue false
raise "#{test_name} is already defined in #{self}" if defined
- define_method(test_name, &block)
+ if block_given?
+ define_method(test_name, &block)
+ else
+ define_method(test_name) do
+ flunk "No implementation provided for #{name}"
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
index 70a44eab8c..63d1ba6507 100644
--- a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
+++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
@@ -36,7 +36,11 @@ module Test
# post :delete, :id => ...
# end
def assert_difference(expressions, difference = 1, message = nil, &block)
- expression_evaluations = Array(expressions).collect{ |expression| lambda { eval(expression, block.send!(:binding)) } }
+ expression_evaluations = Array(expressions).map do |expression|
+ lambda do
+ eval(expression, block.__send__(:binding))
+ end
+ end
original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call }
yield
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 4866fa0dc8..b7b8807c6d 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,6 @@
require 'tzinfo'
module ActiveSupport
- # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
+ # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
# limited to UTC and the system's <tt>ENV['TZ']</tt> zone.
#
# You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> -- instead, Rails provides the methods
@@ -32,12 +32,12 @@ module ActiveSupport
class TimeWithZone
include Comparable
attr_reader :time_zone
-
+
def initialize(utc_time, time_zone, local_time = nil, period = nil)
@utc, @time_zone, @time = utc_time, time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time
end
-
+
# Returns a Time or DateTime instance that represents the time in +time_zone+.
def time
@time ||= period.to_local(@utc)
@@ -51,7 +51,7 @@ module ActiveSupport
alias_method :getgm, :utc
alias_method :getutc, :utc
alias_method :gmtime, :utc
-
+
# Returns the underlying TZInfo::TimezonePeriod.
def period
@period ||= time_zone.period_for_utc(@utc)
@@ -62,38 +62,38 @@ module ActiveSupport
return self if time_zone == new_zone
utc.in_time_zone(new_zone)
end
-
+
# Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone
def localtime
utc.getlocal
end
alias_method :getlocal, :localtime
-
+
def dst?
period.dst?
end
alias_method :isdst, :dst?
-
+
def utc?
time_zone.name == 'UTC'
end
alias_method :gmt?, :utc?
-
+
def utc_offset
period.utc_total_offset
end
alias_method :gmt_offset, :utc_offset
alias_method :gmtoff, :utc_offset
-
+
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || utc_offset.to_utc_offset_s(colon)
end
-
+
# Time uses +zone+ to display the time zone abbreviation, so we're duck-typing it.
def zone
period.zone_identifier.to_s
end
-
+
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
@@ -122,7 +122,7 @@ module ActiveSupport
%("#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}")
end
end
-
+
def to_yaml(options = {})
if options.kind_of?(YAML::Emitter)
utc.to_yaml(options)
@@ -130,19 +130,19 @@ module ActiveSupport
time.to_yaml(options).gsub('Z', formatted_offset(true, 'Z'))
end
end
-
+
def httpdate
utc.httpdate
end
-
+
def rfc2822
to_s(:rfc822)
end
alias_method :rfc822, :rfc2822
-
+
# <tt>:db</tt> format outputs time in UTC; all others output time in local.
# Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
- def to_s(format = :default)
+ def to_s(format = :default)
return utc.to_s(format) if format == :db
if formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
@@ -150,27 +150,39 @@ module ActiveSupport
"#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
end
end
-
+
# Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and +formatted_offset+, respectively, before passing to
# Time#strftime, so that zone information is correct
def strftime(format)
format = format.gsub('%Z', zone).gsub('%z', formatted_offset(false))
time.strftime(format)
end
-
+
# Use the time in UTC for comparisons.
def <=>(other)
utc <=> other
end
-
+
def between?(min, max)
utc.between?(min, max)
end
-
+
+ def past?
+ utc.past?
+ end
+
+ def today?
+ time.today?
+ end
+
+ def future?
+ utc.future?
+ end
+
def eql?(other)
utc == other
end
-
+
def +(other)
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
@@ -194,7 +206,7 @@ module ActiveSupport
result.in_time_zone(time_zone)
end
end
-
+
def since(other)
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
@@ -204,7 +216,7 @@ module ActiveSupport
utc.since(other).in_time_zone(time_zone)
end
end
-
+
def ago(other)
since(-other)
end
@@ -218,53 +230,53 @@ module ActiveSupport
utc.advance(options).in_time_zone(time_zone)
end
end
-
- %w(year mon month day mday hour min sec).each do |method_name|
+
+ %w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
class_eval <<-EOV
def #{method_name}
time.#{method_name}
end
EOV
end
-
+
def usec
time.respond_to?(:usec) ? time.usec : 0
end
-
+
def to_a
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
-
+
def to_f
utc.to_f
- end
-
+ end
+
def to_i
utc.to_i
end
alias_method :hash, :to_i
alias_method :tv_sec, :to_i
-
+
# A TimeWithZone acts like a Time, so just return +self+.
def to_time
self
end
-
+
def to_datetime
utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
-
+
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
true
end
-
+
# Say we're a Time to thwart type checking.
def is_a?(klass)
klass == ::Time || super
end
alias_method :kind_of?, :is_a?
-
+
# Neuter freeze because freezing can cause problems with lazy loading of attributes.
def freeze
self
@@ -273,9 +285,9 @@ module ActiveSupport
def marshal_dump
[utc, time_zone.name, time]
end
-
+
def marshal_load(variables)
- initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc)
+ initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
@@ -290,10 +302,10 @@ module ActiveSupport
result = time.__send__(sym, *args, &block)
result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
end
-
- private
+
+ private
def get_period_and_ensure_valid_local_time
- # we don't want a Time.local instance enforcing its own DST rules as well,
+ # we don't want a Time.local instance enforcing its own DST rules as well,
# so transfer time values to a utc constructor if necessary
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
begin
@@ -304,11 +316,11 @@ module ActiveSupport
retry
end
end
-
+
def transfer_time_values_to_utc_constructor(time)
::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
end
-
+
def duration_of_variable_length?(obj)
ActiveSupport::Duration === obj && obj.parts.flatten.detect {|p| [:years, :months, :days].include? p }
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 788d40bfa8..a294dd0ba3 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -124,7 +124,7 @@ module ActiveSupport
"Kathmandu" => "Asia/Katmandu",
"Astana" => "Asia/Dhaka",
"Dhaka" => "Asia/Dhaka",
- "Sri Jayawardenepura" => "Asia/Dhaka",
+ "Sri Jayawardenepura" => "Asia/Colombo",
"Almaty" => "Asia/Almaty",
"Novosibirsk" => "Asia/Novosibirsk",
"Rangoon" => "Asia/Rangoon",
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
index 9e347d94e9..344c77aecf 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
@@ -10,7 +10,8 @@ require 'i18n/exceptions'
module I18n
@@backend = nil
- @@default_locale = 'en-US'
+ @@load_path = nil
+ @@default_locale = :'en-US'
@@exception_handler = :default_exception_handler
class << self
@@ -49,26 +50,22 @@ module I18n
@@exception_handler = exception_handler
end
- # Allow client libraries to pass a block that populates the translation
- # storage. Decoupled for backends like a db backend that persist their
- # translations, so the backend can decide whether/when to yield or not.
- def populate(&block)
- backend.populate(&block)
- end
-
- # Allows client libraries to pass arguments that specify a source for
- # translation data to be loaded by the backend. The backend defines
- # acceptable sources.
+ # Allow clients to register paths providing translation data sources. The
+ # backend defines acceptable sources.
+ #
# E.g. the provided SimpleBackend accepts a list of paths to translation
# files which are either named *.rb and contain plain Ruby Hashes or are
- # named *.yml and contain YAML data.)
- def load_translations(*args)
- backend.load_translations(*args)
+ # named *.yml and contain YAML data. So for the SimpleBackend clients may
+ # register translation files like this:
+ # I18n.load_path << 'path/to/locale/en-US.yml'
+ def load_path
+ @@load_path ||= []
end
-
- # Stores translations for the given locale in the backend.
- def store_translations(locale, data)
- backend.store_translations locale, data
+
+ # Sets the load path instance. Custom implementations are expected to
+ # behave like a Ruby Array.
+ def load_path=(load_path)
+ @@load_path = load_path
end
# Translates, pluralizes and interpolates a given key using a given locale,
@@ -187,6 +184,4 @@ module I18n
keys.flatten.map{|k| k.to_sym}
end
end
-end
-
-
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb
index a53f7fe772..2dbaf8a405 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb
@@ -1,15 +1,11 @@
-require 'strscan'
+require 'yaml'
module I18n
module Backend
class Simple
- # Allow client libraries to pass a block that populates the translation
- # storage. Decoupled for backends like a db backend that persist their
- # translations, so the backend can decide whether/when to yield or not.
- def populate(&block)
- yield
- end
-
+ INTERPOLATION_RESERVED_KEYS = %w(scope default)
+ MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
+
# Accepts a list of paths to translation files. Loads translations from
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
# for details.
@@ -47,12 +43,15 @@ module I18n
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
type = object.respond_to?(:sec) ? 'time' : 'date'
+ # TODO only translate these if format is a String?
formats = translate(locale, :"#{type}.formats")
format = formats[format.to_sym] if formats && formats[format.to_sym]
# TODO raise exception unless format found?
format = format.to_s.dup
- format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
+ # TODO only translate these if the format string is actually present
+ # TODO check which format strings are present, then bulk translate then, then replace them
+ format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
@@ -60,7 +59,16 @@ module I18n
object.strftime(format)
end
+ def initialized?
+ @initialized ||= false
+ end
+
protected
+
+ def init_translations
+ load_translations(*I18n.load_path)
+ @initialized = true
+ end
def translations
@translations ||= {}
@@ -73,6 +81,7 @@ module I18n
# <tt>%w(currency format)</tt>.
def lookup(locale, key, scope = [])
return unless key
+ init_translations unless initialized?
keys = I18n.send :normalize_translation_keys, locale, key, scope
keys.inject(translations){|result, k| result[k.to_sym] or return nil }
end
@@ -95,7 +104,7 @@ module I18n
rescue MissingTranslationData
nil
end
-
+
# Picks a translation from an array according to English pluralization
# rules. It will pick the first translation if count is not equal to 1
# and the second translation if it is equal to 1. Other backends can
@@ -108,7 +117,7 @@ module I18n
raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
entry[key]
end
-
+
# Interpolates values into a given string.
#
# interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
@@ -118,29 +127,27 @@ module I18n
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
# interpolation).
def interpolate(locale, string, values = {})
- return string if !string.is_a?(String)
+ return string unless string.is_a?(String)
- string = string.gsub(/%d/, '{{count}}').gsub(/%s/, '{{value}}')
if string.respond_to?(:force_encoding)
- original_encoding = string.encoding
- string.force_encoding(Encoding::BINARY)
- end
- s = StringScanner.new(string)
-
- while s.skip_until(/\{\{/)
- s.string[s.pos - 3, 1] = '' and next if s.pre_match[-1, 1] == '\\'
- start_pos = s.pos - 2
- key = s.scan_until(/\}\}/)[0..-3]
- end_pos = s.pos - 1
+ original_encoding = string.encoding
+ string.force_encoding(Encoding::BINARY)
+ end
- raise ReservedInterpolationKey.new(key, string) if %w(scope default).include?(key)
- raise MissingInterpolationArgument.new(key, string) unless values.has_key? key.to_sym
+ result = string.gsub(MATCH) do
+ escaped, pattern, key = $1, $2, $2.to_sym
- s.string[start_pos..end_pos] = values[key.to_sym].to_s
- s.unscan
+ if escaped
+ pattern
+ elsif INTERPOLATION_RESERVED_KEYS.include?(pattern)
+ raise ReservedInterpolationKey.new(pattern, string)
+ elsif !values.include?(key)
+ raise MissingInterpolationArgument.new(pattern, string)
+ else
+ values[key].to_s
+ end
end
-
- result = s.string
+
result.force_encoding(original_encoding) if original_encoding
result
end
diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Colombo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Colombo.rb
new file mode 100644
index 0000000000..f6531fa819
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Colombo.rb
@@ -0,0 +1,30 @@
+require 'tzinfo/timezone_definition'
+
+module TZInfo
+ module Definitions
+ module Asia
+ module Colombo
+ include TimezoneDefinition
+
+ timezone 'Asia/Colombo' do |tz|
+ tz.offset :o0, 19164, 0, :LMT
+ tz.offset :o1, 19172, 0, :MMT
+ tz.offset :o2, 19800, 0, :IST
+ tz.offset :o3, 19800, 1800, :IHST
+ tz.offset :o4, 19800, 3600, :IST
+ tz.offset :o5, 23400, 0, :LKT
+ tz.offset :o6, 21600, 0, :LKT
+
+ tz.transition 1879, 12, :o1, 17335550003, 7200
+ tz.transition 1905, 12, :o2, 52211763607, 21600
+ tz.transition 1942, 1, :o3, 116657485, 48
+ tz.transition 1942, 8, :o4, 9722413, 4
+ tz.transition 1945, 10, :o2, 38907909, 16
+ tz.transition 1996, 5, :o5, 832962600
+ tz.transition 1996, 10, :o6, 846266400
+ tz.transition 2006, 4, :o2, 1145039400
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb
index 6319c09210..28dd34334f 100644
--- a/activesupport/test/buffered_logger_test.rb
+++ b/activesupport/test/buffered_logger_test.rb
@@ -134,6 +134,7 @@ class BufferedLoggerTest < Test::Unit::TestCase
a.join
b.join
- assert_equal "a\nb\nc\nx\ny\nz\n", @output.string
+ assert @output.string.include?("a\nb\nc\n")
+ assert @output.string.include?("x\ny\nz\n")
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 0f3cf4c75c..b53c754780 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -210,6 +210,29 @@ class DateExtCalculationsTest < Test::Unit::TestCase
end
end
+ uses_mocha 'past?, today? and future?' do
+ def test_today
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, Date.new(1999, 12, 31).today?
+ assert_equal true, Date.new(2000,1,1).today?
+ assert_equal false, Date.new(2000,1,2).today?
+ end
+
+ def test_past
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal true, Date.new(1999, 12, 31).past?
+ assert_equal false, Date.new(2000,1,1).past?
+ assert_equal false, Date.new(2000,1,2).past?
+ end
+
+ def test_future
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, Date.new(1999, 12, 31).future?
+ assert_equal false, Date.new(2000,1,1).future?
+ assert_equal true, Date.new(2000,1,2).future?
+ end
+ end
+
uses_mocha 'TestDateCurrent' do
def test_current_returns_date_today_when_zone_default_not_set
with_env_tz 'US/Central' do
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 854a3a05e1..be3cd8b5d6 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -207,6 +207,81 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_match(/^2080-02-28T15:15:10-06:?00$/, DateTime.civil(2080, 2, 28, 15, 15, 10, -0.25).xmlschema)
end
+ uses_mocha 'Test DateTime past?, today? and future?' do
+ def test_today_with_offset
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today?
+ assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today?
+ end
+
+ def test_today_without_offset
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, DateTime.civil(1999,12,31,23,59,59).today?
+ assert_equal true, DateTime.civil(2000,1,1,0).today?
+ assert_equal true, DateTime.civil(2000,1,1,23,59,59).today?
+ assert_equal false, DateTime.civil(2000,1,2,0).today?
+ end
+
+ def test_past_with_offset
+ DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
+ assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past?
+ end
+
+ def test_past_without_offset
+ DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
+ assert_equal true, DateTime.civil(2005,2,10,20,30,44).past?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,45).past?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,46).past?
+ end
+
+ def test_future_with_offset
+ DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
+ assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future?
+ assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future?
+ end
+
+ def test_future_without_offset
+ DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
+ assert_equal false, DateTime.civil(2005,2,10,20,30,44).future?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,45).future?
+ assert_equal true, DateTime.civil(2005,2,10,20,30,46).future?
+ end
+ end
+
+ uses_mocha 'TestDateTimeCurrent' do
+ def test_current_returns_date_today_when_zone_default_not_set
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
+ end
+
+ def test_current_returns_time_zone_today_when_zone_default_set
+ Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
+ ensure
+ Time.zone_default = nil
+ end
+ end
+
+ def test_current_without_time_zone
+ assert DateTime.current.is_a?(DateTime)
+ end
+
+ def test_current_with_time_zone
+ with_env_tz 'US/Eastern' do
+ assert DateTime.current.is_a?(DateTime)
+ end
+ end
+
def test_acts_like_time
assert DateTime.new.acts_like_time?
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 2315d8f3db..deb9b7544d 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -58,6 +58,11 @@ class EnumerableTests < Test::Unit::TestCase
assert_equal Payment.new(0), [].sum(Payment.new(0))
end
+ def test_each_with_object
+ result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
+ assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result)
+ end
+
def test_index_by
payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ]
assert_equal({ 5 => payments[0], 15 => payments[1], 10 => payments[2] },
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index fc8ed45358..44d48e7577 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -62,7 +62,7 @@ class HashExtTest < Test::Unit::TestCase
@symbols = @symbols.with_indifferent_access
@mixed = @mixed.with_indifferent_access
- assert_equal 'a', @strings.send!(:convert_key, :a)
+ assert_equal 'a', @strings.__send__(:convert_key, :a)
assert_equal 1, @strings.fetch('a')
assert_equal 1, @strings.fetch(:a.to_s)
@@ -75,9 +75,9 @@ class HashExtTest < Test::Unit::TestCase
hashes.each do |name, hash|
method_map.sort_by { |m| m.to_s }.each do |meth, expected|
- assert_equal(expected, hash.send!(meth, 'a'),
+ assert_equal(expected, hash.__send__(meth, 'a'),
"Calling #{name}.#{meth} 'a'")
- assert_equal(expected, hash.send!(meth, :a),
+ assert_equal(expected, hash.__send__(meth, :a),
"Calling #{name}.#{meth} :a")
end
end
@@ -341,6 +341,20 @@ class HashExtTest < Test::Unit::TestCase
assert_equal expected, original.except!(:c)
assert_equal expected, original
end
+
+ def test_except_with_original_frozen
+ original = { :a => 'x', :b => 'y' }
+ original.freeze
+ assert_nothing_raised { original.except(:a) }
+ end
+
+ uses_mocha 'except with expectation' do
+ def test_except_with_mocha_expectation_on_original
+ original = { :a => 'x', :b => 'y' }
+ original.expects(:delete).never
+ original.except(:a)
+ end
+ end
end
class IWriteMyOwnXML
@@ -733,7 +747,7 @@ class HashToXmlTest < Test::Unit::TestCase
def test_empty_string_works_for_typecast_xml_value
assert_nothing_raised do
- Hash.send!(:typecast_xml_value, "")
+ Hash.__send__(:typecast_xml_value, "")
end
end
@@ -839,6 +853,27 @@ class QueryTest < Test::Unit::TestCase
:person => {:id => [20, 10]}
end
+ def test_expansion_count_is_limited
+ assert_raises RuntimeError do
+ attack_xml = <<-EOT
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
+ <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
+ <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+ ]>
+ <member>
+ &a;
+ </member>
+ EOT
+ Hash.from_xml(attack_xml)
+ end
+ end
+
private
def assert_query_equal(expected, actual, message = nil)
assert_equal expected.split('&'), actual.to_query.split('&')
diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb
new file mode 100644
index 0000000000..b1d4bc5e06
--- /dev/null
+++ b/activesupport/test/core_ext/module/synchronization_test.rb
@@ -0,0 +1,85 @@
+require 'abstract_unit'
+
+class SynchronizationTest < Test::Unit::TestCase
+ def setup
+ @target = Class.new
+ @target.cattr_accessor :mutex, :instance_writer => false
+ @target.mutex = Mutex.new
+ @instance = @target.new
+ end
+
+ def test_synchronize_aliases_method_chain_with_synchronize
+ @target.module_eval do
+ attr_accessor :value
+ synchronize :value, :with => :mutex
+ end
+ assert @instance.respond_to?(:value_with_synchronization)
+ assert @instance.respond_to?(:value_without_synchronization)
+ end
+
+ def test_synchronize_does_not_change_behavior
+ @target.module_eval do
+ attr_accessor :value
+ synchronize :value, :with => :mutex
+ end
+ expected = "some state"
+ @instance.value = expected
+ assert_equal expected, @instance.value
+ end
+
+ def test_synchronize_with_no_mutex_raises_an_argument_error
+ assert_raises(ArgumentError) do
+ @target.synchronize :to_s
+ end
+ end
+
+ def test_double_synchronize_raises_an_argument_error
+ @target.synchronize :to_s, :with => :mutex
+ assert_raises(ArgumentError) do
+ @target.synchronize :to_s, :with => :mutex
+ end
+ end
+
+ def dummy_sync
+ dummy = Object.new
+ def dummy.synchronize
+ @sync_count ||= 0
+ @sync_count += 1
+ yield
+ end
+ def dummy.sync_count; @sync_count; end
+ dummy
+ end
+
+ def test_mutex_is_entered_during_method_call
+ @target.mutex = dummy_sync
+ @target.synchronize :to_s, :with => :mutex
+ @instance.to_s
+ @instance.to_s
+ assert_equal 2, @target.mutex.sync_count
+ end
+
+ def test_can_synchronize_method_with_punctuation
+ @target.module_eval do
+ def dangerous?
+ @dangerous
+ end
+ def dangerous!
+ @dangerous = true
+ end
+ end
+ @target.synchronize :dangerous?, :dangerous!, :with => :mutex
+ @instance.dangerous!
+ assert @instance.dangerous?
+ end
+
+ def test_can_synchronize_singleton_methods
+ @target.mutex = dummy_sync
+ class << @target
+ synchronize :to_s, :with => :mutex
+ end
+ assert @target.respond_to?(:to_s_without_synchronization)
+ assert_nothing_raised { @target.to_s; @target.to_s }
+ assert_equal 2, @target.mutex.sync_count
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
index b0a746fdc7..e88dcb52d5 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -108,11 +108,6 @@ class ClassExtTest < Test::Unit::TestCase
end
class ObjectTests < Test::Unit::TestCase
- def test_send_bang_aliases_send_before_19
- assert_respond_to 'a', :send!
- assert_equal 1, 'a'.send!(:size)
- end
-
def test_suppress_re_raises
assert_raises(LoadError) { suppress(ArgumentError) {raise LoadError} }
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index c9f959ef32..c0decf2c3f 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -201,3 +201,9 @@ class StringInflectionsTest < Test::Unit::TestCase
end
end
end
+
+class StringBehaviourTest < Test::Unit::TestCase
+ def test_acts_like_string
+ assert 'Bambi'.acts_like_string?
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 8740497b3d..8ceaedc7f4 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -117,6 +117,7 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,3,31,0,0,0).end_of_quarter
assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,21,10,10,10).end_of_quarter
assert_equal Time.local(2007,6,30,23,59,59), Time.local(2007,4,1,0,0,0).end_of_quarter
+ assert_equal Time.local(2008,6,30,23,59,59), Time.local(2008,5,31,0,0,0).end_of_quarter
end
def test_end_of_year
@@ -524,13 +525,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0, 0)
- assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
# This won't overflow on 64bit linux
- expected_to_overflow = Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1)
- unless expected_to_overflow.is_a?(Time)
+ unless time_is_64bits?
+ assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
@@ -546,7 +546,10 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
def test_local_time
assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
- assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+
+ unless time_is_64bits?
+ assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+ end
end
def test_next_month_on_31st
@@ -561,6 +564,74 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
assert_nothing_raised { Time.now.xmlschema }
end
+ uses_mocha 'Test Time past?, today? and future?' do
+ def test_today_with_time_local
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, Time.local(1999,12,31,23,59,59).today?
+ assert_equal true, Time.local(2000,1,1,0).today?
+ assert_equal true, Time.local(2000,1,1,23,59,59).today?
+ assert_equal false, Time.local(2000,1,2,0).today?
+ end
+
+ def test_today_with_time_utc
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, Time.utc(1999,12,31,23,59,59).today?
+ assert_equal true, Time.utc(2000,1,1,0).today?
+ assert_equal true, Time.utc(2000,1,1,23,59,59).today?
+ assert_equal false, Time.utc(2000,1,2,0).today?
+ end
+
+ def test_past_with_time_current_as_time_local
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
+ assert_equal true, Time.local(2005,2,10,15,30,44).past?
+ assert_equal false, Time.local(2005,2,10,15,30,45).past?
+ assert_equal false, Time.local(2005,2,10,15,30,46).past?
+ assert_equal true, Time.utc(2005,2,10,20,30,44).past?
+ assert_equal false, Time.utc(2005,2,10,20,30,45).past?
+ assert_equal false, Time.utc(2005,2,10,20,30,46).past?
+ end
+ end
+
+ def test_past_with_time_current_as_time_with_zone
+ with_env_tz 'US/Eastern' do
+ twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)')
+ Time.stubs(:current).returns(twz)
+ assert_equal true, Time.local(2005,2,10,10,30,44).past?
+ assert_equal false, Time.local(2005,2,10,10,30,45).past?
+ assert_equal false, Time.local(2005,2,10,10,30,46).past?
+ assert_equal true, Time.utc(2005,2,10,15,30,44).past?
+ assert_equal false, Time.utc(2005,2,10,15,30,45).past?
+ assert_equal false, Time.utc(2005,2,10,15,30,46).past?
+ end
+ end
+
+ def test_future_with_time_current_as_time_local
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
+ assert_equal false, Time.local(2005,2,10,15,30,44).future?
+ assert_equal false, Time.local(2005,2,10,15,30,45).future?
+ assert_equal true, Time.local(2005,2,10,15,30,46).future?
+ assert_equal false, Time.utc(2005,2,10,20,30,44).future?
+ assert_equal false, Time.utc(2005,2,10,20,30,45).future?
+ assert_equal true, Time.utc(2005,2,10,20,30,46).future?
+ end
+ end
+
+ def test_future_with_time_current_as_time_with_zone
+ with_env_tz 'US/Eastern' do
+ twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)')
+ Time.stubs(:current).returns(twz)
+ assert_equal false, Time.local(2005,2,10,10,30,44).future?
+ assert_equal false, Time.local(2005,2,10,10,30,45).future?
+ assert_equal true, Time.local(2005,2,10,10,30,46).future?
+ assert_equal false, Time.utc(2005,2,10,15,30,44).future?
+ assert_equal false, Time.utc(2005,2,10,15,30,45).future?
+ assert_equal true, Time.utc(2005,2,10,15,30,46).future?
+ end
+ end
+ end
+
def test_acts_like_time
assert Time.new.acts_like_time?
end
@@ -624,4 +695,42 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ def time_is_64bits?
+ Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).is_a?(Time)
+ end
+end
+
+class TimeExtMarshalingTest < Test::Unit::TestCase
+ def test_marshaling_with_utc_instance
+ t = Time.utc(2000)
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_local_instance
+ t = Time.local(2000)
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_frozen_utc_instance
+ t = Time.utc(2000).freeze
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_frozen_local_instance
+ t = Time.local(2000).freeze
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index dfe04485be..72b540efe0 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -152,6 +152,50 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal false, @twz.between?(Time.utc(2000,1,1,0,0,1), Time.utc(2000,1,1,0,0,2))
end
+ uses_mocha 'TimeWithZone past?, today? and future?' do
+ def test_today
+ Date.stubs(:current).returns(Date.new(2000, 1, 1))
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today?
+ end
+
+ def test_past_with_time_current_as_time_local
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ end
+ end
+
+ def test_past_with_time_current_as_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
+ Time.stubs(:current).returns(twz)
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ end
+
+ def test_future_with_time_current_as_time_local
+ with_env_tz 'US/Eastern' do
+ Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ end
+ end
+
+ def future_with_time_current_as_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
+ Time.stubs(:current).returns(twz)
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ end
+ end
+
def test_eql?
assert @twz.eql?(Time.utc(2000))
assert @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
@@ -353,7 +397,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
def test_date_part_value_methods
silence_warnings do # silence warnings raised by tzinfo gem
twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
- twz.stubs(:method_missing).returns(nil) #ensure these methods are defined directly on class
+ twz.expects(:method_missing).never
assert_equal 1999, twz.year
assert_equal 12, twz.month
assert_equal 31, twz.day
@@ -361,6 +405,8 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal 18, twz.min
assert_equal 17, twz.sec
assert_equal 500, twz.usec
+ assert_equal 5, twz.wday
+ assert_equal 365, twz.yday
end
end
end
@@ -538,7 +584,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.since(1.days + 1.second).inspect
assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", (twz + 1.days + 1.second).inspect
end
-
+
def test_advance_1_day_across_spring_dst_transition_backwards
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,10,30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
@@ -548,7 +594,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.days).inspect
assert_equal "Sat, 01 Apr 2006 10:30:01 EST -05:00", twz.ago(1.days - 1.second).inspect
end
-
+
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
@@ -565,7 +611,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(24.hours).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:hours => 24).inspect
end
-
+
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,11,30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
@@ -582,7 +628,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(24.hours).inspect
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:hours => -24).inspect
end
-
+
def test_advance_1_day_across_fall_dst_transition
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
@@ -593,7 +639,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.since(1.days + 1.second).inspect
assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", (twz + 1.days + 1.second).inspect
end
-
+
def test_advance_1_day_across_fall_dst_transition_backwards
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,10,30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
@@ -603,7 +649,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.days).inspect
assert_equal "Sat, 28 Oct 2006 10:30:01 EDT -04:00", twz.ago(1.days - 1.second).inspect
end
-
+
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
@@ -620,7 +666,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(24.hours).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:hours => 24).inspect
end
-
+
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,9,30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
@@ -669,7 +715,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.month).inspect
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.month).inspect
end
-
+
def test_advance_1_year
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,2,15,10,30))
assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(:years => 1).inspect
@@ -679,7 +725,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.years_ago(1).inspect
assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", (twz - 1.year).inspect
end
-
+
def test_advance_1_year_during_dst
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,7,15,10,30))
assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(:years => 1).inspect
@@ -689,6 +735,14 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect
end
+
+ protected
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
+ ensure
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ end
end
class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 39c9c74c94..18ad784837 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -146,42 +146,42 @@ class DependenciesTest < Test::Unit::TestCase
def test_directories_manifest_as_modules_unless_const_defined
with_loading 'autoloading_fixtures' do
assert_kind_of Module, ModuleFolder
- Object.send! :remove_const, :ModuleFolder
+ Object.__send__ :remove_const, :ModuleFolder
end
end
def test_module_with_nested_class
with_loading 'autoloading_fixtures' do
assert_kind_of Class, ModuleFolder::NestedClass
- Object.send! :remove_const, :ModuleFolder
+ Object.__send__ :remove_const, :ModuleFolder
end
end
def test_module_with_nested_inline_class
with_loading 'autoloading_fixtures' do
assert_kind_of Class, ModuleFolder::InlineClass
- Object.send! :remove_const, :ModuleFolder
+ Object.__send__ :remove_const, :ModuleFolder
end
end
def test_directories_may_manifest_as_nested_classes
with_loading 'autoloading_fixtures' do
assert_kind_of Class, ClassFolder
- Object.send! :remove_const, :ClassFolder
+ Object.__send__ :remove_const, :ClassFolder
end
end
def test_class_with_nested_class
with_loading 'autoloading_fixtures' do
assert_kind_of Class, ClassFolder::NestedClass
- Object.send! :remove_const, :ClassFolder
+ Object.__send__ :remove_const, :ClassFolder
end
end
def test_class_with_nested_inline_class
with_loading 'autoloading_fixtures' do
assert_kind_of Class, ClassFolder::InlineClass
- Object.send! :remove_const, :ClassFolder
+ Object.__send__ :remove_const, :ClassFolder
end
end
@@ -190,7 +190,7 @@ class DependenciesTest < Test::Unit::TestCase
assert_kind_of Class, ClassFolder::ClassFolderSubclass
assert_kind_of Class, ClassFolder
assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder
- Object.send! :remove_const, :ClassFolder
+ Object.__send__ :remove_const, :ClassFolder
end
end
@@ -199,7 +199,7 @@ class DependenciesTest < Test::Unit::TestCase
sibling = ModuleFolder::NestedClass.class_eval "NestedSibling"
assert defined?(ModuleFolder::NestedSibling)
assert_equal ModuleFolder::NestedSibling, sibling
- Object.send! :remove_const, :ModuleFolder
+ Object.__send__ :remove_const, :ModuleFolder
end
end
@@ -208,7 +208,7 @@ class DependenciesTest < Test::Unit::TestCase
assert ! defined?(ModuleFolder)
assert_raises(NameError) { ModuleFolder::Object }
assert_raises(NameError) { ModuleFolder::NestedClass::Object }
- Object.send! :remove_const, :ModuleFolder
+ Object.__send__ :remove_const, :ModuleFolder
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 8eebe1be25..f304844e82 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -98,6 +98,12 @@ class InflectorTest < Test::Unit::TestCase
end
end
+ def test_parameterize
+ StringToParameterized.each do |some_string, parameterized_string|
+ assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))
+ end
+ end
+
def test_classify
ClassNameToTableName.each do |class_name, table_name|
assert_equal(class_name, ActiveSupport::Inflector.classify(table_name))
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index a9dd83a389..8057809dbd 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -142,6 +142,14 @@ module InflectorTestCases
"NodeChild" => "node_children"
}
+ StringToParameterized = {
+ "Donald E. Knuth" => "donald-e-knuth",
+ "Random text with *(bad)* characters" => "random-text-with-bad-characters",
+ "Malmö" => "malmo",
+ "Garçons" => "garcons",
+ "Allow_Under_Scores" => "allow_under_scores"
+ }
+
UnderscoreToHuman = {
"employee_salary" => "Employee salary",
"employee_id" => "Employee",
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 63cfadb7ec..a87309b038 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -167,6 +167,10 @@ class CharsTest < Test::Unit::TestCase
assert_equal false, 'test'.chars.respond_to?(:a_method_that_doesnt_exist)
end
+ def test_acts_like_string
+ assert 'Bambi'.chars.acts_like_string?
+ end
+
protected
def with_kcode(kcode)
diff --git a/activesupport/test/secure_random_test.rb b/activesupport/test/secure_random_test.rb
new file mode 100644
index 0000000000..b0b6c21a81
--- /dev/null
+++ b/activesupport/test/secure_random_test.rb
@@ -0,0 +1,15 @@
+require 'abstract_unit'
+
+class SecureRandomTest < Test::Unit::TestCase
+ def test_random_bytes
+ b1 = ActiveSupport::SecureRandom.random_bytes(64)
+ b2 = ActiveSupport::SecureRandom.random_bytes(64)
+ assert_not_equal b1, b2
+ end
+
+ def test_hex
+ b1 = ActiveSupport::SecureRandom.hex(64)
+ b2 = ActiveSupport::SecureRandom.hex(64)
+ assert_not_equal b1, b2
+ end
+end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 3a276d5aad..51d66e4c01 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 [Antonio Cangiano]
+
* Added config.threadsafe! to toggle allow concurrency settings and disable the dependency loader [Josh Peek]
* Turn cache_classes on by default [Josh Peek]
diff --git a/railties/Rakefile b/railties/Rakefile
index 174c85b59a..ec2fe850e6 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -272,20 +272,49 @@ Rake::RDocTask.new { |rdoc|
rdoc.rdoc_files.include('lib/commands/**/*.rb')
}
-guides = ['securing_rails_applications', 'testing_rails_applications', 'creating_plugins']
-guides_html_files = []
-guides.each do |guide_name|
- input = "doc/guides/#{guide_name}/#{guide_name}.txt"
- output = "doc/guides/#{guide_name}/#{guide_name}.html"
+# In this array, one defines the guides for which HTML output should be
+# generated. Specify the folder names of the guides. If the .txt filename
+# doesn't equal its folder name, then specify a hash: { 'folder_name' => 'basename' }
+guides = [
+ 'getting_started_with_rails',
+ 'securing_rails_applications',
+ 'testing_rails_applications',
+ 'creating_plugins',
+ 'migrations',
+ { 'routing' => 'routing_outside_in' },
+ { 'forms' =>'form_helpers' },
+ { 'activerecord' => 'association_basics' },
+ { 'debugging' => 'debugging_rails_applications' }
+]
+
+guides_html_files = [] # autogenerated from the 'guides' variable.
+guides.each do |entry|
+ if entry.is_a?(Hash)
+ guide_folder = entry.keys.first
+ guide_name = entry.values.first
+ else
+ guide_folder = entry
+ guide_name = entry
+ end
+ input = "doc/guides/#{guide_folder}/#{guide_name}.txt"
+ output = "doc/guides/#{guide_folder}/#{guide_name}.html"
guides_html_files << output
- file output => Dir["doc/guides/#{guide_name}/*.txt"] do
- sh "mizuho", input, "--template", "manualsonrails", "--multi-page",
- "--icons-dir", "../icons"
+ task output => Dir["doc/guides/#{guide_folder}/*.txt"] do
+ ENV['MANUALSONRAILS_INDEX_URL'] = '../index.html'
+ ENV.delete('MANUALSONRAILS_TOC')
+ sh "mizuho", input, "--template", "manualsonrails", "--icons-dir", "../icons"
end
end
+task 'doc/guides/index.html' => 'doc/guides/index.txt' do
+ ENV.delete('MANUALSONRAILS_INDEX_URL')
+ ENV['MANUALSONRAILS_TOC'] = 'no'
+ sh "mizuho", 'doc/guides/index.txt', "--template", "manualsonrails", "--icons-dir", "icons"
+end
+
desc "Generate HTML output for the guides"
task :generate_guides => guides_html_files
+task :generate_guides => 'doc/guides/index.html'
# Generate GEM ----------------------------------------------------------------------------
diff --git a/railties/doc/guides/activerecord/association_basics.txt b/railties/doc/guides/activerecord/association_basics.txt
new file mode 100644
index 0000000000..9d950c91dd
--- /dev/null
+++ b/railties/doc/guides/activerecord/association_basics.txt
@@ -0,0 +1,1657 @@
+A Guide to Active Record Associations
+=====================================
+
+This guide covers the association features of Active Record. By referring to this guide, you will be able to:
+
+* Declare associations between Active Record models
+* Understand the various types of Active Record associations
+* Use the methods added to your models by creating associations
+
+== Why Associations?
+
+Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a customers model and an orders model. Each customer can have many orders. Without associations, the model declarations would look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+end
+
+class Order < ActiveRecord::Base
+end
+-------------------------------------------------------
+
+Now, suppose we wanted to add a new order for an existing customer. We'd need to do something like this:
+
+[source, ruby]
+-------------------------------------------------------
+@order = Order.create(:order_date => Time.now, :customer_id => @customer.id)
+-------------------------------------------------------
+
+Or consider deleting a customer, and ensuring that all of its orders get deleted as well:
+
+[source, ruby]
+-------------------------------------------------------
+@orders = Order.find_by_customer_id(@customer.id)
+@orders.each do |order|
+ order.destroy
+end
+@customer.destroy
+-------------------------------------------------------
+
+With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+-------------------------------------------------------
+
+With this change, creating a new order for a particular customer is easier:
+[source, ruby]
+-------------------------------------------------------
+@order = @customer.orders.create(:order_date => Time.now)
+-------------------------------------------------------
+
+Deleting a customer and all of its orders is much easier:
+
+[source, ruby]
+-------------------------------------------------------
+@customer.destroy
+-------------------------------------------------------
+
+To learn more about the different types of associations, read the next section of this Guide. That's followed by some tips and tricks for working with associations, and then by a complete reference to the methods and options for associations in Rails.
+
+== The Types of Associations
+
+In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model +belongs_to+ another, you enable Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of association:
+
+* +belongs_to+
+* +has_one+
+* +has_many+
+* +has_many :through+
+* +has_one :through+
+* +has_and_belongs_to_many+
+
+In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate.
+
+=== The +belongs_to+ Association
+
+A +belongs_to+ association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+-------------------------------------------------------
+
+=== The +has_one+ Association
+
+A +has_one+ association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account
+end
+-------------------------------------------------------
+
+=== The +has_many+ Association
+
+A +has_many+ association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a +belongs_to+ association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :customers
+end
+-------------------------------------------------------
+
+NOTE: The name of the other model is pluralized when declaring a +has_many+ association.
+
+=== The +has_many :through+ Association
+
+A +has_many :through+ association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Physician < ActiveRecord::Base
+ has_many :appointments
+ has_many :patients, :through => :appointments
+end
+
+class Appointment < ActiveRecord::Base
+ belongs_to :physician
+ belongs_to :patient
+end
+
+class Patient < ActiveRecord::Base
+ has_many :appointments
+ has_many :physicians, :through => :appointments
+-------------------------------------------------------
+
+=== The +has_one :through+ Association
+
+A +has_one :through+ association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account
+ has_one :account_history, :through => :account
+end
+
+class Account < ActiveRecord::Base
+ belongs_to :account
+ has_one :account_history
+end
+
+class AccountHistory < ActiveRecord::Base
+ belongs_to :account
+end
+-------------------------------------------------------
+
+=== The +has_and_belongs_to_many+ Association
+
+A +has_and_belongs_to_many+ association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Assembly < ActiveRecord::Base
+ has_and_belongs_to_many :parts
+end
+
+class Part < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies
+end
+-------------------------------------------------------
+
+=== Choosing Between +belongs_to+ and +has_one+
+
+If you want to set up a 1-1 relationship between two models, you'll need to add +belongs_to+ to one, and +has_one+ to the other. How do you know which is which?
+
+The distinction is in where you place the foreign key (it goes on the table for the class declaring the +belongs_to+ association), but you should give some thought to the actual meaning of the data as well. The +has_one+ relationship says that one of something is yours - that is, that something points back to you. For example, it makes more sense to say that a supplier owns an account than that an account owns a supplier. This suggests that the correct relationships are like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account
+end
+
+class Account < ActiveRecord::Base
+ belongs_to :supplier
+end
+-------------------------------------------------------
+
+The corresponding migration might look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class CreateSuppliers < ActiveRecord::Migration
+ def self.up
+ create_table :suppliers do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :accounts do |t|
+ t.integer :supplier_id
+ t.string :account_number
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :accounts
+ drop_table :suppliers
+ end
+end
+-------------------------------------------------------
+
+=== Choosing Between +has_many :through+ and +has_and_belongs_to_many+
+
+Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use +has_and_belongs_to_many+, which allows you to make the association directly:
+
+[source, ruby]
+-------------------------------------------------------
+class Assembly < ActiveRecord::Base
+ has_and_belongs_to_many :parts
+end
+
+class Part < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies
+end
+-------------------------------------------------------
+
+The second way to declare a many-to-many relationship is to use +has_many :through+. This makes the association indirectly, through a join model:
+
+[source, ruby]
+-------------------------------------------------------
+class Assembly < ActiveRecord::Base
+ has_many :manifests
+ has_many :parts, :through => :manifests
+end
+
+class Manifest < ActiveRecord::Base
+ belongs_to :assembly
+ belongs_to :part
+end
+
+class Part < ActiveRecord::Base
+ has_many :manifests
+ has_many :assemblies, :through => :manifests
+end
+-------------------------------------------------------
+
+The simplest rule of thumb is that you should set up a +has_many :through+ relationship if you need to work with the relationship model as an independent entity. If you don't need to do anything with the relationship model, it may be simpler to set up a +has_and_belongs_to_many+ relationship (though you'll need to remember to create the joining table).
+
+You should use +has_many :through+ if you need validations, callbacks, or extra attributes on the join model.
+
+=== Polymorphic Associations
+
+A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared:
+
+[source, ruby]
+-------------------------------------------------------
+class Picture < ActiveRecord::Base
+ belongs_to :imageable, :polymorphic => true
+end
+
+class Employee < ActiveRecord::Base
+ has_many :pictures, :as => :attachable
+end
+
+class Product < ActiveRecord::Base
+ has_many :pictures, :as => :attachable
+end
+-------------------------------------------------------
+
+You can think of a polymorphic +belongs_to+ declaration as setting up an interface that any other model can use. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface:
+
+[source, ruby]
+-------------------------------------------------------
+class CreatePictures < ActiveRecord::Migration
+ def self.up
+ create_table :pictures do |t|
+ t.string :name
+ t.integer :imageable_id
+ t.string :imageable_type
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :pictures
+ end
+end
+-------------------------------------------------------
+
+== Tips, Tricks, and Warnings
+
+Here are a few things you should know to make efficient use of Active Record associations in your Rails applications:
+
+* Controlling caching
+* Avoiding name collisions
+* Updating the schema
+* Controlling association scope
+
+=== Controlling Caching
+
+All of the association methods are built around caching that keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example:
+
+[source, ruby]
+-------------------------------------------------------
+customer.orders # retrieves orders from the database
+customer.orders.size # uses the cached copy of orders
+customer.orders.empty? # uses the cached copy of orders
+-------------------------------------------------------
+
+But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass +true+ to the association call:
+
+[source, ruby]
+-------------------------------------------------------
+customer.orders # retrieves orders from the database
+customer.orders.size # uses the cached copy of orders
+customer.orders(true).empty? # discards the cached copy of orders and goes back to the database
+-------------------------------------------------------
+
+=== Avoiding Name Collisions
+
+You are not free to use just any name for your associations. Because creating an association adds a method with that name to the model, it is a bad idea to give an association a name that is already used for an instance method of ActiveRecord::Base. The association method would override the base method and break things. For instance, +attributes+ or +connection+ are bad names for associations.
+
+=== Updating the Schema
+
+Associations are extremely useful, but they are not magic. You are responsible for maintaining your database schema to match your associations. In practice, this means two things. First, you need to create foreign keys as appropriate:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+-------------------------------------------------------
+
+This declaration needs to be backed up by the proper foreign key declaration on the orders table:
+
+[source, ruby]
+-------------------------------------------------------
+class CreateOrders < ActiveRecord::Migration
+ def self.up
+ create_table :orders do |t|
+ t.order_date :datetime
+ t.order_number :string
+ t.customer_id :integer
+ end
+ end
+
+ def self.down
+ drop_table :orders
+ end
+end
+-------------------------------------------------------
+
+If you create an association some time after you build the underlying model, you need to remember to create an +add_column+ migration to provide the necessary foreign key.
+
+Second, if you create a +has_and_belongs_to_many+ association, you need to explicitly create the joining table. Unless name of the join table is explicitly specified by using the +:join_table+ option, Active Record create the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
+
+WARNING: The precedence between model names is calculated using the +<+ operator for +String+. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers".
+
+Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
+
+[source, ruby]
+-------------------------------------------------------
+class Assembly < ActiveRecord::Base
+ has_and_belongs_to_many :parts
+end
+
+class Part < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies
+end
+-------------------------------------------------------
+
+These need to be backed up by a migration to create the +assemblies_parts+ table. This table should be created without a primary key:
+
+[source, ruby]
+-------------------------------------------------------
+class CreateAssemblyPartJoinTable < ActiveRecord::Migration
+ def self.up
+ create_table :assemblies_parts, :id => false do |t|
+ t.integer :assembly_id
+ t.integer :part_id
+ end
+ end
+
+ def self.down
+ drop_table :assemblies_parts
+ end
+end
+-------------------------------------------------------
+
+=== Controlling Association Scope
+
+By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example:
+
+[source, ruby]
+-------------------------------------------------------
+module MyApplication
+ module Business
+ class Supplier < ActiveRecord::Base
+ has_one :account
+ end
+
+ class Account < ActiveRecord::Base
+ belongs_to :supplier
+ end
+ end
+end
+-------------------------------------------------------
+
+This will work fine, because both the +Supplier+ and the +Account+ class are defined within the same scope.
+
+You can associate a model with a model in a different scope, but only by specifying the complete class name in your association declaration:
+
+[source, ruby]
+-------------------------------------------------------
+module MyApplication
+ module Business
+ class Supplier < ActiveRecord::Base
+ has_one :account, :class_name => "MyApplication::Billing::Account"
+ end
+ end
+
+ module Billing
+ class Account < ActiveRecord::Base
+ belongs_to :supplier, :class_name => "MyApplication::Business::Supplier"
+ end
+ end
+end
+-------------------------------------------------------
+
+== Detailed Association Reference
+
+The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association.
+
+=== The +belongs_to+ Association
+
+The +belongs_to+ association creates a one-to-one match with another model. In database terms, this association says that this class contains the foreign key. If the other class contains the foreign key, then you should use +has_one+ instead.
+
+==== Methods Added by +belongs_to+
+
+When you declare a +belongs_to+ assocation, the declaring class automatically gains five methods related to the association:
+
+* +_association_(force_reload = false)+
+* +_association_=(associate)+
+* +_association_.nil?+
+* +build___association__(attributes = {})+
+* +create___association__(attributes = {})+
+
+In all of these methods, +_association_+ is replaced with the symbol passed as the first argument to +belongs_to+. For example, given the declaration:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+-------------------------------------------------------
+
+Each instance of the order model will have these methods:
+
+[source, ruby]
+-------------------------------------------------------
+customer
+customer=
+customer.nil?
+build_customer
+create_customer
+-------------------------------------------------------
+
+===== +_association_(force_reload = false)+
+
+The +_association_+ method returns the associated object, if any. If no associated object is found, it returns +nil+.
+
+[source, ruby]
+-------------------------------------------------------
+@customer = @order.customer
+-------------------------------------------------------
+
+If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass +true+ as the +force_reload+ argument.
+
+===== +_association_=(associate)+
+
+The +_association_=+ method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associate object and setting this object's foreign key to the same value.
+
+[source, ruby]
+-------------------------------------------------------
+@order.customer = @customer
+-------------------------------------------------------
+
+===== +_association_.nil?+
+
+The +_association_.nil?+ method returns +true+ if there is no associated object.
+
+[source, ruby]
+-------------------------------------------------------
+if @order.customer.nil?
+ @msg = "No customer found for this order"
+end
+-------------------------------------------------------
+
+===== +build___association__(attributes = {})+
+
+The +build__\_association__+ method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set, but the associated object will _not_ yet be saved.
+
+[source, ruby]
+-------------------------------------------------------
+@customer = @order.build_customer({:customer_number => 123, :customer_name => "John Doe"})
+-------------------------------------------------------
+
+===== +create___association__(attributes = {})+
+
+The +create__\_association__+ method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set. In addition, the associated object _will_ be saved (assuming that it passes any validations).
+
+[source, ruby]
+-------------------------------------------------------
+@customer = @order.create_customer({:customer_number => 123, :customer_name => "John Doe"})
+-------------------------------------------------------
+
+==== Options for +belongs_to+
+
+In many situations, you can use the default behavior of +belongs_to+ without any customization. But despite Rails' emphasis of convention over customization, you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a +belongs_to+ association. For example, an association with several options might look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :counter_cache => true, :conditions => "active = 1"
+end
+-------------------------------------------------------
+
+===== +:class_name+
+
+If the name of the other model cannot be derived from the association name, you can use the +:class_name+ option to supply the model name. For example, if an order belongs to a customer, but the actual name of the model containing customers is patron, you'd set things up this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :class_name => :patron
+end
+-------------------------------------------------------
+
+===== +:conditions+
+
+The +:conditions+ option lets you specify the conditions that the associated object must meet (in the syntax used by a SQL +WHERE+ clause).
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :conditions => "active = 1"
+end
+-------------------------------------------------------
+
+===== +:select+
+
+The +:select+ option lets you override the SQL +SELECT+ clause that is used to retrieve data about the associated object. By default, Rails retrieves all columns.
+
+===== +:foreign_key+
+
+By convention, Rails guesses that the column used to hold the foreign key on this model is the name of the association with the suffix +_id+ added. The +:foreign_key+ option lets you set the name of the foreign key directly:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :class_name => :patron, :foreign_key => "patron_id"
+end
+-------------------------------------------------------
+
+TIP: In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.
+
+===== +:dependent+
+
+If you set the +:dependent+ option to +:destroy+, then deleting this object will call the destroy method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method.
+
+WARNING: You should not specify this option on a +belongs_to+ association that is connected with a +has_many+ association on the other class. Doing so can lead to orphaned records in your database.
+
+===== +:counter_cache+
+
+The +:counter_cache+ option can be used to make finding the number of belonging objects more efficient. Consider these models:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+With these declarations, asking for the value of +@customer.orders.size+ requires making a call to the database to perform a +COUNT(*)+ query. To avoid this call, you can add a counter cache to the _belonging_ model:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :counter_cache => true
+end
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+With this declaration, Rails will keep the cache value up to date, and then return that value in response to the +.size+ method.
+
+Although the +:counter_cache+ option is specified on the model that includes the +belongs_to+ declaration, the actual column must be added to the _associated_ model. In the case above, you would need to add a column named +orders_count+ to the +Customer+ model. You can override the default column name if you need to:
+
+[source, ruby]
+-------------------------------------------------------
+class Order < ActiveRecord::Base
+ belongs_to :customer, :counter_cache => :customer_count
+end
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+Counter cache columns are added to the containing model's list of read-only attributes through +attr_readonly+.
+
+WARNING: When you create a counter cache column in the database, be sure to specify a default value of zero. Otherwise, Rails will not properly maintain the counter.
+
+===== +:include+
+
+You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
+
+[source, ruby]
+-------------------------------------------------------
+class LineItem < ActiveRecord::Base
+ belongs_to :order
+end
+class Order < ActiveRecord::Base
+ belongs_to :customer
+ has_many :line_items
+end
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+If you frequently retrieve customers directly from line items (+@line_item.order.customer+), then you can make your code somewhat more efficient by including customers in the association from line items to orders:
+
+[source, ruby]
+-------------------------------------------------------
+class LineItem < ActiveRecord::Base
+ belongs_to :order, :include => :customer
+end
+class Order < ActiveRecord::Base
+ belongs_to :customer
+ has_many :line_items
+end
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+===== +:polymorphic+
+
+Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations are discussed in detail later in this guide.
+
+===== +:readonly+
+
+If you set the +:readonly+ option to +true+, then the associated object will be read-only when retrieved via the association.
+
+===== +:validate+
+
+If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved.
+
+===== +:accessible+
+
+The +:accessible+ option is the association version of +ActiveRecord::Base#attr_accessible+. If you set the +:accessible+ option to true, then mass assignment is allowed for this association.
+
+==== When are Objects Saved?
+Assigning an object to a +belongs_to+ association does _not_ automatically save the object. It does not save the associated object either.
+
+=== The has_one Association
+
+The +has_one+ association creates a one-to-one match with another model. In database terms, this association says that the other class contains the foreign key. If this class contains the foreign key, then you should use +belongs_to+ instead.
+
+==== Methods Added by +has_one+
+
+When you declare a +has_one+ association, the declaring class automatically gains five methods related to the association:
+
+* +_association_(force_reload = false)+
+* +_association_=(associate)+
+* +_association_.nil?+
+* +build___association__(attributes = {})+
+* +create___association__(attributes = {})+
+
+In all of these methods, +_association_+ is replaced with the symbol passed as the first argument to +has_one+. For example, given the declaration:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account
+end
+-------------------------------------------------------
+
+Each instance of the order model will have these methods:
+
+[source, ruby]
+-------------------------------------------------------
+account
+account=
+account.nil?
+build_account
+create_account
+-------------------------------------------------------
+
+===== +_association_(force_reload = false)+
+
+The +_association_+ method returns the associated object, if any. If no associated object is found, it returns +nil+.
+
+[source, ruby]
+-------------------------------------------------------
+@account = @supplier.account
+-------------------------------------------------------
+
+If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass +true+ as the +force_reload+ argument.
+
+===== +_association_=(associate)+
+
+The +_association_=+ method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from this object and setting the associate object's foreign key to the same value.
+
+[source, ruby]
+-------------------------------------------------------
+@suppler.account = @account
+-------------------------------------------------------
+
+===== +_association_.nil?+
+
+The +_association_.nil?+ method returns +true+ if there is no associated object.
+
+[source, ruby]
+-------------------------------------------------------
+if @supplier.account.nil?
+ @msg = "No account found for this supplier"
+end
+-------------------------------------------------------
+
+===== +build___association__(attributes = {})+
+
+The +build__\_association__+ method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this its foreign key will be set, but the associated object will _not_ yet be saved.
+
+[source, ruby]
+-------------------------------------------------------
+@account = @supplier.build_account({:terms => "Net 30"})
+-------------------------------------------------------
+
+===== +create___association__(attributes = {})+
+
+The +create__\_association__+ method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through its foreign key will be set. In addition, the associated object _will_ be saved (assuming that it passes any validations).
+
+[source, ruby]
+-------------------------------------------------------
+@account = @supplier.create_account({:terms => "Net 30"})
+-------------------------------------------------------
+
+==== Options for +has_one+
+
+
+In many situations, you can use the default behavior of +has_one+ without any customization. But despite Rails' emphasis of convention over customization, you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a +has_one+ association. For example, an association with several options might look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account, :class_name => :billing, :dependent => :nullify
+end
+-------------------------------------------------------
+
+===== +:class_name+
+
+If the name of the other model cannot be derived from the association name, you can use the +:class_name+ option to supply the model name. For example, if a supplier has an account, but the actual name of the model containing accounts is billing, you'd set things up this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account, :class_name => :billing
+end
+-------------------------------------------------------
+
+===== +:conditions+
+
+The +:conditions+ option lets you specify the conditions that the associated object must meet (in the syntax used by a SQL +WHERE+ clause).
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account, :conditions => "confirmed = 1"
+end
+-------------------------------------------------------
+
+===== +:order+
+
+The +:order+ option dictates the order in which associated objects will be received (in the syntax used by a SQL +WHERE+ clause). Because a +has_one+ association will only retrieve a single associated object, this option should not be needed.
+
+===== +:dependent+
+
+If you set the +:dependent+ option to +:destroy+, then deleting this object will call the destroy method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
+
+===== +:foreign_key+
+
+By convention, Rails guesses that the column used to hold the foreign key on the other model is the name of this model with the suffix +_id+ added. The +:foreign_key+ option lets you set the name of the foreign key directly:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account, :foreign_key => "supp_id"
+end
+-------------------------------------------------------
+
+TIP: In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.
+
+===== +:primary_key+
+
+By convention, Rails guesses that the column used to hold the primary key of this model is +id+. You can override this and explicitly specify the primary key with the +:primary_key+ option.
+
+===== +:include+
+
+You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account
+end
+class Account < ActiveRecord::Base
+ belongs_to :supplier
+ belongs_to :representative
+end
+class Representative < ActiveRecord::Base
+ has_many :accounts
+end
+-------------------------------------------------------
+
+If you frequently retrieve representatives directly from suppliers (+@supplier.account.representative+), then you can make your code somewhat more efficient by including representatives in the association from suppliers to accounts:
+
+[source, ruby]
+-------------------------------------------------------
+class Supplier < ActiveRecord::Base
+ has_one :account, :include => :representative
+end
+class Account < ActiveRecord::Base
+ belongs_to :supplier
+ belongs_to :representative
+end
+class Representative < ActiveRecord::Base
+ has_many :accounts
+end
+-------------------------------------------------------
+
+===== +:as+
+
+Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations are discussed in detail later in this guide.
+
+===== +:select+
+
+The +:select+ option lets you override the SQL +SELECT+ clause that is used to retrieve data about the associated object. By default, Rails retrieves all columns.
+
+===== +:through+
+
+The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations are discussed in detail later in this guide.
+
+===== +:source+
+
+The +:source+ option specifies the source association name for a +has_one :through+ association.
+
+===== +:source_type+
+
+The +:source_type+ option specifies the source association type for a +has_one :through+ association that proceeds through a polymorphic association.
+
+===== +:readonly+
+
+If you set the +:readonly+ option to +true+, then the associated object will be read-only when retrieved via the association.
+
+===== +:validate+
+
+If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved.
+
+===== +:accessible+
+
+The +:accessible+ option is the association version of +ActiveRecord::Base#attr_accessible+. If you set the +:accessible+ option to true, then mass assignment is allowed for this association.
+
+==== When are Objects Saved?
+
+When you assign an object to a +has_one+ association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too.
+
+If either of these saves fails due to validation errors, then the assignment statement returns +false+ and the assignment itself is cancelled.
+
+If the parent object (the one declaring the +has_one+ association) is unsaved (that is, +new_record?+ returns +true+) then the child objects are not saved.
+
+If you want to assign an object to a +has_one+ association without saving the object, use the +association.build+ method.
+
+=== The has_many Association
+
+The +has_many+ association creates a one-to-many relationship with another model. In database terms, this association says that the other class will have a foreign key that refers to instances of this class.
+
+==== Methods Added
+
+When you declare a +has_many+ association, the declaring class automatically gains 13 methods related to the association:
+
+* +_collection_(force_reload = false)+
+* +_collection_<<(object, ...)+
+* +_collection_.delete(object, ...)+
+* +_collection_=objects+
+* +_collection\_singular_\_ids+
+* +_collection\_singular_\_ids=ids+
+* +_collection_.clear+
+* +_collection_.empty?+
+* +_collection_.size+
+* +_collection_.find(...)+
+* +_collection_.exist?(...)+
+* +_collection_.build(attributes = {}, ...)+
+* +_collection_.create(attributes = {})+
+
+In all of these methods, +_collection_+ is replaced with the symbol passed as the first argument to +has_many+, and +_collection\_singular_+ is replaced with the singularized version of that symbol.. For example, given the declaration:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+-------------------------------------------------------
+
+Each instance of the customer model will have these methods:
+
+[source, ruby]
+-------------------------------------------------------
++orders(force_reload = false)+
++orders<<(object, ...)+
++orders.delete(object, ...)+
++orders=objects+
++order_ids+
++order_ids=ids+
++orders.clear+
++orders.empty?+
++orders.size+
++orders.find(...)+
++orders.exist?(...)+
++orders.build(attributes = {}, ...)+
++orders.create(attributes = {})+
+-------------------------------------------------------
+
+===== +_collection_(force_reload = false)+
+
+The +_collection_+ method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
+
+[source, ruby]
+-------------------------------------------------------
+@orders = @customer.orders
+-------------------------------------------------------
+
+===== +_collection_<<(object, ...)+
+
+The +_collection<<+ method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.
+
+[source, ruby]
+-------------------------------------------------------
+@customer.orders << @order1
+-------------------------------------------------------
+
+===== +_collection_.delete(object, ...)+
+
+The +_collection_.delete+ method removes one or more objects from the collection by setting their foreign keys to +NULL+.
+
+[source, ruby]
+-------------------------------------------------------
+@customer.orders.delete(@order1)
+-------------------------------------------------------
+
+WARNING: The +_collection_.delete+ method will destroy the deleted object if they are declared as +belongs_to+ and are dependent on this model.
+
+===== +_collection_=objects+
+
+The +_collection_=+ method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
+
+===== +_collection\_singular_\_ids+
+
+# Returns an array of the associated objects' ids
+
+The +_collection\_singular_\_ids+ method returns an array of the ids of the objects in the collection.
+
+[source, ruby]
+-------------------------------------------------------
+@order_ids = @customer.order_ids
+-------------------------------------------------------
+
+===== +__collection\_singular_\_ids=ids+
+
+The +__collection\_singular_\_ids=+ method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
+
+===== +_collection_.clear+
+
+The +_collection_.clear+ method removes every object from the collection. This destroys the associated objects if they are associated with +:dependent => :destroy+, deletes them directly from the database if +:dependent => :delete_all+, and otherwise sets their foreign keys to +NULL+.
+
+===== +_collection_.empty?+
+
+The +_collection_.empty?+ method returns +true+ if the collection does not contain any associated objects.
+
+[source, ruby]
+-------------------------------------------------------
+<% if @customer.orders.empty? %>
+ No Orders Found
+<% end %>
+-------------------------------------------------------
+
+===== +_collection_.size+
+
+The +_collection_.size+ method returns the number of objects in the collection.
+
+[source, ruby]
+-------------------------------------------------------
+@order_count = @customer.orders.size
+-------------------------------------------------------
+
+===== +_collection_.find(...)+
+
+The +_collection_.find+ method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+.
+
+[source, ruby]
+-------------------------------------------------------
+@open_orders = @customer.orders.find(:all, :conditions => "open = 1")
+-------------------------------------------------------
+
+===== +_collection_.exist?(...)+
+
+The +_collection_.exist?+ method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as +ActiveRecord::Base.exists?+.
+
+===== +_collection_.build(attributes = {}, ...)+
+
+The +_collection_.build+ method returns one or more new objects of the associated type. These objects will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved.
+
+[source, ruby]
+-------------------------------------------------------
+@order = @customer.orders.build({:order_date => Time.now, :order_number => "A12345"})
+-------------------------------------------------------
+
+===== +_collection_.create(attributes = {})+
+
+The +_collection_.create+ method returns one a new object of the associated type. This object will be instantiated from the passed attributes, the link through its foreign key will be created, and the associated object _will_ be saved (assuming that it passes any validations).
+
+[source, ruby]
+-------------------------------------------------------
+@order = @customer.orders.create({:order_date => Time.now, :order_number => "A12345"})
+-------------------------------------------------------
+
+==== Options for has_many
+
+In many situations, you can use the default behavior for +has_many+ without any customization. But you can alter that behavior in a number of ways. This section cover the options that you can pass when you create a +has_many+ association. For example, an association with several options might look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :dependent => :delete_all, :validate => :false
+end
+-------------------------------------------------------
+
+===== +:class_name+
+
+If the name of the other model cannot be derived from the association name, you can use the +:class_name+ option to supply the model name. For example, if a customer has many orders, but the actual name of the model containing orders is transactions, you'd set things up this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :class_name => :transaction
+end
+-------------------------------------------------------
+
+===== +:conditions+
+
+The +:conditions+ option lets you specify the conditions that the associated object must meet (in the syntax used by a SQL +WHERE+ clause).
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :confirmed_orders, :class_name => :orders, :conditions => "confirmed = 1"
+end
+-------------------------------------------------------
+
+You can also set conditions via a hash:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :confirmed_orders, :class_name => :orders, :conditions => { :confirmed => true }
+end
+-------------------------------------------------------
+
+If you use a hash-style +:conditions+ option, then record creation via this association will be automatically scoped using the hash. In this case, using +@customer.confirmed_orders.create+ or +@customer.confirmed_orders.build+ will create orders where the confirmed column has the value +true+.
+
+===== +:order+
+
+The +:order+ option dictates the order in which associated objects will be received (in the syntax used by a SQL +WHERE+ clause).
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :order => "date_confirmed DESC"
+end
+-------------------------------------------------------
+
+===== +:foreign_key+
+
+By convention, Rails guesses that the column used to hold the foreign key on the other model is the name of this model with the suffix +_id+ added. The +:foreign_key+ option lets you set the name of the foreign key directly:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :foreign_key => "cust_id"
+end
+-------------------------------------------------------
+
+TIP: In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.
+
+===== +:primary_key+
+
+By convention, Rails guesses that the column used to hold the primary key of this model is +id+. You can override this and explicitly specify the primary key with the +:primary_key+ option.
+
+===== +:dependent+
+
+If you set the +:dependent+ option to +:destroy+, then deleting this object will call the destroy method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
+
+NOTE: This option is ignored when you use the +:through+ option on the association.
+
+===== +:finder_sql+
+
+Normally Rails automatically generates the proper SQL to fetch the association members. With the +:finder_sql+ option, you can specify a complete SQL statement to fetch them yourself. If fetching objects requires complex multi-table SQL, this may be necessary.
+
+===== +:counter_sql+
+
+Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
+
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+
+===== +:extend+
+
+The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide.
+
+===== +:include+
+
+You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+class Order < ActiveRecord::Base
+ belongs_to :customer
+ has_many :line_items
+end
+class LineItem < ActiveRecord::Base
+ belongs_to :orders
+end
+-------------------------------------------------------
+
+If you frequently retrieve line items directly from customers (+@customer.orders.line_items+), then you can make your code somewhat more efficient by including line items in the association from customers to orders:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :include => :line_items
+end
+class Order < ActiveRecord::Base
+ belongs_to :customer
+ has_many :line_items
+end
+class LineItem < ActiveRecord::Base
+ belongs_to :orders
+end
+-------------------------------------------------------
+
+===== +:group+
+
+The +:group+ option supplies an attribute name to group the result set by, using a +GROUP BY+ clause in the finder SQL.
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :line_items, :through => :orders, :group => "orders.id"
+end
+-------------------------------------------------------
+
+===== +:limit+
+
+The +:limit+ option lets you restrict the total number of objects that will be fetched through an association.
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :recent_orders, :class_name => :orders, :order => "order_date DESC", :limit => 100
+end
+-------------------------------------------------------
+
+===== +:offset+
+
+The +:offset+ option lets you specify the starting offset for fetching objects via an association. For example, if you set +:offset => 11+, it will skip the first 10 records.
+
+===== +:select+
+
+The +:select+ option lets you override the SQL +SELECT+ clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns.
+
+WARNING: If you specify your own +:select+, be sure to include the primary key and foreign key columns of the associated model. If you do not, Rails will throw an error.
+
+===== +:as+
+
+Setting the +:as+ option indicates that this is a polymorphic association, as discussed earlier in this guide.
+
+===== +:through+
+
+The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed earlier in this guide.
+
+===== +:source+
+
+The +:source+ option specifies the source association name for a +has_many :through+ association. You only need to use this option if the name of the source association cannot be automatically inferred from the association name.
+
+===== +:source_type+
+
+The +:source_type+ option specifies the source association type for a +has_many :through+ association that proceeds through a polymorphic association.
+
+===== +:uniq+
+
+Specify the +:uniq => true+ option to remove duplicates from the collection. This is most useful in conjunction with the +:through+ option.
+
+===== +:readonly+
+
+If you set the +:readonly+ option to +true+, then the associated objects will be read-only when retrieved via the association.
+
+===== +:validate+
+
+If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved.
+
+===== +:accessible+
+
+The +:accessible+ option is the association version of +ActiveRecord::Base#attr_accessible+. If you set the +:accessible+ option to true, then mass assignment is allowed for this association.
+
+==== When are Objects Saved?
+
+When you assign an object to a +has_many+ association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.
+
+If any of these saves fails due to validation errors, then the assignment statement returns +false+ and the assignment itself is cancelled.
+
+If the parent object (the one declaring the +has_many+ association) is unsaved (that is, +new_record?+ returns +true+) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved.
+
+If you want to assign an object to a +has_many+ association without saving the object, use the +_collection_.build+ method.
+
+=== The +has_and_belongs_to_many+ Association
+
+The +has_and_belongs_to_many+ association creates a many-to-many relationship with another model. In database terms, this associates two classes via an intermediate join table that includes foreign keys referring to each of the classes.
+
+==== Methods Added
+
+When you declare a +has_and_belongs_to_many+ association, the declaring class automatically gains 13 methods related to the association:
+
+* +_collection_(force_reload = false)+
+* +_collection_<<(object, ...)+
+* +_collection_.delete(object, ...)+
+* +_collection_=objects+
+* +_collection\_singular_\_ids+
+* +_collection\_singular_\_ids=ids+
+* +_collection_.clear+
+* +_collection_.empty?+
+* +_collection_.size+
+* +_collection_.find(...)+
+* +_collection_.exist?(...)+
+* +_collection_.build(attributes = {})+
+* +_collection_.create(attributes = {})+
+
+In all of these methods, +_collection_+ is replaced with the symbol passed as the first argument to +has_many+, and +_collection\_singular+ is replaced with the singularized version of that symbol.. For example, given the declaration:
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies
+end
+-------------------------------------------------------
+
+Each instance of the part model will have these methods:
+
+[source, ruby]
+-------------------------------------------------------
++assemblies(force_reload = false)+
++assemblies<<(object, ...)+
++assemblies.delete(object, ...)+
++assemblies=objects+
++assembly_ids+
++assembly_ids=ids+
++assemblies.clear+
++assemblies.empty?+
++assemblies.size+
++assemblies.find(...)+
++assemblies.exist?(...)+
++assemblies.build(attributes = {}, ...)+
++assemblies.create(attributes = {})+
+-------------------------------------------------------
+
+===== Additional Field Methods
+
+If the join table for a +has_and_belongs_to_many+ association has additional fields beyond the two foreign keys, these fields will be added as attributes to records retrieved via that association. Records returned with additional attributes will always be read-only, because Rails cannot save changes to those attributes.
+
+WARNING: The use of extra attributes on the join table in a +has_and_belongs_to_many+ association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a +has_many :through+ association instead of +has_and_belongs_to_many+.
+
+
+===== +_collection_(force_reload = false)+
+
+The +_collection_+ method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
+
+[source, ruby]
+-------------------------------------------------------
+@assemblies = @part.assemblies
+-------------------------------------------------------
+
+===== +_collection_<<(object, ...)+
+
+The +_collection<<+ method adds one or more objects to the collection by creating records in the join table.
+
+[source, ruby]
+-------------------------------------------------------
+@part.assemblies << @assembly1
+-------------------------------------------------------
+
+NOTE: This method is aliased as +_collection_.concat+ and +_collection_.push+.
+
+===== +_collection_.delete(object, ...)+
+
+The +_collection_.delete+ method removes one or more objects from the collection by deleting records in the join table+. This does not destroy the objects.
+
+[source, ruby]
+-------------------------------------------------------
+@part.assemblies.delete(@assembly1)
+-------------------------------------------------------
+
+===== +_collection_=objects+
+
+The +_collection_=+ method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
+
+===== +_collection\_singular_\_ids+
+
+# Returns an array of the associated objects' ids
+
+The +_collection\_singular_\_ids+ method returns an array of the ids of the objects in the collection.
+
+[source, ruby]
+-------------------------------------------------------
+@assembly_ids = @part.assembly_ids
+-------------------------------------------------------
+
+===== +_collection\_singular_\_ids=ids+
+
+The +_collection\_singular_\_ids=+ method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
+
+===== +_collection_.clear+
+
+The +_collection_.clear+ method removes every object from the collection. This does not destroy the associated objects.
+
+===== +_collection_.empty?+
+
+The +_collection_.empty?+ method returns +true+ if the collection does not contain any associated objects.
+
+[source, ruby]
+-------------------------------------------------------
+<% if @part.assemblies.empty? %>
+ This part is not used in any assemblies
+<% end %>
+-------------------------------------------------------
+
+===== +_collection_.size+
+
+The +_collection_.size+ method returns the number of objects in the collection.
+
+[source, ruby]
+-------------------------------------------------------
+@assembly_count = @part.assemblies.size
+-------------------------------------------------------
+
+===== +_collection_.find(...)+
+
+The +_collection_.find+ method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. It also adds the additional condition that the object must be in the collection.
+
+[source, ruby]
+-------------------------------------------------------
+@new_assemblies = @part.assemblies.find(:all, :conditions => ["created_at > ?", 2.days.ago])
+-------------------------------------------------------
+
+===== +_collection_.exist?(...)+
+
+The +_collection_.exist?+ method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as +ActiveRecord::Base.exists?+.
+
+===== +_collection_.build(attributes = {})+
+
+The +_collection_.build+ method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through the join table will be created, but the associated object will _not_ yet be saved.
+
+[source, ruby]
+-------------------------------------------------------
+@assembly = @part.assemblies.build({:assembly_name => "Transmission housing"})
+-------------------------------------------------------
+
+===== +_collection_.create(attributes = {})+
+
+The +_collection_.create+ method returns a new object of the associated type. This objects will be instantiated from the passed attributes, the link through the join table will be created, and the associated object _will_ be saved (assuming that it passes any validations).
+
+[source, ruby]
+-------------------------------------------------------
+@assembly = @part.assemblies.create({:assembly_name => "Transmission housing"})
+-------------------------------------------------------
+
+==== Options for has_and_belongs_to_many
+
+In many situations, you can use the default behavior for +has_and_belongs_to_many+ without any customization. But you can alter that behavior in a number of ways. This section cover the options that you can pass when you create a +has_and_belongs_to_many+ association. For example, an association with several options might look like this:
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :uniq => true, :read_only => true
+end
+-------------------------------------------------------
+
+===== +:class_name+
+
+If the name of the other model cannot be derived from the association name, you can use the +:class_name+ option to supply the model name. For example, if a part has many assemblies, but the actual name of the model containing assemblies is gadgets, you'd set things up this way:
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :class_name => :gadgets
+end
+-------------------------------------------------------
+
+===== +:join_table+
+
+If the default name of the join table, based on lexical ordering, is not what you want, you can use the +:join_table+ option to override the default.
+
+===== +:foreign_key+
+
+By convention, Rails guesses that the column in the join table used to hold the foreign key pointing to this model is the name of this model with the suffix +_id+ added. The +:foreign_key+ option lets you set the name of the foreign key directly:
+
+===== +:association_foreign_key+
+
+By convention, Rails guesses that the column in the join table used to hold the foreign key pointing to the other model is the name of that model with the suffix +_id+ added. The +:association_foreign_key+ option lets you set the name of the foreign key directly:
+
+TIP: The +:foreign_key+ and +:association_foreign_key+ options are useful when setting up a many-to-many self-join. For example:
+
+[source, ruby]
+-------------------------------------------------------
+class User < ActiveRecord::Base
+ has_and_belongs_to_many :friends, :class_name => :users,
+ :foreign_key => "this_user_id", :association_foreign_key => "other_user_id"
+end
+-------------------------------------------------------
+
+===== +:conditions+
+
+The +:conditions+ option lets you specify the conditions that the associated object must meet (in the syntax used by a SQL +WHERE+ clause).
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :conditions => "factory = 'Seattle'"
+end
+-------------------------------------------------------
+
+You can also set conditions via a hash:
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :conditions => { :factory => 'Seattle' }
+end
+-------------------------------------------------------
+
+If you use a hash-style +:conditions+ option, then record creation via this association will be automatically scoped using the hash. In this case, using +@parts.assemblies.create+ or +@parts.assemblies.build+ will create orders where the factory column has the value "Seattle".
+
+===== +:order+
+
+The +:order+ option dictates the order in which associated objects will be received (in the syntax used by a SQL +WHERE+ clause).
+
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :order => "assembly_name ASC"
+end
+-------------------------------------------------------
+
+===== +:uniq+
+
+Specify the +:uniq => true+ option to remove duplicates from the collection.
+
+===== +:finder_sql+
+
+Normally Rails automatically generates the proper SQL to fetch the association members. With the +:finder_sql+ option, you can specify a complete SQL statement to fetch them yourself. If fetching objects requires complex multi-table SQL, this may be necessary.
+
+===== +:counter_sql+
+
+Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
+
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+
+===== +:delete_sql+
+
+Normally Rails automatically generates the proper SQL to remove links between the associated classes. With the +:delete_sql+ option, you can specify a complete SQL statement to delete them yourself.
+
+===== +:insert_sql+
+
+Normally Rails automatically generates the proper SQL to create links between the associated classes. With the +:insert_sql+ option, you can specify a complete SQL statement to insert them yourself.
+
+===== +:extend+
+
+The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide.
+
+===== +:include+
+
+You can use the :include option to specify second-order associations that should be eager-loaded when this association is used.
+
+===== +:group+
+
+The +:group+ option supplies an attribute name to group the result set by, using a +GROUP BY+ clause in the finder SQL.
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :group => "factory"
+end
+-------------------------------------------------------
+
+===== +:limit+
+
+The +:limit+ option lets you restrict the total number of objects that will be fetched through an association.
+
+[source, ruby]
+-------------------------------------------------------
+class Parts < ActiveRecord::Base
+ has_and_belongs_to_many :assemblies, :order => "created_at DESC", :limit => 50
+end
+-------------------------------------------------------
+
+===== +:offset+
+
+The +:offset+ option lets you specify the starting offset for fetching objects via an association. For example, if you set +:offset => 11+, it will skip the first 10 records.
+
+===== +:select+
+
+The +:select+ option lets you override the SQL +SELECT+ clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns.
+
+===== +:readonly+
+
+If you set the +:readonly+ option to +true+, then the associated objects will be read-only when retrieved via the association.
+
+===== +:validate+
+
+If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved.
+
+===== +:accessible+
+
+The +:accessible+ option is the association version of +ActiveRecord::Base#attr_accessible+. If you set the +:accessible+ option to true, then mass assignment is allowed for this association.
+
+
+==== When are Objects Saved?
+
+When you assign an object to a +has_and_belongs_to_many+ association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
+
+If any of these saves fails due to validation errors, then the assignment statement returns +false+ and the assignment itself is cancelled.
+
+If the parent object (the one declaring the +has_and_belongs_to_many+ association) is unsaved (that is, +new_record?+ returns +true+) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved.
+
+If you want to assign an object to a +has_and_belongs_to_many+ association without saving the object, use the +_collection_.build+ method.
+
+=== Association Callbacks
+
+Normal callbacks hook into the lifecycle of Active Record objects, allowing you to work with those objects at various points. For example, you can use a +:before_save+ callback to cause something to happen just before an object is saved.
+
+Association callbacks are similar to normal callbacks, but they are triggered by events in the lifecycle of a collection. There are four available association callbacks:
+
+* before_add
+* after_add
+* before_remove
+* after_remove
+
+You define association callbacks by adding options to the association declaration. For example:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :before_add => :check_credit_limit
+
+ def check_credit_limit(order)
+ ...
+ end
+end
+-------------------------------------------------------
+
+Rails passes the object being added or removed to the callback.
+
+You can stack callbacks on a single event by passing them as an array:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :before_add => [:check_credit_limit, :calculate_shipping_charges]
+
+ def check_credit_limit(order)
+ ...
+ end
+
+ def calculate_shipping_charges(order)
+ ...
+ end
+end
+-------------------------------------------------------
+
+If a +before_add+ callback throws an exception, the object does not get added to the collection. Similarly, if a +before_remove+ callback throws an exception, the object does not get removed from the collection.
+
+=== Association Extensions
+
+You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders do
+ def find_by_order_prefix(order_number)
+ find_by_region_id(order_number[0..2])
+ end
+ end
+end
+-------------------------------------------------------
+
+If you have an extension that should be shared by many associations, you can use a named extension module. For example:
+
+[source, ruby]
+-------------------------------------------------------
+module FindRecentExtension
+ def find_recent
+ find(:all, :conditions => ["created_at > ?", 5.days.ago])
+ end
+end
+
+class Customer < ActiveRecord::Base
+ has_many :orders, :extend => FindRecentExtension
+end
+
+class Supplier < ActiveRecord::Base
+ has_many :deliveries, :extend => FindRecentExtension
+end
+-------------------------------------------------------
+
+To include more than one extension module in a single association, specify an array of names:
+
+[source, ruby]
+-------------------------------------------------------
+class Customer < ActiveRecord::Base
+ has_many :orders, :extend => [FindRecentExtension, FindActiveExtension]
+end
+-------------------------------------------------------
+
+Extensions can refer to the internals of the association proxy using these three accessors:
+
+* +proxy_owner+ returns the object that the association is a part of.
+* +proxy_reflection+ returns the reflection object that describes the association.
+* +proxy_target+ returns the associated object for +belongs_to+ or +has_one+, or the collection of associated objects for +has_many+ or +has_and_belongs_to_many+.
diff --git a/railties/doc/guides/debugging/debugging_rails_applications.txt b/railties/doc/guides/debugging/debugging_rails_applications.txt
new file mode 100644
index 0000000000..eb1135d094
--- /dev/null
+++ b/railties/doc/guides/debugging/debugging_rails_applications.txt
@@ -0,0 +1,604 @@
+Debugging Rails applications
+============================
+
+This guide covers how to debug Ruby on Rails applications. By referring to this guide, you will be able to:
+
+* Understand the purpose of debugging
+* Track down problems and issues in your application that your tests aren't identifying
+* Learn the different ways of debugging
+* Analyze the stack trace
+
+== View helpers for debugging
+
+=== debug
+
+`debug` will return a <pre>-tag that has object dumped by YAML. Generating readable output to inspect any object.
+
+[source, html]
+----------------------------------------------------------------------------
+<%= debug @post %>
+<p>
+ <b>Title:</b>
+ <%=h @post.title %>
+</p>
+----------------------------------------------------------------------------
+
+Will render something like this:
+
+----------------------------------------------------------------------------
+--- !ruby/object:Post
+attributes:
+ updated_at: 2008-09-05 22:55:47
+ body: It's a very helpful guide for debugging your Rails app.
+ title: Rails debugging guide
+ published: t
+ id: "1"
+ created_at: 2008-09-05 22:55:47
+attributes_cache: {}
+
+
+Title: Rails debugging guide
+----------------------------------------------------------------------------
+
+
+=== do it yourself
+
+Displaying an instance variable, or any other object or method, in yaml format can be achieved this way:
+
+[source, html]
+----------------------------------------------------------------------------
+<%= simple_format @post.to_yaml %>
+<p>
+ <b>Title:</b>
+ <%=h @post.title %>
+</p>
+----------------------------------------------------------------------------
+
+`to_yaml` converts the method to yaml format leaving it more readable and finally `simple_format` help us to render each line as in the console. This is how `debug` method does its magic.
+
+As a result of this, you will have something like this in your view:
+
+----------------------------------------------------------------------------
+--- !ruby/object:Post
+attributes:
+updated_at: 2008-09-05 22:55:47
+body: It's a very helpful guide for debugging your Rails app.
+title: Rails debugging guide
+published: t
+id: "1"
+created_at: 2008-09-05 22:55:47
+attributes_cache: {}
+
+Title: Rails debugging guide
+----------------------------------------------------------------------------
+
+Another useful method for displaying object values is `inspect`, especially when working with arrays or hashes, it will print the object value as a string, for example:
+
+[source, html]
+----------------------------------------------------------------------------
+<%= [1, 2, 3, 4, 5].inspect %>
+<p>
+ <b>Title:</b>
+ <%=h @post.title %>
+</p>
+----------------------------------------------------------------------------
+
+Will be rendered as follows:
+
+----------------------------------------------------------------------------
+[1, 2, 3, 4, 5]
+
+Title: Rails debugging guide
+----------------------------------------------------------------------------
+
+== The logger
+
+=== What is it?
+
+Rails makes use of ruby’s standard `logger`, `Log4r`, or another logger that provides a similar interface can also be substituted if you wish.
+
+If you want to change the logger you can specify it in your `environment.rb` or any environment file.
+
+[source, ruby]
+----------------------------------------------------------------------------
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
+----------------------------------------------------------------------------
+
+Or in the `__Initializer__` section, add _any_ of the following
+
+[source, ruby]
+----------------------------------------------------------------------------
+config.logger = Logger.new(STDOUT)
+config.logger = Log4r::Logger.new("Application Log")
+----------------------------------------------------------------------------
+
+[TIP]
+By default, each log is created under `RAILS_ROOT/log/` and the log file name is `environment_name.log`.
+
+=== Log levels
+
+When something is logged it's printed into the corresponding log if the message log level is equal or higher than the configured log level. If you want to know the current log level just call `ActiveRecord::Base.logger.level` method.
+
+The available log levels are: +:debug+, +:info+, +:warn+, +:error+, +:fatal+, each level has a log level number from 0 up to 4 respectively. To change the default log level, use
+
+[source, ruby]
+----------------------------------------------------------------------------
+config.log_level = Logger::WARN # In any environment initializer, or
+ActiveRecord::Base.logger.level = 0 # at any time
+----------------------------------------------------------------------------
+
+This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information.
+
+[TIP]
+Rails default log level is +info+ in production mode and +debug+ in development and test mode.
+
+=== Sending messages
+
+To write in the current log use the `logger.(debug|info|warn|error|fatal)` method from within a controller, model or mailer:
+
+[source, ruby]
+----------------------------------------------------------------------------
+logger.debug "Person attributes hash: #{@person.attributes.inspect}"
+logger.info "Processing the request..."
+logger.fatal "Terminating application, raised unrecoverable error!!!"
+----------------------------------------------------------------------------
+
+A common example:
+
+[source, ruby]
+----------------------------------------------------------------------------
+class PostsController < ApplicationController
+ # ...
+
+ def create
+ @post = Post.new(params[:post])
+ logger.debug "New post: #{@post.attributes.inspect}"
+ logger.debug "Post should be valid: #{@post.valid?}"
+
+ if @post.save
+ flash[:notice] = 'Post was successfully created.'
+ logger.debug "The post was saved and now is the user is going to be redirected..."
+ redirect_to(@post)
+ else
+ render :action => "new"
+ end
+ end
+
+ # ...
+end
+----------------------------------------------------------------------------
+
+Will be logged like this:
+
+----------------------------------------------------------------------------
+Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
+ Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
+ Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"}
+New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil}
+Post should be valid: true
+ Post Create (0.000443) INSERT INTO "posts" ("updated_at", "title", "body", "published", "created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails', 'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54')
+The post was saved and now is the user is going to be redirected...
+Redirected to #<Post:0x20af760>
+Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
+----------------------------------------------------------------------------
+
+Notice the logged lines, now you can search for any unexpected behavior in the output.
+
+By now you should know how to use the logs in any environment. Remember to take advantage of the log levels and use them wisely, mostly in production mode.
+
+== Debugging with ruby-debug
+
+Many times your code may not behave as you expect, sometimes you will try to print in logs, console or view values to make a diagnostic of the problem.
+
+Unfortunately, you won't find always the answer you are looking for this way. In that case, you will need to know what's happening and adventure into Rails, in this journey the debugger will be your best companion.
+
+If you ever wanted to learn about Rails source code but you didn't know where to start, this may be the best way, just debug any request to your application and use this guide to learn how to move in the code you have written but also go deeper into Rails code.
+
+=== Setup
+
+Ruby-debug comes as a gem so to install, just run:
+
+----------------------------------------------------------------------------
+$ sudo gem in ruby-debug
+----------------------------------------------------------------------------
+
+In case you want to download a particular version or get the source code, refer to link:http://rubyforge.org/projects/ruby-debug/[project's page on rubyforge].
+
+Rails has built-in support for ruby-debug since April 28, 2007. Inside any Rails application you can invoke the debugger by calling the `debugger` method.
+
+Let's take a look at an example:
+
+[source, ruby]
+----------------------------------------------------------------------------
+class PeopleController < ApplicationController
+ def new
+ debugger
+ @person = Person.new
+ end
+end
+----------------------------------------------------------------------------
+
+If you see the message in the console or logs:
+
+----------------------------------------------------------------------------
+***** Debugger requested, but was not available: Start server with --debugger to enable *****
+----------------------------------------------------------------------------
+
+Make sure you have started your web server with the option --debugger:
+
+----------------------------------------------------------------------------
+~/PathTo/rails_project$ script/server --debugger
+----------------------------------------------------------------------------
+
+[TIP]
+In development mode, you can dynamically `require \'ruby-debug\'` instead of restarting the server, in case it was started without `--debugger`.
+
+In order to use Rails debugging you'll need to be running either *WEBrick* or *Mongrel*. For the moment, no alternative servers are supported.
+
+=== The shell
+
+As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window you've fired up your application server and you will be placed in the ruby-debug's prompt `(rdb:n)`. The _n_ is the thread number.
+
+If you got there by a browser request, the browser will be hanging until the debugger has finished and the trace has completely run as any normal request.
+
+For example:
+
+----------------------------------------------------------------------------
+@posts = Post.find(:all)
+(rdb:7)
+----------------------------------------------------------------------------
+
+Now it's time to play and dig into our application. The first we are going to do is ask our debugger for help... so we type: `help` (You didn't see that coming, right?)
+
+----------------------------------------------------------------------------
+(rdb:7) help
+ruby-debug help v0.10.2
+Type 'help <command-name>' for help on a specific command
+
+Available commands:
+backtrace delete enable help next quit show trace
+break disable eval info p reload source undisplay
+catch display exit irb pp restart step up
+condition down finish list ps save thread var
+continue edit frame method putl set tmate where
+----------------------------------------------------------------------------
+
+[TIP]
+To view the help menu for any command use `help <command-name>` in active debug mode. For example: _help var_
+
+The second command before we move on, is one of the most useful command: `list` (or his shorthand `l`).
+
+This command will give us a starting point of where we are by printing 10 lines centered around the current line; the current line here is line 6 and is marked by =>.
+
+----------------------------------------------------------------------------
+(rdb:7) list
+[1, 10] in /PathToProject/posts_controller.rb
+ 1 class PostsController < ApplicationController
+ 2 # GET /posts
+ 3 # GET /posts.xml
+ 4 def index
+ 5 debugger
+=> 6 @posts = Post.find(:all)
+ 7
+ 8 respond_to do |format|
+ 9 format.html # index.html.erb
+ 10 format.xml { render :xml => @posts }
+----------------------------------------------------------------------------
+
+If we do it again, this time using just `l`, the next ten lines of the file will be printed out.
+
+----------------------------------------------------------------------------
+(rdb:7) l
+[11, 20] in /PathTo/project/app/controllers/posts_controller.rb
+ 11 end
+ 12 end
+ 13
+ 14 # GET /posts/1
+ 15 # GET /posts/1.xml
+ 16 def show
+ 17 @post = Post.find(params[:id])
+ 18
+ 19 respond_to do |format|
+ 20 format.html # show.html.erb
+----------------------------------------------------------------------------
+
+And so on until the end of the current file, when the end of file is reached, it will start again from the beginning of the file and continue again up to the end, acting as a circular buffer.
+
+=== The context
+When we start debugging your application, we will be placed in different contexts as you go through the different parts of the stack.
+
+A context will be created when a stopping point or an event is reached. It has information about the suspended program which enable a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place the debugged program is stopped.
+
+At any time we can call the `backtrace` command (or alias `where`) to print the backtrace of the application, this is very helpful to know how we got where we are. If you ever wondered about how you got somewhere in your code, then `backtrace` is your answer.
+
+----------------------------------------------------------------------------
+(rdb:5) where
+ #0 PostsController.index
+ at line /PathTo/project/app/controllers/posts_controller.rb:6
+ #1 Kernel.send
+ at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
+ #2 ActionController::Base.perform_action_without_filters
+ at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
+ #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...)
+ at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617
+...
+----------------------------------------------------------------------------
+
+You move anywhere you want in this trace using the `frame _n_` command, where _n_ is the specified frame number.
+
+----------------------------------------------------------------------------
+(rdb:5) frame 2
+#2 ActionController::Base.perform_action_without_filters
+ at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
+----------------------------------------------------------------------------
+
+The available variables are the same as if we were running the code line by line, after all, that's what debugging is.
+
+Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one.
+
+=== Threads
+
+The debugger can list, stop, resume and switch between running threads, the command `thread` (or the abbreviated `th`) is used an allows the following options:
+
+* `thread` shows the current thread.
+* `thread list` command is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution.
+* `thread stop _n_` stop thread _n_.
+* `thread resume _n_` resume thread _n_.
+* `thread switch _n_` switch thread context to _n_.
+
+This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code.
+
+=== Inspecting variables
+
+Any expression can be evaluated in the current context, just type it!
+
+In the following example we will print the instance_variables defined within the current context.
+
+----------------------------------------------------------------------------
+@posts = Post.find(:all)
+(rdb:11) instance_variables
+["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"]
+----------------------------------------------------------------------------
+
+As you may have figured out, all variables that you can access from a controller are displayed, lets run the next line, we will use `next` (we will get later into this command).
+
+----------------------------------------------------------------------------
+(rdb:11) next
+Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET]
+ Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e
+ Parameters: {"action"=>"index", "controller"=>"posts"}
+/PathToProject/posts_controller.rb:8
+respond_to do |format|
+-------------------------------------------------------------------------------
+
+And we'll ask again for the instance_variables.
+
+----------------------------------------------------------------------------
+(rdb:11) instance_variables.include? "@posts"
+true
+----------------------------------------------------------------------------
+
+Now +@posts+ is a included in them, because the line defining it was executed.
+
+[TIP]
+You can also step into *irb* mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But you must know that this is an experimental feature.
+
+To show variables and their values the `var` method is the most convenient way:
+
+----------------------------------------------------------------------------
+var
+(rdb:1) v[ar] const <object> show constants of object
+(rdb:1) v[ar] g[lobal] show global variables
+(rdb:1) v[ar] i[nstance] <object> show instance variables of object
+(rdb:1) v[ar] l[ocal] show local variables
+----------------------------------------------------------------------------
+
+This is a great way for inspecting the values of the current context variables. For example:
+
+----------------------------------------------------------------------------
+(rdb:9) var local
+ __dbg_verbose_save => false
+----------------------------------------------------------------------------
+
+You can also inspect for an object method this way:
+
+----------------------------------------------------------------------------
+(rdb:9) var instance Post.new
+@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"...
+@attributes_cache = {}
+@new_record = true
+----------------------------------------------------------------------------
+
+[TIP]
+Commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console.
+
+We can use also `display` to start watching variables, this is a good way of tracking values of a variable while the execution goes on.
+
+----------------------------------------------------------------------------
+(rdb:1) display @recent_comments
+1: @recent_comments =
+----------------------------------------------------------------------------
+
+The variables inside the displaying list will be printed with their values after we move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example).
+
+=== Step by step
+
+Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution.
+
+Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to ruby-debug.
+
+[TIP]
+You can also use `step+ _n_` and `step- _n_` to move forward or backward _n_ steps respectively.
+
+You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps.
+
+The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just single step, while `next` moves to the next line without descending inside methods.
+
+Lets run the next line in this example:
+
+[source, ruby]
+----------------------------------------------------------------------------
+class Author < ActiveRecord::Base
+ has_one :editorial
+ has_many :comments
+
+ def find_recent_comments(limit = 10)
+ debugger
+ @recent_comments ||= comments.find(
+ :all,
+ :conditions => ["created_at > ?", 1.week.ago],
+ :limit => limit
+ )
+ end
+end
+----------------------------------------------------------------------------
+
+[TIP]
+You can use ruby-debug while using script/console but remember to `require "ruby-debug"` before calling `debugger` method.
+
+----------------------------------------------------------------------------
+/PathTo/project $ script/console
+Loading development environment (Rails 2.1.0)
+>> require "ruby-debug"
+=> []
+>> author = Author.first
+=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
+>> author.find_recent_comments
+/PathTo/project/app/models/author.rb:11
+)
+----------------------------------------------------------------------------
+
+Now we are where we wanted to be, lets look around.
+
+----------------------------------------------------------------------------
+(rdb:1) list
+[6, 15] in /PathTo/project/app/models/author.rb
+ 6 debugger
+ 7 @recent_comments ||= comments.find(
+ 8 :all,
+ 9 :conditions => ["created_at > ?", 1.week.ago],
+ 10 :limit => limit
+=> 11 )
+ 12 end
+ 13 end
+----------------------------------------------------------------------------
+
+We are at the end of the line, but... was this line executed? We can inspect the instance variables.
+
+----------------------------------------------------------------------------
+(rdb:1) var instance
+@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
+@attributes_cache = {}
+----------------------------------------------------------------------------
+
++@recent_comments+ hasn't been defined yet, so we can assure this line hasn't been executed yet, lets move on this code.
+
+----------------------------------------------------------------------------
+(rdb:1) next
+/PathTo/project/app/models/author.rb:12
+@recent_comments
+(rdb:1) var instance
+@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
+@attributes_cache = {}
+@comments = []
+@recent_comments = []
+----------------------------------------------------------------------------
+
+Now we can see how +@comments+ relationship was loaded and @recent_comments defined because the line was executed.
+
+In case we want deeper in the stack trace we can move single `steps` and go into Rails code, this is the best way for finding bugs in your code, or maybe in Ruby or Rails.
+
+=== Breakpoints
+
+A breakpoint makes your application stop whenever a certain point in the program is reached and the debugger shell is invoked in that line.
+
+You can add breakpoints dynamically with the command `break` (or just `b`), there are 3 possible ways of adding breakpoints manually:
+
+* `break line`: set breakpoint in the _line_ in the current source file.
+* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger.
+* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line.
+
+----------------------------------------------------------------------------
+(rdb:5) break 10
+Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10
+----------------------------------------------------------------------------
+
+Use `info breakpoints _n_` or `info break _n_` lo list breakpoints, is _n_ is defined it shows that breakpoints, otherwise all breakpoints are listed.
+
+----------------------------------------------------------------------------
+(rdb:5) info breakpoints
+Num Enb What
+ 1 y at filters.rb:10
+----------------------------------------------------------------------------
+
+Deleting breakpoints: use the command `delete _n_` to remove the breakpoint number _n_ or all of them if _n_ is not specified.
+
+----------------------------------------------------------------------------
+(rdb:5) delete 1
+(rdb:5) info breakpoints
+No breakpoints.
+----------------------------------------------------------------------------
+
+Enabling/Disabling breakpoints:
+
+* `enable breakpoints`: allow a list _breakpoints_ or all of them if none specified, to stop your program (this is the default state when you create a breakpoint).
+* `disable breakpoints`: the _breakpoints_ will have no effect on your program.
+
+=== Catching Exceptions
+
+The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it.
+
+To list existent catchpoints use `catch`.
+
+=== Resuming Execution
+
+* `continue` [line-specification] (or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached.
+* `finish` [frame-number] (or `fin`): execute until selected stack frame returns. If no frame number is given, we run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given we run until frame frames returns.
+
+=== Editing
+
+At any time, you may use any of this commands to edit the code you are evaluating:
+
+* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given.
+* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified.
+
+=== Quitting
+To exit the debugger, use the `quit` command (abbreviated `q`), or alias `exit`.
+
+A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again.
+
+=== Settings
+
+There are some settings that can be configured in ruby-debug to make it easier to debug your code, being among others useful options:
+
+* `set reload`: Reload source code when changed.
+* `set autolist`: Execute `list` command on every breakpoint.
+* `set listsize _n_`: Set number of source lines to list by default _n_.
+* `set forcestep`: Make sure `next` and `step` commands always move to a new line
+
+You can see the full list by using `help set` or `help set subcommand` to inspect any of them.
+
+[TIP]
+You can include any number of this configuration lines inside a `.rdebugrc` file in your HOME directory, and ruby-debug will read it every time it is loaded
+
+The following lines are recommended to be included in `.rdebugrc`:
+
+----------------------------------------------------------------------------
+set autolist
+set forcestep
+set listsize 25
+----------------------------------------------------------------------------
+
+
+== References
+
+* link:http://www.datanoise.com/ruby-debug[ruby-debug Homepage]
+* link:http://www.sitepoint.com/article/debug-rails-app-ruby-debug/[Article: Debugging a Rails application with ruby-debug]
+* link:http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/[ruby-debug Basics screencast]
+* link:http://railscasts.com/episodes/54-debugging-with-ruby-debug[Ryan Bate's ruby-debug screencast]
+* link:http://railscasts.com/episodes/24-the-stack-trace[Ryan Bate's stack trace screencast]
+* link:http://railscasts.com/episodes/56-the-logger[Ryan Bate's logger screencast]
+* link:http://bashdb.sourceforge.net/ruby-debug.html[Debugging with ruby-debug]
+* link:http://cheat.errtheblog.com/s/rdebug/[ruby-debug cheat sheet]
+* link:http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging[Ruby on Rails Wiki: How to Configure Logging] \ No newline at end of file
diff --git a/railties/doc/guides/forms/form_helpers.txt b/railties/doc/guides/forms/form_helpers.txt
new file mode 100644
index 0000000000..7b0aeb0ed9
--- /dev/null
+++ b/railties/doc/guides/forms/form_helpers.txt
@@ -0,0 +1,270 @@
+Rails form helpers
+==================
+Mislav Marohnić <mislav.marohnic@gmail.com>
+
+Forms in web applications are an essential interface for user input. They are also often considered the most complex elements of HTML. Rails deals away with these complexities by providing numerous view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
+
+In this guide we will:
+
+* Create search forms and similar kind of generic forms not representing any specific model in your application;
+* Make model-centric forms for creation and editing of specific database records;
+* Generate select boxes from multiple types of data;
+* Learn what makes a file upload form different;
+* Build complex, multi-model forms.
+
+NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit http://api.rubyonrails.org/[the Rails API documentation] for a complete reference.
+
+
+Basic forms
+-----------
+
+The most basic form helper is `form_tag`.
+
+----------------------------------------------------------------------------
+<% form_tag do %>
+ Form contents
+<% end %>
+----------------------------------------------------------------------------
+
+When called without arguments like this, it creates a form element that has the current page for action attribute and "POST" as method (some line breaks added for readability):
+
+.Sample rendering of `form_tag`
+----------------------------------------------------------------------------
+<form action="/home/index" method="post">
+ <div style="margin:0;padding:0">
+ <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
+ </div>
+ Form contents
+</form>
+----------------------------------------------------------------------------
+
+If you carefully observe this output, you can see that the helper generated something we didn't specify: a `div` element with a hidden input inside. This is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form which action isn't "GET" (provided that this security feature is enabled).
+
+NOTE: Throughout this guide, this `div` with the hidden input will be stripped away to have clearer code samples.
+
+Generic search form
+~~~~~~~~~~~~~~~~~~~
+
+Probably the most minimal form often seen on the web is a search form with a single text input for search terms. This form consists of:
+
+1. a form element with "GET" method,
+2. a label for the input,
+3. a text input element, and
+4. a submit element.
+
+IMPORTANT: Always use "GET" as the method for search forms. Benefits are many: users are able to bookmark a specific search and get back to it; browsers cache results of "GET" requests, but not "POST"; and other.
+
+To create that, we will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively.
+
+.A basic search form
+----------------------------------------------------------------------------
+<% form_tag(search_path, :method => "get") do %>
+ <%= label_tag(:q, "Search for:") %>
+ <%= text_field_tag(:q) %>
+ <%= submit_tag("Search") %>
+<% end %>
+----------------------------------------------------------------------------
+
+[TIP]
+============================================================================
+`search_path` can be a named route specified in "routes.rb":
+
+----------------------------------------------------------------------------
+map.search "search", :controller => "search"
+----------------------------------------------------------------------------
+============================================================================
+
+The above view code will result in the following markup:
+
+.Search form HTML
+----------------------------------------------------------------------------
+<form action="/search" method="get">
+ <label for="q">Search for:</label>
+ <input id="q" name="q" type="text" />
+ <input name="commit" type="submit" value="Search" />
+</form>
+----------------------------------------------------------------------------
+
+Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML.
+
+TIP: For every form input, an ID attribute is generated from its name ("q" in our example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
+
+Multiple hashes in form helper attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By now we've seen that the `form_tag` helper accepts 2 arguments: the path for the action attribute and an options hash for parameters (like `:method`).
+
+Identical to the `link_to` helper, the path argument doesn't have to be given as string or a named route. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. Still, we cannot simply write this:
+
+.A bad way to pass multiple hashes as method arguments
+----------------------------------------------------------------------------
+form_tag(:controller => "people", :action => "search", :method => "get")
+# => <form action="/people/search?method=get" method="post">
+----------------------------------------------------------------------------
+
+Here we wanted to pass two hashes, but the Ruby interpreter sees only one hash, so Rails will construct a URL that we didn't want. The solution is to delimit the first hash (or both hashes) with curly brackets:
+
+.The correct way of passing multiple hashes as arguments
+----------------------------------------------------------------------------
+form_tag({:controller => "people", :action => "search"}, :method => "get")
+# => <form action="/people/search" method="get">
+----------------------------------------------------------------------------
+
+This is a common pitfall when using form helpers, since many of them accept multiple hashes. So in future, if a helper produces unexpected output, make sure that you have delimited the hash parameters properly.
+
+WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an ugly `expecting tASSOC` syntax error.
+
+Checkboxes, radio buttons and other controls
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Checkboxes are form controls that give the user a set of options they can enable or disable:
+
+----------------------------------------------------------------------------
+<%= check_box_tag(:pet_dog) %>
+ <%= label_tag(:pet_dog, "I own a dog") %>
+<%= check_box_tag(:pet_cat) %>
+ <%= label_tag(:pet_cat, "I own a cat") %>
+
+output:
+
+<input id="pet_dog" name="pet_dog" type="checkbox" value="1" />
+ <label for="pet_dog">I own a dog</label>
+<input id="pet_cat" name="pet_cat" type="checkbox" value="1" />
+ <label for="pet_cat">I own a cat</label>
+----------------------------------------------------------------------------
+
+Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (user can only pick one):
+
+----------------------------------------------------------------------------
+<%= radio_button_tag(:age, "child") %>
+ <%= label_tag(:age_child, "I am younger than 21") %>
+<%= radio_button_tag(:age, "adult") %>
+ <%= label_tag(:age_adult, "I'm over 21") %>
+
+output:
+
+<input id="age_child" name="age" type="radio" value="child" />
+ <label for="age_child">I am younger than 21</label>
+<input id="age_adult" name="age" type="radio" value="adult" />
+ <label for="age_adult">I'm over 21</label>
+----------------------------------------------------------------------------
+
+IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option, while also providing a larger clickable region.
+
+Other form controls we might mention are the text area, password input and hidden input:
+
+----------------------------------------------------------------------------
+<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
+<%= password_field_tag(:password) %>
+<%= hidden_field_tag(:parent_id, "5") %>
+
+output:
+
+<textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea>
+<input id="password" name="password" type="password" />
+<input id="parent_id" name="parent_id" type="hidden" value="5" />
+----------------------------------------------------------------------------
+
+Hidden inputs are not shown to the user, but they hold data same as any textual input. Values inside them can be changed with JavaScript.
+
+TIP: If you're using password input fields (for any purpose), you might want to prevent their values showing up in application logs by activating `filter_parameter_logging(:password)` in your ApplicationController.
+
+How do forms with PUT or DELETE methods work?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). Still, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. How does this work, then?
+
+Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"` that is set to reflect the _real_ method:
+
+----------------------------------------------------------------------------
+form_tag(search_path, :method => "put")
+
+output:
+
+<form action="/search" method="post">
+ <div style="margin:0;padding:0">
+ <input name="_method" type="hidden" value="put" />
+ <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
+ </div>
+ ...
+----------------------------------------------------------------------------
+
+When parsing POSTed data, Rails will take into account the special `"_method"` parameter and act as if the HTTP method was the one specified inside it ("PUT" in this example).
+
+
+Forms that deal with model attributes
+-------------------------------------
+
+When we're dealing with an actual model, we will use a different set of form helpers and have Rails take care of some details in the background. In the following examples we will handle an Article model. First, let us have the controller create one:
+
+.articles_controller.rb
+----------------------------------------------------------------------------
+def new
+ @article = Article.new
+end
+----------------------------------------------------------------------------
+
+Now we switch to the view. The first thing to remember is that we should use `form_for` helper instead of `form_tag`, and that we should pass the model name and object as arguments:
+
+.articles/new.html.erb
+----------------------------------------------------------------------------
+<% form_for :article, @article, :url => { :action => "create" } do |f| %>
+ <%= f.text_field :title %>
+ <%= f.text_area :body, :size => "60x12" %>
+ <%= submit_tag "Create" %>
+<% end %>
+----------------------------------------------------------------------------
+
+There are a few things to note here:
+
+1. `:article` is the name of the model and `@article` is our record.
+2. The URL for the action attribute is passed as a parameter named `:url`.
+3. The `form_for` method yields *a form builder* object (the `f` variable).
+4. Methods to create form controls are called *on* the form builder object `f` and *without* the `"_tag"` suffix (so `text_field_tag` becomes `f.text_field`).
+
+The resulting HTML is:
+
+----------------------------------------------------------------------------
+<form action="/articles/create" method="post">
+ <input id="article_title" name="article[title]" size="30" type="text" />
+ <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
+ <input name="commit" type="submit" value="Create" />
+</form>
+----------------------------------------------------------------------------
+
+A nice thing about `f.text_field` and other helper methods is that they will pre-fill the form control with the value read from the corresponding attribute in the model. For example, if we created the article instance by supplying an initial value for the title in the controller:
+
+----------------------------------------------------------------------------
+@article = Article.new(:title => "Rails makes forms easy")
+----------------------------------------------------------------------------
+
+... the corresponding input will be rendered with a value:
+
+----------------------------------------------------------------------------
+<input id="post_title" name="post[title]" size="30" type="text" value="Rails makes forms easy" />
+----------------------------------------------------------------------------
+
+Relying on record identification
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the previous chapter we handled the Article model. This model is directly available to users of our application and, following the best practices for developing with Rails, we should declare it *a resource*.
+
+When dealing with RESTful resources, our calls to `form_for` can get significantly easier if we rely on *record identification*. In short, we can just pass the model instance and have Rails figure out model name and the rest:
+
+----------------------------------------------------------------------------
+## Creating a new article
+# long-style:
+form_for(:article, @article, :url => articles_path)
+# same thing, short-style (record identification gets used):
+form_for(@article)
+
+## Editing an existing article
+# long-style:
+form_for(:article, @article, :url => article_path(@article), :method => "put")
+# short-style:
+form_for(@article)
+----------------------------------------------------------------------------
+
+Notice how the short-style `form_for` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`.
+
+WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, `:url` and `:method` explicitly. \ No newline at end of file
diff --git a/railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt b/railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt
new file mode 100644
index 0000000000..2805e5629d
--- /dev/null
+++ b/railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt
@@ -0,0 +1,348 @@
+Getting Started With Rails
+==========================
+
+This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with:
+
+* Installing Rails, creating a new Rails application, and connecting your application to a database
+* Understanding the purpose of each folder in the Rails structure
+* Creating a scaffold, and explain what it is creating and why you need each element
+* The basics of model, view, and controller interaction
+* The basics of HTTP and RESTful design
+
+== How to use this guide
+This guide is designed for beginners who want to get started with a Rails application from scratch. It assumes that you have no prior experience using the framework. However, it is highly recommended that you *familiarize yourself with Ruby before diving into Rails*. Rails isn't going to magically revolutionize the way you write web applications if you have no experience with the language it uses.
+
+== What is Rails?
+Rails is a web development framework written in the Ruby language. It is designed to make programming web applications easier by making several assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than other languages and frameworks.
+
+== Installing Rails
+
+`gem install rails`
+
+== Create a new Rails project
+
+We're going to create a Rails project called "blog", which is the project that we will build off of for this guide.
+
+From your terminal, type:
+
+`rails blog`
+
+This will create a folder in your working directory called "blog". Open up that folder and have a look at it. For the majority of this tutorial, we will live in the app/ folder, but here's a basic rundown on the function of each folder in a Rails app:
+
+[grid="all"]
+`-----------`-----------------------------------------------------------------------------------------------------------------------------
+File/Folder Purpose
+------------------------------------------------------------------------------------------------------------------------------------------
+README This is a brief instruction manual for your application. Use it to tell others what it does, how to set it up, etc.
+Rakefile
+app/ Contains the controllers, models, and views for your application. We'll focus on the app folder in this guide
+config/ Configure your application's runtime rules, routes, database, etc.
+db/ Shows your current database schema, as well as the database migrations (we'll get into migrations shortly)
+doc/ In-depth documentation for your application
+lib/ Extended modules for your application (not covered in this guide)
+log/ Application log files
+public/ The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go
+script/ Scripts provided by Rails to do recurring tasks, benchmarking, plugin installation, starting the console or the web server
+test/ Unit tests, fixtures, etc. (not covered in this guide)
+tmp/ Temporary files
+vendor/ Plugins folder
+-------------------------------------------------------------------------------------------------------------------------------------------
+
+=== Configure SQLite Database
+
+Rails comes with built-in support for SQLite, which is a lightweight flat-file based database application. While it is not designed for a production environment, it works well for development and testing. Rails defaults to SQLite as the database adapter when creating a new project, but you can always change it later.
+
+Open up +config/database.yml+ and you'll see the following:
+
+--------------------------------------------------------------------
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ timeout: 5000
+--------------------------------------------------------------------
+
+If you're not running OS X 10.5 or greater, you'll need to install the SQLite gem. Similar to installing Rails you just need to run:
+
+`gem install sqlite3-ruby`
+
+Because we're using SQLite, there's really nothing else you need to do to setup your database!
+
+=== Configure MySQL Database
+
+.MySQL Tip
+*******************************
+If you want to skip directly to using MySQL on your development machine, typing the following will get you setup with a MySQL configuration file that assumes MySQL is running locally and that the root password is blank:
+
+`rails blog -d mysql`
+
+You'll need to make sure you have MySQL up and running on your system with the correct permissions. MySQL installation and configuration is outside the scope of this document.
+*******************************
+
+If you choose to use MySQL, your +config/database.yml+ will look a little different:
+
+--------------------------------------------------------------------
+# MySQL. Versions 4.1 and 5.0 are recommended.
+#
+# Install the MySQL driver:
+# gem install mysql
+# On Mac OS X:
+# sudo gem install mysql -- --with-mysql-dir=/usr/local/mysql
+# On Mac OS X Leopard:
+# sudo env ARCHFLAGS="-arch i386" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+# This sets the ARCHFLAGS environment variable to your native architecture
+# On Windows:
+# gem install mysql
+# Choose the win32 build.
+# Install MySQL and put its /bin directory on your path.
+#
+# And be sure to use new-style password hashing:
+# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+development:
+ adapter: mysql
+ encoding: utf8
+ database: blog_development
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: mysql
+ encoding: utf8
+ database: blog_test
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+
+production:
+ adapter: mysql
+ encoding: utf8
+ database: blog_production
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+----------------------------------------------------------------------
+
+== Starting the web server
+Rails comes bundled with the lightweight Webrick web server, which (like SQLite) works great in development mode, but is not designed for a production environment. If you install Mongrel with `gem install mongrel`, Rails will use the Mongrel web server as the default instead (recommended).
+*******************
+If you're interested in alternative web servers for development and/or production, check out mod_rails (a.k.a Passenger)
+*******************
+Rails lets you run in development, test, and production environments (you can also add an unlimited number of additional environments if necessary). In this guide, we're going to work with the development environment only, which is the default when starting the server. From the root of your application folder, simply type the following to startup the web server:
+
+`./script/server`
+
+This will start a process that allows you to connect to your application via a web browser on port 3000. Open up a browser to +http://localhost:3000/+
+
+You can hit Ctrl+C anytime from the terminal to stop the web server.
+
+You should see the "Welcome Aboard" default Rails screen, and can click on the "About your application's environment" link to see a brief summary of your current configuration. If you've gotten this far, you're riding rails! Let's dive into the code!
+
+== Models, Views, and Controllers
+Rails uses Model, View, Controller (MVC) architecture because it isolates business logic from the user interface, ensuring that changes to a template will not affect the underlying code that makes it function. It also helps keep your code clean and DRY (Don't Repeat Yourself!) by making it perfectly clear where different types of code belong.
+
+=== The Model
+The model represents the information (data) of the application and the rules to manipulate that data. In the case of Rails, models are primarily used for managing the rules of interaction with a corresponding database table. Assume that for every table in your database, you will have a corresponding model (not necessarily the other way around, but that's beyond the scope of this guide).
+
+Models in Rails use a singular name, and their corresponding database tables use a plural name. In the case of our "Blog" application, we're going to need a table for our blog posts. Because we're generating a model, we want to use the singular name:
+
+`./script/generate model Post`
+
+You'll see that this generates several files, we're going to focus on two. First, let's take a look at +app/models/post.rb+
+
+-------------------------------
+class Post < ActiveRecord::Base
+end
+-------------------------------
+
+This is what each model you create will look like by default. Here Rails is making the assumption that your Post model will be tied to a database, because it is telling the Post class to descend from the ActiveRecord::Base class, which is where all the database magic happens. Let's leave the model alone for now and move onto migrations.
+
+==== Migrations
+Database migrations make it simple to add/remove/modify tables, columns, and indexes while allowing you to roll back or forward between states with ease.
+
+Have a look at +db/migrate/2008XXXXXXXXXX_create_posts.rb+ (Yours will have numbers specific to the time that the file was generated), which was generated when creating our Post model:
+
+-------------------------------------------
+class CreatePosts < ActiveRecord::Migration
+ def self.up
+ create_table :posts do |t|
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :posts
+ end
+end
+-------------------------------------------
+
+By default, Rails creates a database migration that will create the table for "posts" (plural name of model). The +create_table+ method takes a ruby block, and by default you'll see +t.timestamps+ in there, which automatically creates and automatically handles +created_at+ and +updated_at+ datetime columns. The +self.up+ section handles progression of the database, whereas the +self.down+ handles regression (or rollback) of the migration.
+
+Let's add some more columns to our migration that suit our post table. We'll create a +name+ column for the person who wrote the post, a +title+ column for the title of the post, and a +content+ column for the actual post content.
+
+-------------------------------------------
+class CreatePosts < ActiveRecord::Migration
+ def self.up
+ create_table :posts do |t|
+ t.string :name
+ t.string :title
+ t.text :content
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :posts
+ end
+end
+-------------------------------------------
+
+Now that we have our migration just right, we can run the migration (the +self.up+ portion) by returning to the terminal and running:
+
+`rake db:migrate`
+
+This command will always run any migrations that have not yet been run.
+
+.Singular and Plural Inflections
+**************************************************************************************************************
+Rails is very smart, it knows that if you have a model "Person," the database table should be called "people". If you have a model "Company", the database table will be called "companies". There are a few circumstances where it will not know the correct singular and plural of a model name, but you should have no problem with this as long as you are using common English words. Fixing these rare circumstances is beyond the scope of this guide.
+**************************************************************************************************************
+
+=== The Controller
+The controller communicates input from the user (the view) to the model.
+
+==== RESTful Design
+The REST idea will likely take some time to wrap your brain around if you're new to the concept. But know the following:
+
+* It is best to keep your controllers RESTful at all times if possible
+* Resources must be defined in +config/routes.rb+ in order for the RESTful architecture to work properly, so let's add that now:
+
+--------------------
+map.resources :posts
+--------------------
+
+* The seven actions that are automatically part of the RESTful design in Rails are +index+, +show+, +new+, +create+, +edit+, +update+, and +destroy+.
+
+Let's generate a controller:
+
+`./script/generate controller Posts`
+
+Open up the controller that it generates in +app/controllers/posts_controller.rb+. It should look like:
+
+---------------------------------------------
+class PostsController < ApplicationController
+end
+---------------------------------------------
+
+Because of the +map.resources :posts+ line in your +config/routes.rb+ file, this controller is ready to take on all seven actions listed above. But we're going to need some logic in this controller in order to interact with the model, and we're going to need to generate our view files so the user can interact with your application from their browser.
+
+We're going to use the scaffold generator to create all the files and basic logic to make this work, now that you know how to generate models and controllers manually.
+
+To do that, let's completely start over. Back out of your Rails project folder, and *remove it completely* (`rm -rf blog`).
+Create the project again and enter the directory by running the commands:
+
+`rails blog`
+
+`cd blog`
+
+
+=== Rails Scaffold
+Whenever you are dealing with a resource and you know you'll need a way to manage that resource in your application, you can start by generating a scaffold. The reason that this guide did not start with generating the scaffold is because it is not all that useful once you are using Rails on a regular basis. For our blog, we want a "Post" resource, so let's generate that now:
+
+`./script/generate scaffold Post name:string title:string content:text`
+
+This generates the model, controller, migration, views, tests, and routes for this resource. It also populates these files with default data to get started.
+
+First, let's make sure our database is up to date by running `rake db:migrate`. That may generate an error if your database still has the tables from our earlier migration. In this case, let's completely reset the database and run all migrations by running `rake db:reset`.
+
+Start up the web server with `./script/server` and point your browser to `http://localhost:3000/posts`.
+
+Here you'll see an example of the instant gratification of Rails where you can completely manage the Post resource. You'll be able to create, edit, and delete blog posts with ease. Go ahead, try it out.
+
+Now let's see how all this works. Open up `app/controllers/posts_controller.rb`, and you'll see this time it is filled with code.
+
+==== Index
+
+Let's take a look at the `index` action:
+
+-----------------------------------------
+def index
+ @posts = Post.find(:all)
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @posts }
+ end
+end
+-----------------------------------------
+
+In this action, we're setting the `@posts` instance variable to a hash of all posts in the database. `Post.find(:all)` or `Post.all` (in Rails 2.1) calls on our model to return all the Posts in the database with no additional conditions.
+
+The `respond_to` block handles both HTML and XML calls to this action. If we call `http://localhost:3000/posts.xml`, we'll see all our posts in XML format. The HTML format looks for our corresponding view in `app/views/posts/index.html.erb`. You can add any number of formats to this block to allow actions to be processed with different file types.
+
+==== Show
+
+Back in your browser, click on the "New post" link and create your first post if you haven't done so already. Return back to the index, and you'll see the details of your post listed, along with three actions to the right of the post: `show`, `edit`, and `destroy`. Click the `show` link, which will bring you to the URL `http://localhost:3000/posts/1`. Now let's look at the `show` action in `app/controllers/posts_controller.rb`:
+
+-----------------------------------------
+def show
+ @post = Post.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @post }
+ end
+end
+-----------------------------------------
+
+This time, we're setting `@post` to a single record in the database that is searched for by its `id`, which is provided to the controller by the "1" in `http://localhost:3000/posts/1`. The `show` action is ready to handle HTML or XML with the `respond_to` block: XML can be accessed at: `http://localhost:3000/posts/1.xml`.
+
+==== New & Create
+
+Description of new and create actions
+
+==== Edit & Update
+
+For the `edit`, `update`, and `destroy` actions, we will use the same `@post = Post.find(params[:id])` to find the appropriate record.
+
+==== Destroy
+
+Description of the destroy action
+
+=== The View
+The view is where you put all the code that gets seen by the user: divs, tables, text, checkboxes, etc. Think of the view as the home of your HTML. If done correctly, there should be no business logic in the view.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/railties/doc/guides/index.txt b/railties/doc/guides/index.txt
new file mode 100644
index 0000000000..87d6804ead
--- /dev/null
+++ b/railties/doc/guides/index.txt
@@ -0,0 +1,53 @@
+Ruby on Rails guides
+====================
+
+.link:getting_started_with_rails/getting_started_with_rails.html[Getting Started with Rails]
+***********************************************************
+TODO: Insert some description here.
+***********************************************************
+
+.link:activerecord/association_basics.html[Active Record Associations]
+***********************************************************
+Introduction to Active Record associations.
+***********************************************************
+
+.link:migrations/migrations.html[Rails Database Migrations]
+***********************************************************
+TODO: Insert some description here.
+***********************************************************
+
+.link:forms/form_helpers.html[Action View Form Helpers]
+***********************************************************
+Guide to using built in Form helpers.
+***********************************************************
+
+.link:testing_rails_applications/testing_rails_applications.html[Testing Rails Applications]
+***********************************************************
+This is a rather comprehensive guide to doing both unit and functional tests
+in Rails. It covers everything from ``What is a test?'' to the testing APIs.
+Enjoy.
+***********************************************************
+
+.link:securing_rails_applications/securing_rails_applications.html[Securing Rails Applications]
+***********************************************************
+This manual describes common security problems in web applications and how to
+avoid them with Rails.
+***********************************************************
+
+.link:routing/routing_outside_in.html[Rails Routing from the Outside In]
+***********************************************************
+This guide covers the user-facing features of Rails routing. If you want to
+understand how to use routing in your own Rails applications, start here.
+***********************************************************
+
+.link:debugging/debugging_rails_applications.html[Debugging Rails Applications]
+***********************************************************
+This guide describes how to debug Rails applications. It covers the different
+ways of achieving this and how to understand what is happening "behind the scenes"
+of your code.
+***********************************************************
+
+.link:creating_plugins/creating_plugins.html[The Basics of Creating Rails Plugins]
+***********************************************************
+TODO: Insert some description here.
+***********************************************************
diff --git a/railties/doc/guides/migrations/anatomy_of_a_migration.txt b/railties/doc/guides/migrations/anatomy_of_a_migration.txt
new file mode 100644
index 0000000000..9f325af914
--- /dev/null
+++ b/railties/doc/guides/migrations/anatomy_of_a_migration.txt
@@ -0,0 +1,85 @@
+== Anatomy Of A Migration ==
+
+Before I dive into the details of a migration, here are a few examples of the sorts of things you can do:
+
+[source, ruby]
+------------------------
+class CreateProducts < ActiveRecord::Migration
+ def self.up
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :products
+ end
+end
+------------------------
+
+This migration adds a table called `products` with a string column called `name` and a text column called `description`. A primary key column called `id` will also be added, however since this is the default we do not need to ask for this. The timestamp columns `created_at` and `updated_at` which Active Record populates automatically will also be added. Reversing this migration is as simple as dropping the table.
+
+Migrations are not limited to changing the schema. You can also use them to fix bad data in the database or populate new fields:
+
+[source, ruby]
+------------------------
+class AddReceiveNewsletterToUsers < ActiveRecord::Migration
+ def self.up
+ change_table :users do |t|
+ t.boolean :receive_newsletter, :default => false
+ end
+ User.update_all ["receive_newsletter = ?", true]
+ end
+
+ def self.down
+ remove_column :users, :receive_newsletter
+ end
+end
+------------------------
+
+This migration adds an `receive_newsletter` column to the `users` table. We want it to default to `false` for new users, but existing users are considered
+to have already opted in, so we use the User model to set the flag to `true` for existing users.
+
+NOTE: Some <<models,caveats>> apply to using models in your migrations.
+
+=== Migrations are classes
+A migration is a subclass of ActiveRecord::Migration that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them).
+
+Active Record provides methods that perform common data definition tasks in a database independent way (you'll read about them in detail later):
+
+* `create_table`
+* `change_table`
+* `drop_table`
+* `add_column`
+* `remove_column`
+* `change_column`
+* `rename_column`
+* `add_index`
+* `remove_index`
+
+If you need to perform tasks specific to your database (for example create a <<foreign_key,foreign key>> constraint) then the `execute` function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could
+write code to set the value of that column for existing records (if necessary using your models).
+
+On databases that support transactions with statements that change the schema (such as PostgreSQL), migrations are wrapped in a transaction. If the database does not support this (for example MySQL and SQLite) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to unpick the changes that were made by hand.
+
+=== What's in a name ===
+
+Migrations are stored in files in `db/migrate`, one for each migration class. The name of the file is of the form `YYYYMMDDHHMMSS_create_products.rb`, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The migration class' name must match (the camelcased version of) the latter part of the file name. For example `20080906120000_create_products.rb` should define CreateProducts and `20080906120001_add_details_to_products.rb` should define AddDetailsToProducts. If you do feel the need to change the file name then you MUST update the name of the class inside or Rails will complain about a missing class.
+
+Internally Rails only uses the migration's number (the timestamp) to identify them. Prior to Rails 2.1 the migration number started at 1 and was incremented each time a migration was generated. With multiple developers it was easy for these to clash requiring you to rollback migrations and renumber them. With Rails 2.1 this is largely avoided by using the creation time of the migration to identify them. You can revert to the old numbering scheme by setting `config.active_record.timestamped_migrations` to `false` in `environment.rb`.
+
+The combination of timestamps and recording which migrations have been run allows Rails to handle common situations that occur with multiple developers.
+
+For example Alice adds migrations `20080906120000` and `20080906123000` and Bob adds `20080906124500` and runs it. Alice finishes her changes and checks in her migrations and Bob pulls down the latest changes. Rails knows that it has not run Alice's two migrations so `rake db:migrate` would run them (even though Bob's migration with a later timestamp has been run), and similarly migrating down would not run their down methods.
+
+Of course this is no substitution for communication within the team, for example if Alice's migration removed a table that Bob's migration assumed the existence of then trouble will still occur.
+
+=== Changing migrations ===
+
+Occasionally you will make a mistake while writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run `rake db:migrate`. You must rollback the migration (for example with `rake db:rollback`), edit your migration and then run `rake db:migrate` to run the corrected version.
+
+In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. Just use some common sense.
+
diff --git a/railties/doc/guides/migrations/creating_a_migration.txt b/railties/doc/guides/migrations/creating_a_migration.txt
new file mode 100644
index 0000000000..892c73a533
--- /dev/null
+++ b/railties/doc/guides/migrations/creating_a_migration.txt
@@ -0,0 +1,109 @@
+== Creating A Migration ==
+
+=== Creating a model ===
+
+The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want then statements for adding those will also be created. For example, running
+
+`ruby script/generate model Product name:string description:text` will create a migration that looks like this
+
+[source, ruby]
+-----------------------
+class CreateProducts < ActiveRecord::Migration
+ def self.up
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :products
+ end
+end
+-----------------------
+
+You can append as many column name/type pairs as you want. By default `t.timestamps` (which creates the `updated_at` and `created_at` columns that
+are automatically populated by Active Record) will be added for you.
+
+=== Creating a standalone migration ===
+If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator:
+
+`ruby script/generate migration AddPartNumberToProducts`
+
+This will create an empty but appropriately named migration:
+
+[source, ruby]
+-----------------------
+class AddPartNumberToProducts < ActiveRecord::Migration
+ def self.up
+ end
+
+ def self.down
+ end
+end
+-----------------------
+
+If the migration name is of the form AddXXXToYYY or RemoveXXXFromY and is followed by a list of column names and types then a migration containing
+the appropriate add and remove column statements will be created.
+
+`ruby script/generate migration AddPartNumberToProducts part_number:string`
+
+will generate
+
+[source, ruby]
+-----------------------
+class AddPartNumberToProducts < ActiveRecord::Migration
+ def self.up
+ add_column :products, :part_number, :string
+ end
+
+ def self.down
+ remove_column :products, :part_number
+ end
+end
+-----------------------
+
+Similarly,
+
+`ruby script/generate migration RemovePartNumberFromProducts part_number:string`
+
+generates
+
+[source, ruby]
+-----------------------
+class RemovePartNumberFromProducts < ActiveRecord::Migration
+ def self.up
+ remove_column :products, :part_number
+ end
+
+ def self.down
+ add_column :products, :part_number, :string
+ end
+end
+-----------------------
+
+You are not limited to one magically generated column, for example
+
+`ruby script/generate migration AddDetailsToProducts part_number:string price:decimal`
+
+generates
+
+[source, ruby]
+-----------------------
+class AddDetailsToProducts < ActiveRecord::Migration
+ def self.up
+ add_column :products, :part_number, :string
+ add_column :products, :price, :decimal
+ end
+
+ def self.down
+ remove_column :products, :price
+ remove_column :products, :part_number
+ end
+end
+-----------------------
+
+As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit.
+
diff --git a/railties/doc/guides/migrations/foreign_keys.txt b/railties/doc/guides/migrations/foreign_keys.txt
new file mode 100644
index 0000000000..c1cde64096
--- /dev/null
+++ b/railties/doc/guides/migrations/foreign_keys.txt
@@ -0,0 +1,7 @@
+[[foreign_key]]
+== Active Record and Referential Integrity ==
+The Active Record way is that intelligence belongs in your models, not in the database. As such features such as triggers or foreign key constraints, which push some of that intelligence back into the database are not heavily used.
+
+Validations such as `validates_uniqueness_of` are one way in which models can enforce data integrity. The `:dependent` option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level these cannot guarantee referential integrity and so some people augment them with foreign key constraints.
+
+Although Active Record does not provide any tools for working directly with such features, the `execute` method can be used to execute arbitrary SQL. There are also a number of plugins such as http://agilewebdevelopment.com/plugins/search?search=redhillonrails[redhillonrails] which add foreign key support to Active Record (including support for dumping foreign keys in `schema.rb`). \ No newline at end of file
diff --git a/railties/doc/guides/migrations/migrations.txt b/railties/doc/guides/migrations/migrations.txt
new file mode 100644
index 0000000000..bbb39d0ccb
--- /dev/null
+++ b/railties/doc/guides/migrations/migrations.txt
@@ -0,0 +1,21 @@
+Migrations
+==========
+
+Migrations are a convenient way for you to alter your database in a structured and organised manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run it. You'd also have to keep track of which changes need to be run against the production machines next time you deploy. Active Record tracks which migrations have already been run so all you have to do is update your source and run `rake db:migrate`. Active Record will work out which migrations should be run.
+
+Migrations also allow you to describe these transformations using Ruby. The great thing about this is that (like most of Active Record's functionality) it is database independent: you don't need to worry about the precise syntax of CREATE TABLE any more that you worry about variations on SELECT * (you can drop down to raw SQL for database specific features). For example you could use SQLite3 in development, but MySQL in production.
+
+You'll learn all about migrations including:
+
+* The generators you can use to create them
+* The methods Active Record provides to manipulate your database
+* The Rake tasks that manipulate them
+* How they relate to `schema.rb`
+
+include::anatomy_of_a_migration.txt[]
+include::creating_a_migration.txt[]
+include::writing_a_migration.txt[]
+include::rakeing_around.txt[]
+include::using_models_in_migrations.txt[]
+include::scheming.txt[]
+include::foreign_keys.txt[] \ No newline at end of file
diff --git a/railties/doc/guides/migrations/rakeing_around.txt b/railties/doc/guides/migrations/rakeing_around.txt
new file mode 100644
index 0000000000..1fcca0cf24
--- /dev/null
+++ b/railties/doc/guides/migrations/rakeing_around.txt
@@ -0,0 +1,111 @@
+== Running Migrations ==
+
+Rails provides a set of rake tasks to work with migrations which boils down to running certain sets of migrations. The very first migration related rake task you use will probably be `db:migrate`. In its most basic form it just runs the `up` method for all the migrations that have not yet been run. If there are no such migrations it exits.
+
+If you specify a target version, Active Record will run the required migrations (up or down) until it has reached the specified version. The
+version is the numerical prefix on the migration's filename. For example to migrate to version 20080906120000 run
+
+------------------------------------
+rake db:migrate VERSION=20080906120000
+------------------------------------
+
+If this is greater than the current version (i.e. it is migrating upwards) this will run the `up` method on all migrations up to and including 20080906120000, if migrating downwards this will run the `down` method on all the migrations down to, but not including, 20080906120000.
+
+=== Rolling back ===
+
+A common task is to rollback the last migration, for example if you made a mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run
+
+------------------
+rake db:rollback
+------------------
+
+This will run the `down` method from the latest migration. If you need to undo several migrations you can provide a `STEP` parameter:
+
+------------------
+rake db:rollback STEP=3
+------------------
+
+will run the `down` method fron the last 3 migrations.
+
+The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating back up again. As with the `db:rollback` task you can use the `STEP` parameter if you need to go more than one version back, for example
+
+------------------
+rake db:migrate:redo STEP=3
+------------------
+
+Neither of these Rake tasks do anything you could not do with `db:migrate`, they are simply more convenient since you do not need to explicitly specify the version to migrate to.
+
+Lastly, the `db:reset` task will drop the database, recreate it and load the current schema into it.
+
+NOTE: This is not the same as running all the migrations - see the section on <<schema,schema.rb>>.
+
+=== Being Specific ===
+
+If you need to run a specific migration up or down the `db:migrate:up` and `db:migrate:down` tasks will do that. Just specify the appropriate version and the corresponding migration will have its `up` or `down` method invoked, for example
+
+------------------
+rake db:migrate:up VERSION=20080906120000
+------------------
+
+will run the `up` method from the 20080906120000 migration. These tasks check whether the migration has already run, so for example `db:migrate:up VERSION=20080906120000` will do nothing if Active Record believes that 20080906120000 has already been run.
+
+
+=== Being talkative ===
+
+By default migrations tell you exactly what they're doing and how long it took.
+A migration creating a table and adding an index might produce output like this
+-------------------------
+== 20080906170109 CreateProducts: migrating ===================================
+-- create_table(:products)
+ -> 0.0021s
+-- add_index(:products, :name)
+ -> 0.0026s
+== 20080906170109 CreateProducts: migrated (0.0059s) ==========================
+-------------------------
+Several methods are provided that allow you to control all this:
+
+* `suppress_messages` suppresses any output generated by its block
+* `say` outputs text (the second argument controls whether it is indented or not)
+* `say_with_time` outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected.
+
+For example, this migration
+
+[source, ruby]
+----------------------
+class CreateProducts < ActiveRecord::Migration
+ def self.up
+ suppress_messages do
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+ t.timestamps
+ end
+ end
+ say "Created a table"
+ suppress_messages {add_index :products, :name}
+ say "and an index!", true
+ say_with_time 'Waiting for a while' do
+ sleep 10
+ 250
+ end
+ end
+
+ def self.down
+ drop_table :products
+ end
+end
+----------------------
+
+generates the following output
+----------------------
+== 20080906170109 CreateProducts: migrating ===================================
+-- Created a table
+ -> and an index!
+-- Waiting for a while
+ -> 10.0001s
+ -> 250 rows
+== 20080906170109 CreateProducts: migrated (10.0097s) =========================
+----------------------
+
+If you just want Active Record to shut up then running `rake db:migrate VERBOSE=false` will suppress any output.
+
diff --git a/railties/doc/guides/migrations/scheming.txt b/railties/doc/guides/migrations/scheming.txt
new file mode 100644
index 0000000000..ba4fea8fe3
--- /dev/null
+++ b/railties/doc/guides/migrations/scheming.txt
@@ -0,0 +1,47 @@
+== Schema dumping and you ==
+[[schema]]
+=== What are schema files for? ===
+Migrations, mighty as they may be, are not the authoritative source for your database schema. That role falls to either `schema.rb` or an SQL file which Active Record generates by examining the database. They are not designed to be edited, they just represent the current state of the database.
+
+There is no need (and it is error prone) to deploy a new instance of an app by replaying the entire migration history. It is much simpler and faster to just load into the database a description of the current schema.
+
+For example, this is how the test database is created: the current development database is dumped (either to `schema.rb` or `development.sql`) and then loaded into the test database.
+
+Schema files are also useful if want a quick look at what attributes an Active Record object has. This information is not in the model's code and is frequently spread across several migrations but is all summed up in the schema file. The http://agilewebdevelopment.com/plugins/annotate_models[annotate_models] plugin, which automatically adds (and updates) comments at the top of each model summarising the schema, may also be of interest.
+
+=== Types of schema dumps ===
+There are two ways to dump the schema. This is set in `config/environment.rb` by the `config.active_record.schema_format` setting, which may be either `:sql` or `:ruby`.
+
+If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look at this file you'll find that it looks an awful lot like one very big migration:
+
+[source, ruby]
+--------------------------------------
+ActiveRecord::Schema.define(:version => 20080906171750) do
+ create_table "authors", :force => true do |t|
+ t.string "name"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "products", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "part_number"
+ end
+end
+--------------------------------------
+
+In many ways this is exactly what it is. This file is created by inspecting the database and expressing its structure using `create_table`, `add_index` and so on. Because this is database independent it could be loaded into any database that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases.
+
+There is however a trade-off: `schema.rb` cannot express database specific items such as foreign key constraints, triggers or stored procedures. While in a migration you can execute custom SQL statements, the schema dumper cannot reconstitute those statements from the database. If you are using features like this then you should set the schema format to `:sql`.
+
+Instead of using Active Record's schema dumper the database's structure will be dumped using a tool specific to that database (via the `db:structure:dump` Rake task) into `db/#\{RAILS_ENV\}_structure.sql`. For example for PostgreSQL the `pg_dump` utility is used and for MySQL this file will contain the output of SHOW CREATE TABLE for the various tables. Loading this schema is simply a question of executing the SQL statements contained inside.
+
+By definition this will be a perfect copy of the database's structure but this will usually prevent loading the schema into a database other than the one used to create it.
+
+=== Schema dumps and source control ===
+
+Because they are the authoritative source for your database schema, it is strongly recommended that you check them into source control.
+
diff --git a/railties/doc/guides/migrations/using_models_in_migrations.txt b/railties/doc/guides/migrations/using_models_in_migrations.txt
new file mode 100644
index 0000000000..35a4c6fdfd
--- /dev/null
+++ b/railties/doc/guides/migrations/using_models_in_migrations.txt
@@ -0,0 +1,46 @@
+[[models]]
+== Using Models In Your Migrations ==
+When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done but some caution should be observed.
+
+Consider for example a migration that uses the Product model to update a row in the corresponding table. Alice later updates the Product model, adding a new column and a validation on it. Bob comes back from holiday, updates the source and runs outstanding migrations with `rake db:migrate`, including the one that used the Product model. When the migration runs the source is up to date and so the Product model has the validation added by Alice. The database however is still old and so does not have that column and an error ensues because that validation is on a column that does not yet exist.
+
+Frequently I just want to update rows in the database without writing out the SQL by hand: I'm not using anything specific to the model. One pattern for this is to define a copy of the model inside the migration itself, for example:
+
+[source, ruby]
+-------------------------
+class AddPartNumberToProducts < ActiveRecord::Migration
+ class Product < ActiveRecord::Base
+ end
+
+ def self.up
+ ...
+ end
+
+ def self.down
+ ...
+ end
+end
+-------------------------
+The migration has its own minimal copy of the Product model and no longer cares about the Product model defined in the application.
+
+=== Dealing with changing models ===
+
+For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try and use the old column information. You can force Active Record to re-read the column information with the `reset_column_information` method, for example
+
+[source, ruby]
+-------------------------
+class AddPartNumberToProducts < ActiveRecord::Migration
+ class Product < ActiveRecord::Base
+ end
+
+ def self.up
+ add_column :product, :part_number, :string
+ Product.reset_column_information
+ ...
+ end
+
+ def self.down
+ ...
+ end
+end
+-------------------------
diff --git a/railties/doc/guides/migrations/writing_a_migration.txt b/railties/doc/guides/migrations/writing_a_migration.txt
new file mode 100644
index 0000000000..0ab5397a84
--- /dev/null
+++ b/railties/doc/guides/migrations/writing_a_migration.txt
@@ -0,0 +1,159 @@
+== Writing a Migration ==
+
+Once you have created your migration using one of the generators it's time to get to work!
+
+=== Creating a table ===
+
+`create_table` will be one of your workhorses. A typical use would be
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.string :name
+end
+---------------------
+which creates a `products` table with a column called `name` (and as discussed below, an implicit `id` column).
+
+The object yielded to the block allows you create columns on the table. There are two ways of doing this. The first looks like
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.column :name, :string, :null => false
+end
+---------------------
+
+the second form, the so called "sexy" migrations, drops the somewhat redundant column method. Instead, the `string`, `integer` etc. methods create a column of that type. Subsequent parameters are identical.
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.string :name, :null => false
+end
+---------------------
+
+By default `create_table` will create a primary key called `id`. You can change the name of the primary key with the `:primary_key` option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass `:id => false`. If you need to pass database specific options you can place an sql fragment in the `:options` option. For example
+
+[source, ruby]
+---------------------
+create_table :products, :options => "ENGINE=InnoDB" do |t|
+ t.string :name, :null => false
+end
+---------------------
+Will append `ENGINE=InnoDB` to the sql used to create the table (this is Rails' default when using MySQL).
+
+The types Active Record supports are `:primary_key`, `:string`, `:text`, `:integer`, `:float`, `:decimal`, `:datetime`, `:timestamp`, `:time`, `:date`, `:binary`, `:boolean`.
+
+These will be mapped onto an appropriate underlying database type, for example with MySQL `:string` is mapped to `VARCHAR(255)`. You can create columns of
+types not supported by Active Record when using the non sexy syntax, for example
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.column :name, 'polygon', :null => false
+end
+---------------------
+This may however hinder portability to other databases.
+
+=== Changing tables ===
+
+`create_table`'s close cousin is `change_table`. Used for changing existing tables, it is used in a similar fashion to `create_table` but the object yielded to the block knows more tricks. For example
+
+[source, ruby]
+---------------------
+change_table :products do |t|
+ t.remove :description, :name
+ t.string :part_number
+ t.index :part_number
+ t.rename :upccode, :upc_code
+end
+---------------------
+removes the `description` column, creates a `part_number` column and adds an index on it. Finally it renames the `upccode` column. This is the same as doing
+
+[source, ruby]
+---------------------
+remove_column :products, :description
+remove_column :products, :name
+add_column :products, :part_number, :string
+add_index :products, :part_number
+rename_column :products, :upccode, :upc_code
+---------------------
+
+You don't have to keep repeating the table name and it groups all the statements related to modifying one particular table. The individual transformation names are also shorter, for example `remove_column` becomes just `remove` and `add_index` becomes just `index`.
+
+=== Special helpers ===
+
+Active Record provides some shortcuts for common functionality. It is for example very common to add both the `created_at` and `updated_at` columns and so there is a method that does exactly that:
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.timestamps
+end
+---------------------
+will create a new products table with those two columns whereas
+
+[source, ruby]
+---------------------
+change_table :products do |t|
+ t.timestamps
+end
+---------------------
+adds those columns to an existing table.
+
+The other helper is called `references` (also available as `belongs_to`). In its simplest form it just adds some readability
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.references :category
+end
+---------------------
+
+will create a `category_id` column of the appropriate type. Note that you pass the model name, not the column name. Active Record adds the `_id` for you. If you have polymorphic belongs_to associations then `references` will add both of the columns required:
+
+[source, ruby]
+---------------------
+create_table :products do |t|
+ t.references :attachment, :polymorphic => {:default => 'Photo'}
+end
+---------------------
+will add an `attachment_id` column and a string `attachment_type` column with a default value of 'Photo'.
+
+NOTE: The `references` helper does not actually create foreign key constraints for you. You will need to use `execute` for that or a plugin that adds <<foreign_key,foreign key support>>.
+
+If the helpers provided by Active Record aren't enough you can use the `execute` function to execute arbitrary SQL.
+
+For more details and examples of individual methods check the API documentation, in particular the documentation for http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html[ActiveRecord::ConnectionAdapters::SchemaStatements] (which provides the methods available in the `up` and `down` methods), http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html[ActiveRecord::ConnectionAdapters::TableDefinition] (which provides the methods available on the object yielded by `create_table`) and http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/Table.html[ActiveRecord::ConnectionAdapters::Table] (which provides the methods available on the object yielded by `change_table`).
+
+=== Writing your down method ===
+
+The `down` method of your migration should revert the transformations done by the `up` method. In other words the database should be unchanged if you do an `up` followed by a `down`. For example if you create a table in the up you should drop it in the `down` method. It is wise to do things in precisely the reverse order to in the `up` method. For example
+
+[source, ruby]
+---------------------
+class ExampleMigration < ActiveRecord::Migration
+
+ def self.up
+ create_table :products do |t|
+ t.references :category
+ end
+ #add a foreign key
+ execute "ALTER TABLE products ADD CONSTRAINT fk_products_categories FOREIGN KEY (category_id) REFERENCES categories(id)"
+
+ add_column :users, :home_page_url, :string
+
+ rename_column :users, :email, :email_address
+ end
+
+ def self.down
+ rename_column :users, :email_address, :email
+ remove_column :users, :home_page_url
+ execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"
+ drop_table :products
+ end
+end
+---------------------
+Sometimes your migration will do something which is just plain irreversible, for example it might destroy some data. In cases like those when you can't reverse the migration you can raise IrreversibleMigration from your `down` method. If someone tries to revert your migration an error message will be
+displayed saying that it can't be done.
+
diff --git a/railties/doc/guides/routing/routing_outside_in.txt b/railties/doc/guides/routing/routing_outside_in.txt
new file mode 100644
index 0000000000..bc08b107cf
--- /dev/null
+++ b/railties/doc/guides/routing/routing_outside_in.txt
@@ -0,0 +1,838 @@
+Rails Routing from the Outside In
+=================================
+
+This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
+
+* Understand the purpose of routing
+* Decipher the code in +routes.rb+
+* Construct your own routes, using either the classic hash style or the now-preferred RESTful style
+* Identify how a route will map to a controller and action
+
+== The Dual Purpose of Routing
+
+Rails routing is a two-way piece of machinery - rather as if you could turn trees into paper, and then turn paper back into trees. Specifically, it both connects incoming HTTP requests to the code in your application's controllers, and helps you generate URLs without having to hard-code them as strings.
+
+=== Connecting URLs to Code
+
+When your Rails application receives an incoming HTTP request, say
+
+-------------------------------------------------------
+GET /patient/17
+-------------------------------------------------------
+
+the routing engine within Rails is the piece of code that dispatches the request to the appropriate spot in your application. In this case, the application would most likely end up running the +show+ action within the +patients+ controller, displaying the details of the patient whose ID is 17.
+
+=== Generating URLs from Code
+
+Routing also works in reverse. If your application contains this code:
+
+[source, ruby]
+-------------------------------------------------------
+@patient = Patient.find(17)
+<%= link_to "Patient Record", patient_path(@patient) %>
+-------------------------------------------------------
+
+Then the routing engine is the piece that translates that to a link to a URL such as +http://example.com/patient/17+. By using routing in this way, you can reduce the brittleness of your application as compared to one with hard-coded URLs, and make your code easier to read and understand.
+
+NOTE: Patient needs to be declared as a resource for this style of translation via a named route to be available.
+
+== Quick Tour of Routes.rb
+
+There are two components to routing in Rails: the routing engine itself, which is supplied as part of Rails, and the file +config/routes.rb+, which contains the actual routes that will be used by your application. Learning exactly what you can put in +routes.rb+ is the main topic of this guide, but before we dig in let's get a quick overview.
+
+=== Processing the File
+
+In format, +routes.rb+ is nothing more than one big block sent to +ActionController::Routing::Routes.draw+. Within this block, you can have comments, but it's likely that most of your content will be individual lines of code - each line being a route in your application. You'll find five main types of content in this file:
+
+* RESTful Routes
+* Named Routes
+* Nested Routes
+* Regular Routes
+* Default Routes
+
+Each of these types of route is covered in more detail later in this guide.
+
+The +routes.rb+ file is processed from top to bottom when a request comes in. The request will be dispatched to the first matching route. If there is no matching route, then Rails returns HTTP status 404 to the caller.
+
+=== RESTful Routes
+
+RESTful routes take advantage of the built-in REST orientation of Rails to wrap up a lot of routing information in a single declaration. A RESTful route looks like this:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :books
+-------------------------------------------------------
+
+=== Named Routes
+
+Named routes give you very readable links in your code, as well as handling incoming requests. Here's a typical named route:
+
+[source, ruby]
+-------------------------------------------------------
+map.login '/login', :controller => 'sessions', :action => 'new'
+-------------------------------------------------------
+
+=== Nested Routes
+
+Nested routes let you declare that one resource is contained within another resource. You'll see later on how this translates to URLs and paths in your code. For example, if your application includes parts, each of which belongs to an assembly, you might have this nested route declaration:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :assemblies do |assemblies|
+ assemblies.resources :parts
+end
+-------------------------------------------------------
+
+=== Regular Routes
+
+In many applications, you'll also see non-RESTful routing, which explicitly connects the parts of a URL to a particular action. For example,
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'parts/:number', :controller => 'inventory', :action => 'show'
+-------------------------------------------------------
+
+=== Default Routes
+
+The default routes are a safety net that catch otherwise-unrouted requests. Many Rails applications will contain this pair of default routes:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id'
+map.connect ':controller/:action/:id.:format'
+-------------------------------------------------------
+
+These default routes are automatically generated when you create a new Rails application. If you're using RESTful routing for everything in your application, you will probably want to remove them. But be sure you're not using the default routes before you remove them!
+
+== RESTful Routing: the Rails Default
+
+RESTful routing is the current standard for routing in Rails, and it's the one that you should prefer for new applications. It can take a little while to understand how RESTful routing works, but it's worth the effort; your code will be easier to read and you'll be working with Rails, rather than fighting against it, when you use this style of routing.
+
+=== What is REST?
+
+The foundation of RESTful routing is generally considered to be Roy Fielding's doctoral thesis, link:http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm[Architectural Styles and the Design of Network-based Software Architectures]. Fortunately, you need not read this entire document to understand how REST works in Rails. REST, an acronym for Representational State Transfer, boils down to two main principles for our purposes:
+
+* Using resource identifiers (which, for the purposes of discussion, you can think of as URLs) to represent resources
+* Transferring representations of the state of that resource between system components.
+
+For example, to a Rails application a request such as this:
+
++DELETE /photos/17+
+
+would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities.
+
+=== CRUD, Verbs, and Actions
+
+In Rails, a RESTful route provides a mapping between HTTP verbs, controller actions, and (implicitly) CRUD operations in a database. A single entry in the routing file, such as
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos
+-------------------------------------------------------
+
+creates seven different routes in your application:
+
+[grid="all"]
+`----------`---------------`-----------`--------`-------------------------------------------
+HTTP verb URL controller action used for
+--------------------------------------------------------------------------------------------
+GET /photos Photos index display a list of all photos
+GET /photos/new Photos new return an HTML form for creating a new photo
+POST /photos Photos create create a new photo
+GET /photos/1 Photos show display a specific photo
+GET /photos/1/edit Photos edit return an HTML form for editing a photo
+PUT /photos/1 Photos update update a specific photo
+DELETE /photos/1 Photos destroy delete a specific photo
+--------------------------------------------------------------------------------------------
+
+For the specific routes (those that reference just a single resource), the identifier for the resource will be available within the corresponding controller action as +params[:id]+.
+
+TIP: If you consistently use RESTful routes in your application, you should disable the default routes in +routes.rb+ so that Rails will enforce the mapping between HTTP verbs and routes.
+
+=== URLs and Paths
+
+Creating a RESTful route will also make available a pile of helpers within your application:
+
+* +photos_url+ and +photos_path+ map to the path for the index and create actions
+* +new_photo_url+ and +new_photo_path+ map to the path for the new action
+* +edit_photo_url+ and +edit_photo_path+ map to the path for the edit action
+* +photo_url+ and +photo_path+ map to the path for the show, update, and destroy actions
+
+NOTE: Because routing makes use of the HTTP verb as well as the path in the request to dispatch requests, the seven routes generated by a RESTful routing entry only give rise to four pairs of helpers.
+
+In each case, the +_url+ helper generates a string containing the entire URL that the application will understand, while the +_path+ helper generates a string containing the relative path from the root of the application. For example:
+
+[source, ruby]
+-------------------------------------------------------
+photos_url # => "http://www.example.com/photos"
+photos_path # => "/photos"
+-------------------------------------------------------
+
+=== Singular Resources
+
+You can also apply RESTful routing to singleton resources within your application. In this case, you use +map.resource+ instead of +map.resources+ and the route generation is slightly different. For example, a routing entry of
+
+[source, ruby]
+-------------------------------------------------------
+map.resource :geocoder
+-------------------------------------------------------
+
+creates seven different routes in your application:
+
+[grid="all"]
+`----------`---------------`-----------`--------`-------------------------------------------
+HTTP verb URL controller action used for
+--------------------------------------------------------------------------------------------
+GET /geocoder/new Geocoders new return an HTML form for creating the new geocoder
+POST /geocoder Geocoders create create the new geocoder
+GET /geocoder Geocoders show display the one and only geocoder resource
+GET /geocoder/edit Geocoders edit return an HTML form for editing the geocoder
+PUT /geocoder Geocoders update update the one and only geocoder resource
+DELETE /geocoder Geocoders destroy delete the geocoder resource
+--------------------------------------------------------------------------------------------
+
+NOTE: Even though the name of the resource is singular in +routes.rb+, the matching controller is still plural.
+
+A singular RESTful route generates an abbreviated set of helpers:
+
+* +new_geocoder_url+ and +new_geocoder_path+ map to the path for the new action
+* +edit_geocoder_url+ and +edit_geocoder_path+ map to the path for the edit action
+* +geocoder_url+ and +geocoder_path+ map to the path for the create, show, update, and destroy actions
+
+=== Customizing Resources
+
+Although the conventions of RESTful routing are likely to be sufficient for many applications, there are a number of ways to customize the way that RESTful routes work. These options include:
+
+* +:controller+
+* +:singular+
+* +:requirements+
+* +:conditions+
+* +:as+
+* +:path_names+
+* +:path_prefix+
+* +:name_prefix+
+
+You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide.
+
+==== Using :controller
+
+The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :controller => "images"
+-------------------------------------------------------
+
+will recognize incoming URLs containing +photo+ but route the requests to the Images controller:
+
+[grid="all"]
+`----------`---------------`-----------`--------`-------------------------------------------
+HTTP verb URL controller action used for
+--------------------------------------------------------------------------------------------
+GET /photos Images index display a list of all images
+GET /photos/new Images new return an HTML form for creating a new image
+POST /photos Images create create a new image
+GET /photos/1 Images show display a specific image
+GET /photos/1/edit Images edit return an HTML form for editing a image
+PUT /photos/1 Images update update a specific image
+DELETE /photos/1 Images destroy delete a specific image
+--------------------------------------------------------------------------------------------
+
+NOTE: The helpers will be generated with the name of the resource, not the name of the controller. So in this case, you'd still get +photos_path+, +photos_new_path+, and so on.
+
+==== Using :singular
+
+If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :teeth, :singular => "tooth"
+-------------------------------------------------------
+
+TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead.
+
+==== Using :requirements
+
+You an use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/}
+-------------------------------------------------------
+
+This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would.
+
+==== Using :conditions
+
+Conditions in Rails routing are currently used only to set the HTTP verb for individual routes. Although in theory you can set this for RESTful routes, in practice there is no good reason to do so. (You'll learn more about conditions in the discussion of classic routing later in this guide.)
+
+==== Using :as
+
+The +:as+ option lets you override the normal naming for the actual generated paths. For example:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :as => "images"
+-------------------------------------------------------
+
+will recognize incoming URLs containing +image+ but route the requests to the Photos controller:
+
+[grid="all"]
+`----------`---------------`-----------`--------`-------------------------------------------
+HTTP verb URL controller action used for
+--------------------------------------------------------------------------------------------
+GET /images Photos index display a list of all photos
+GET /images/new Photos new return an HTML form for creating a new photo
+POST /images Photos create create a new photo
+GET /images/1 Photos show display a specific photo
+GET /images/1/edit Photos edit return an HTML form for editing a photo
+PUT /images/1 Photos update update a specific photo
+DELETE /images/1 Photos destroy delete a specific photo
+--------------------------------------------------------------------------------------------
+
+NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +photos_new_path+, and so on.
+
+==== Using :path_names
+
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :path_names => { :new => 'make', :edit => 'change' }
+-------------------------------------------------------
+
+This would cause the routing to recognize URLs such as
+
+-------------------------------------------------------
+/photos/make
+/photos/1/change
+-------------------------------------------------------
+
+NOTE: The actual action names aren't changed by this option; the two URLs show would still route to the new and edit actions.
+
+TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can set a default in your environment:
+
+[source, ruby]
+-------------------------------------------------------
+config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' }
+-------------------------------------------------------
+
+==== Using :path_prefix
+
+The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :path_prefix => '/photographers/:photographer_id'
+-------------------------------------------------------
+
+Routes recognized by this entry would include:
+
+-------------------------------------------------------
+/photographers/1/photos/2
+/photographers/1/photos
+-------------------------------------------------------
+
+NOTE: In most cases, it's simpler to recognize URLs of this sort by creating nested resources, as discussed in the next section.
+
+==== Using :name_prefix
+
+You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :path_prefix => '/photographers/:photographer_id', :name_prefix => 'photographer_'
+map.resources :photos, :path_prefix => '/agencies/:agency_id', :name_prefix => 'agency_'
+-------------------------------------------------------
+
+This combination will give you route helpers such as +photographer_photos_path+ and +agency_photo_edit_path+ to use in your code.
+
+=== Nested Resources
+
+It's common to have resources that are logically children of other resources. For example, suppose your application includes these models:
+
+[source, ruby]
+-------------------------------------------------------
+class Magazine < ActiveRecord::Base
+ has_many :ads
+end
+
+class Ad < ActiveRecord::Base
+ belongs_to :magazine
+end
+-------------------------------------------------------
+
+Each ad is logically subservient to one magazine. Nested routes allow you to capture this relationship in your routing. In this case, you might include this route declaration:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :magazines do |magazine|
+ magazine.resources :ads
+end
+-------------------------------------------------------
+
+In addition to the routes for magazines, this declaration will also create routes for ads, each of which requires the specification of a magazine in the URL:
+
+[grid="all"]
+`----------`-----------------------`-----------`--------`-------------------------------------------
+HTTP verb URL controller action used for
+--------------------------------------------------------------------------------------------
+GET /magazines/1/ads Ads index display a list of all ads for a specific magazine
+GET /magazines/1/ads/new Ads new return an HTML form for creating a new ad belonging to a specific magazine
+POST /magazines/1/ads Ads create create a new photo belonging to a specific magazine
+GET /magazines/1/ads/1 Ads show display a specific photo belonging to a specific magazine
+GET /magazines/1/ads/1/edit Ads edit return an HTML form for editing a photo belonging to a specific magazine
+PUT /magazines/1/ads/1 Ads update update a specific photo belonging to a specific magazine
+DELETE /magazines/1/ads/1 Ads destroy delete a specific photo belonging to a specific magazine
+--------------------------------------------------------------------------------------------
+
+This will also create routing helpers such as +magazine_ads_url+ and +magazine_edit_ad_path+.
+
+==== Using :name_prefix
+
+The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example,
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :magazines do |magazine|
+ magazine.resources :ads, :name_prefix => 'periodical'
+end
+-------------------------------------------------------
+
+This will create routing helpers such as +periodical_ads_url+ and +periodical_edit_ad_path+. You can even use +:name_prefix+ to suppress the prefix entirely:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :magazines do |magazine|
+ magazine.resources :ads, :name_prefix => nil
+end
+-------------------------------------------------------
+
+This will create routing helpers such as +ads_url+ and +edit_ad_path+. Note that calling these will still require supplying an article id:
+
+[source, ruby]
+-------------------------------------------------------
+ads_url(@magazine)
+edit_ad_path(@magazine, @ad)
+-------------------------------------------------------
+
+==== Using :has_one and :has_many
+
+The +:has_one+ and +:has_many+ options provide a succinct notation for simple nested routes. Use +:has_one+ to nest a singleton resource, or +:has_many+ to nest a plural resource:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :has_one => :photographer, :has_many => [:publications, :versions]
+-------------------------------------------------------
+
+This has the same effect as this set of declarations:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos do |photo|
+ photo.resource :photographer
+ photo.resources :publications
+ photo.resources :versions
+end
+-------------------------------------------------------
+
+==== Limits to Nesting
+
+You can nest resources within other nested resources if you like. For example:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :publishers do |publisher|
+ publisher.resources :magazines do |magazine|
+ magazine.resources :photos
+ end
+end
+-------------------------------------------------------
+
+However, without the use of +name_prefix => nil+, deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize URLs such as
+
+-------------------------------------------------------
+/publishers/1/magazines/2/photos/3
+-------------------------------------------------------
+
+The corresponding route helper would be +publisher_magazine_photo_url+, requiring you to specify objects at all three levels. Indeed, this situation is confusing enough that a popular link:http://weblog.jamisbuck.org/2007/2/5/nesting-resources[article] by Jamis Buck proposes a rule of thumb for good Rails design:
+
+_Resources should never be nested more than 1 level deep._
+
+==== Shallow Nesting
+
+The +:shallow+ option provides an elegant solution to the difficulties of deeply-nested routes. If you specify this option at any level of routing, then paths for nested resources which reference a specific member (that is, those with an +:id+ parameter) will not use the parent path prefix or name prefix. To see what this means, consider this set of routes:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :publishers, :shallow => true do |publisher|
+ publisher.resources :magazines do |magazine|
+ magazine.resources :photos
+ end
+end
+-------------------------------------------------------
+
+This will enable recognition of (among others) these routes:
+
+-------------------------------------------------------
+/publishers/1 ==> publisher_path(1)
+/publishers/1/magazines ==> publisher_magazines_path(1)
+/magazines/2 ==> magazine_path(2)
+/magazines/2/photos ==> magazines_photos_path(2)
+/photos/3 ==> photo_path(3)
+-------------------------------------------------------
+
+With shallow nesting, you need only supply enough information to uniquely identify the resource that you want to work with - but you _can_ supply more information. All of the nested routes continue to work, just as they would without shallow nesting, but less-deeply nested routes (even direct routes) work as well. So, with the declaration above, all of these routes refer to the same resource:
+
+-------------------------------------------------------
+/publishers/1/magazines/2/photos/3 ==> publisher_magazine_photo_path(1,2,3)
+/magazines/2/photos/3 ==> magazine_photo_path(2,3)
+/photos/3 ==> photo_path(3)
+-------------------------------------------------------
+
+Shallow nesting gives you the flexibility to use the shorter direct routes when you like, while still preserving the longer nested routes for times when they add code clarity.
+
+If you like, you can combine shallow nesting with the +:has_one+ and +:has_many+ options:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :publishers, :has_many => { :magazines => :photos }, :shallow => true
+-------------------------------------------------------
+
+=== Adding More RESTful Actions
+
+You are not limited to the seven routes that RESTful routing creates by default. If you like, you may add additional member routes (those which apply to a single instance of the resource), additional new routes (those that apply to creating a new resource), or additional collection routes (those which apply to the collection of resources as a whole).
+
+==== Adding Member Routes
+
+To add a member route, use the +:member+ option:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :member => { :preview => :get }
+-------------------------------------------------------
+
+This will enable Rails to recognize URLs such as +/photos/1/preview+ using the GET HTTP verb, and route them to the preview action of the Photos controller. It will also create a +preview_photo+ route helper.
+
+Within the hash of member routes, each route name specifies the HTTP verb that it will recognize. You can use +:get+, +:put+, +:post+, +:delete+, or +:any+ here.
+
+==== Adding Collection Routes
+
+To add a collection route, use the +:collection+ option:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :collection => { :search => :get }
+-------------------------------------------------------
+
+This will enable Rails to recognize URLs such as +/photos/search+ using the GET HTTP verb, and route them to the search action of the Photos controller. It will also create a +search_photos+ route helper.
+
+==== Adding New Routes
+
+To add a new route (one that creates a new resource), use the +:new+ option:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :new => { :upload => :post }
+-------------------------------------------------------
+
+This will enable Rails to recognize URLs such as +/photos/upload+ using the POST HTTP verb, and route them to the upload action of the Photos controller. It will also create a +upload_photos+ route helper.
+
+TIP: If you want to redefine the verbs accepted by one of the standard actions, you can do so by explicitly mapping that action. For example:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :new => { :new => :any }
+-------------------------------------------------------
+
+This will allow the new action to be invoked by any request to +photos/new+, no matter what HTTP verb you use.
+
+==== A Note of Caution
+
+If you find yourself adding many extra actions to a RESTful route, it's time to stop and ask yourself whether you're disguising the presence of another resource that would be better split off on its own. When the +:member+ and +:collection+ hashes become a dumping-ground, RESTful routes lose the advantage of easy readability that is one of their strongest points.
+
+== Regular Routes
+
+In addition to RESTful routing, Rails supports regular routing - a way to map URLs to controllers and actions. With regular routing, you don't get the masses of routes automatically generated by RESTful routing. Instead, you must set up each route within your application separately.
+
+While RESTful routing has become the Rails standard, there are still plenty of places where the simpler regular routing works fine. You can even mix the two styles within a single application. In general, you should prefer RESTful routing _when possible_, because it will make parts of your application easier to write. But there's no need to try to shoehorn every last piece of your application into a RESTful framework if that's not a good fit.
+
+=== Bound Parameters
+
+When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: +:controller+ maps to the name of a controller in your application, and +:action+ maps to the name of an action within that controller. For example, consider one of the default Rails routes:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id'
+-------------------------------------------------------
+
+If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +Photos+ controller, and to make the final parameter (1) available as +params[:id]+.
+
+=== Wildcard Components
+
+You can set up as many wildcard symbols within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the matching action as part of the params hash. So, if you set up this route:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id/:userid:'
+-------------------------------------------------------
+
+An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be set to 2.
+
+=== Static Text
+
+You can specify static text when creating a route. In this case, the static text is used only for matching the incoming requests:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id/with_user/:userid:'
+-------------------------------------------------------
+
+This route would respond to URLs such as +/photos/show/1/with_user/2+.
+
+=== Querystring Parameters
+
+Rails routing automatically picks up querystring parameters and makes them available in the +params+ hash. For example, with this route:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id'
+-------------------------------------------------------
+
+An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be equal to 2.
+
+=== Defining Defaults
+
+You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply defaults for these two parameters in a hash:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'photo/:id', :controller => 'photos', :action => 'show'
+-------------------------------------------------------
+
+With this route, an incoming URL of +/photos/12+ would be dispatched to the +show+ action within the +Photos+ controller.
+
+=== Named Routes
+
+Regular routes need not use the +connect+ method. You can use any other name here to create a _named route_. For example,
+
+[source, ruby]
+-------------------------------------------------------
+map.logout '/logout', :controller => 'sessions', :action => 'destroy'
+-------------------------------------------------------
+
+This will do two things. First, requests to +/logout+ will be sent to the +destroy+ method of the +Sessions+ controller. Second, Rails will maintain the +logout_path+ and +logout_url+ helpers for use within your code.
+
+=== Route Requirements
+
+You can use the +:requirements+ option to enforce a format for any parameter in a route:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'photo/:id', :controller => 'photos', :action => 'show',
+ :requirements => { :id => /[A-Z]\d{5}/ }
+-------------------------------------------------------
+
+This route would respond to URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'photo/:id', :controller => 'photos', :action => 'show',
+ :id => /[A-Z]\d{5}/
+-------------------------------------------------------
+
+=== Route Conditions
+
+Route conditions (introduced with the +:conditions+ option) are designed to implement restrictions on routes. Currently, the only supported restriction is +:method+:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'photo/:id', :controller => 'photos', :action => 'show',
+ :conditions => { :method => :get }
+-------------------------------------------------------
+
+As with conditions in RESTful routes, you can specify +:get+, +:post+, +:put+, +:delete+, or +:any+ for the acceptable method.
+
+=== Route Globbing
+
+Route globbing is a way to specify that a particular parameter (which must be the last parameter in the route) should engulf all the remaining parts of a route. For example
+
+[source, ruby]
+-------------------------------------------------------
+map.connect 'photo/*other', :controller => 'photos', :action => 'unknown',
+-------------------------------------------------------
+
+This route would match +photo/12+ or +/photo/long/path/to/12+ equally well, creating an array of path segments as the value of +params[:other]+.
+
+=== Route Options
+
+You can use +:with_options+ to simplify defining groups of similar routes:
+
+[source, ruby]
+-------------------------------------------------------
+map.with_options :controller => 'photo' do |photo|
+ photo.list '', :action => 'index'
+ photo.delete ':id/delete', :action => 'delete'
+ photo.edit ':id/edit', :action => 'edit'
+end
+-------------------------------------------------------
+
+The importance of +map.with_options+ has declined with the introduction of RESTful routes.
+
+== Formats and respond_to
+
+There's one more way in which routing can do different things depending on differences in the incoming HTTP request: by issuing a response that corresponds to what the request specifies that it will accept. In Rails routing, you can control this with the special +:format+ parameter in the route.
+
+For instance, consider the second of the default routes in the boilerplate +routes.rb+ file:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id.:format'
+-------------------------------------------------------
+
+This route matches requests such as +/photo/new/1.xml+ or +/photo/show/2.rss+. Within the appropriate action code, you can issue different responses depending on the requested format:
+
+[source, ruby]
+-------------------------------------------------------
+respond_to do |format|
+ format.html # return the default template for HTML
+ format.xml { render :xml => @photo.to_xml }
+end
+-------------------------------------------------------
+
+=== Specifying the Format with an HTTP Header
+
+If there is no +:format+ parameter in the route, Rails will automatically look at the HTTP Accept header to determine the desired format.
+
+=== Recognized MIME types
+
+By default, Rails recognizes +html+, +text+, +json+, +csv+, +xml+, +rss+, +atom+, and +yaml+ as acceptable response types. If you need types beyond this, you can register them in your environment:
+
+[source, ruby]
+-------------------------------------------------------
+Mime::Type.register "image/jpg", :jpg
+-------------------------------------------------------
+
+== The Default Routes
+
+When you create a new Rails application, +routes.rb+ is initialized with two default routes:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect ':controller/:action/:id'
+map.connect ':controller/:action/:id.:format'
+-------------------------------------------------------
+
+These routes provide reasonable defaults for many URLs, if you're not using RESTful routing.
+
+NOTE: The default routes will make every action of every controller in your application accessible to GET requests. If you've designed your application to make consistent use of RESTful and named routes, you should comment out the default routes to prevent access to your controllers through the wrong verbs. If you've had the default routes enabled during development, though, you need to be sure that you haven't unwittingly depended on them somewhere in your application - otherwise you may find mysterious failures when you disable them.
+
+== The Empty Route
+
+Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route.
+
+=== Using map.root
+
+The preferred way to set up the empty route is with the +map.root+ command:
+
+[source, ruby]
+-------------------------------------------------------
+map.root :controller => "pages", :action => "main"
+-------------------------------------------------------
+
+The use of the +root+ method tells Rails that this route applies to requests for the root of the site.
+
+For better readability, you can specify an already-created route in your call to +map.root+:
+
+[source, ruby]
+-------------------------------------------------------
+map.index :controller => "pages", :action => "main"
+map.root :index
+-------------------------------------------------------
+
+Because of the top-down processing of the file, the named route must be specified _before_ the call to +map.route+.
+
+=== Connecting the Empty String
+
+You can also specify an empty route by explicitly connecting the empty string:
+
+[source, ruby]
+-------------------------------------------------------
+map.connect '', :controller => "pages", :action => "main"
+-------------------------------------------------------
+
+TIP: If the empty route does not seem to be working in your application, make sure that you have deleted the file +public/index.html+ from your Rails tree.
+
+== Inspecting and Testing Routes
+
+Routing in your application should not be a "black box" that you never open. Rails offers built-in tools for both inspecting and testing routes.
+
+=== Seeing Existing Routes with rake
+
+If you want a complete list of all of the available routes in your application, run the +rake routes+ command. This will dump all of your routes to the console, in the same order that they appear in +routes.rb+. For each route, you'll see:
+
+* The route name (if any)
+* The HTTP verb used (if the route doesn't respond to all verbs)
+* The URL pattern
+* The routing parameters that will be generated by this URL
+
+For example, here's a small section of the +rake routes+ output for a RESTful route:
+
+-------------------------------------------------------------------------------------------------------
+ users GET /users {:controller=>"users", :action=>"index"}
+formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
+ POST /users {:controller=>"users", :action=>"create"}
+ POST /users.:format {:controller=>"users", :action=>"create"}
+-------------------------------------------------------------------------------------------------------
+
+TIP: You'll find that the output from +rake routes+ is much more readable if you widen your terminal window until the output lines don't wrap.
+
+=== Testing Routes
+
+Routes should be included in your testing strategy (just like the rest of your application). Rails offers three link:http://api.rubyonrails.com/classes/ActionController/Assertions/RoutingAssertions.html[built-in assertions] designed to make testing routes simpler:
+
+* +assert_generates+
+* +assert_recognizes+
+* +assert_routing+
+
+==== The +assert_generates+ Assertion
+
+Use +assert_generates+ to assert that a particular set of options generate a particular path. You can use this with default routes or custom routes
+
+[source, ruby]
+-------------------------------------------------------
+assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" }
+assert_generates "/about", :controller => "pages", :action => "about"
+-------------------------------------------------------
+
+==== The +assert_recognizes+ Assertion
+
+The +assert_recognizes+ assertion is the inverse of +assert_generates+. It asserts that Rails recognizes the given path and routes it to a particular spot in your application.
+
+[source, ruby]
+-------------------------------------------------------
+assert_recognizes { :controller => "photos", :action => "show", :id => "1" }, "/photos/1"
+-------------------------------------------------------
+
+You can supply a +:method+ argument to specify the HTTP verb:
+
+[source, ruby]
+-------------------------------------------------------
+assert_recognizes { :controller => "photos", :action => "create" }, { :path => "photos", :method => :post }
+-------------------------------------------------------
+
+You can also use the RESTful helpers to test recognition of a RESTful route:
+
+[source, ruby]
+-------------------------------------------------------
+assert_recognizes new_photo_url, { :path => "photos", :method => :post }
+-------------------------------------------------------
+
+==== The +assert_routing+ Assertion
+
+The +assert_routing+ assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of +assert_generates+ and +assert_recognizes+.
+
+[source, ruby]
+-------------------------------------------------------
+assert_routing { :path => "photos", :method => :post }, { :controller => "photos", :action => "create" }
+------------------------------------------------------- \ No newline at end of file
diff --git a/railties/environments/environment.rb b/railties/environments/environment.rb
index a85ade371b..abdb863130 100644
--- a/railties/environments/environment.rb
+++ b/railties/environments/environment.rb
@@ -40,7 +40,7 @@ Rails::Initializer.run do |config|
# Make Time.zone default to the specified zone, and make Active Record store time values
# in the database in UTC, and return them converted to the specified local zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Comment line to use default local time.
config.time_zone = 'UTC'
# Your secret key for verifying cookie session data integrity.
@@ -63,5 +63,6 @@ Rails::Initializer.run do |config|
# config.active_record.schema_format = :sql
# Activate observers that should always be running
- # config.active_record.observers = :cacher, :garbage_collector
+ # Please note that observers generated using script/generate observer need to have an _observer suffix
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
end
diff --git a/railties/lib/commands/console.rb b/railties/lib/commands/console.rb
index 2b9d92f647..63df834639 100644
--- a/railties/lib/commands/console.rb
+++ b/railties/lib/commands/console.rb
@@ -1,11 +1,13 @@
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
require 'optparse'
+
options = { :sandbox => false, :irb => irb }
OptionParser.new do |opt|
opt.banner = "Usage: console [environment] [options]"
opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |v| options[:irb] = v }
+ opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
opt.parse!(ARGV)
end
@@ -15,6 +17,17 @@ libs << " -r console_app"
libs << " -r console_sandbox" if options[:sandbox]
libs << " -r console_with_helpers"
+if options[:debugger]
+ begin
+ require 'ruby-debug'
+ libs << " -r ruby-debug"
+ puts "=> Debugger enabled"
+ rescue Exception
+ puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'"
+ exit
+ end
+end
+
ENV['RAILS_ENV'] = case ARGV.first
when "p"; "production"
when "d"; "development"
diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb
index 5be3b5dd8e..6ff895aa30 100644
--- a/railties/lib/commands/dbconsole.rb
+++ b/railties/lib/commands/dbconsole.rb
@@ -6,7 +6,7 @@ include_password = false
OptionParser.new do |opt|
opt.banner = "Usage: dbconsole [options] [environment]"
- opt.on("-p", "--include-password", "Automatically provide the database from database.yml") do |v|
+ opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
include_password = true
end
opt.parse!(ARGV)
diff --git a/railties/lib/commands/plugin.rb b/railties/lib/commands/plugin.rb
index 980244a71b..9ff4739562 100644
--- a/railties/lib/commands/plugin.rb
+++ b/railties/lib/commands/plugin.rb
@@ -461,11 +461,11 @@ module Commands
o.on("-r", "--root=DIR", String,
"Set an explicit rails app directory.",
- "Default: #{@rails_root}") { |@rails_root| self.environment = RailsEnvironment.new(@rails_root) }
+ "Default: #{@rails_root}") { |rails_root| @rails_root = rails_root; self.environment = RailsEnvironment.new(@rails_root) }
o.on("-s", "--source=URL1,URL2", Array,
- "Use the specified plugin repositories instead of the defaults.") { |@sources|}
+ "Use the specified plugin repositories instead of the defaults.") { |sources| @sources = sources}
- o.on("-v", "--verbose", "Turn on verbose output.") { |$verbose| }
+ o.on("-v", "--verbose", "Turn on verbose output.") { |verbose| $verbose = verbose }
o.on("-h", "--help", "Show this help message.") { puts o; exit }
o.separator ""
@@ -552,12 +552,12 @@ module Commands
o.separator "Options:"
o.separator ""
o.on( "-s", "--source=URL1,URL2", Array,
- "Use the specified plugin repositories.") {|@sources|}
+ "Use the specified plugin repositories.") {|sources| @sources = sources}
o.on( "--local",
- "List locally installed plugins.") {|@local| @remote = false}
+ "List locally installed plugins.") {|local| @local, @remote = local, false}
o.on( "--remote",
"List remotely available plugins. This is the default behavior",
- "unless --local is provided.") {|@remote|}
+ "unless --local is provided.") {|remote| @remote = remote}
end
end
@@ -598,7 +598,7 @@ module Commands
o.separator "Options:"
o.separator ""
o.on( "-c", "--check",
- "Report status of repository.") { |@sources|}
+ "Report status of repository.") { |sources| @sources = sources}
end
end
@@ -689,7 +689,7 @@ module Commands
o.separator "Options:"
o.separator ""
o.on( "-l", "--list",
- "List but don't prompt or add discovered repositories.") { |@list| @prompt = !@list }
+ "List but don't prompt or add discovered repositories.") { |list| @list, @prompt = list, !@list }
o.on( "-n", "--no-prompt",
"Add all new repositories without prompting.") { |v| @prompt = !v }
end
@@ -901,7 +901,7 @@ class RecursiveHTTPFetcher
def initialize(urls_to_fetch, level = 1, cwd = ".")
@level = level
@cwd = cwd
- @urls_to_fetch = urls_to_fetch.to_a
+ @urls_to_fetch = RUBY_VERSION >= '1.9' ? urls_to_fetch.lines : urls_to_fetch.to_a
@quiet = false
end
diff --git a/railties/lib/commands/process/spawner.rb b/railties/lib/commands/process/spawner.rb
index dc0008698a..8bf47abb75 100644
--- a/railties/lib/commands/process/spawner.rb
+++ b/railties/lib/commands/process/spawner.rb
@@ -181,10 +181,10 @@ ARGV.options do |opts|
opts.on(" Options:")
- opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |OPTIONS[:port]| }
+ opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v }
if spawner_class.can_bind_to_custom_address?
- opts.on("-a", "--address=ip", String, "Bind to IP address (default: #{OPTIONS[:address]})") { |OPTIONS[:address]| }
+ opts.on("-a", "--address=ip", String, "Bind to IP address (default: #{OPTIONS[:address]})") { |v| OPTIONS[:address] = v }
end
opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v }
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 008f1de8fa..74d2daa34b 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -488,7 +488,7 @@ Run `rake gems:install` to install the missing gems.
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
def initialize_time_zone
if configuration.time_zone
- zone_default = Time.send!(:get_zone, configuration.time_zone)
+ zone_default = Time.__send__(:get_zone, configuration.time_zone)
unless zone_default
raise %{Value assigned to config.time_zone not recognized. Run "rake -D time" for a list of tasks for finding appropriate time zone names.}
end
@@ -784,7 +784,6 @@ Run `rake gems:install` to install the missing gems.
def threadsafe!
self.cache_classes = true
self.dependency_loading = false
- self.active_record.allow_concurrency = true
self.action_controller.allow_concurrency = true
self
end
diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
index 80e8eabfd3..9849948339 100644
--- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb
+++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -1,6 +1,5 @@
require 'rbconfig'
require 'digest/md5'
-require 'rails_generator/secret_key_generator'
class AppGenerator < Rails::Generator::Base
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
@@ -36,7 +35,7 @@ class AppGenerator < Rails::Generator::Base
md5 << @app_name
# Do our best to generate a secure secret key for CookieStore
- secret = Rails::SecretKeyGenerator.new(@app_name).generate_secret
+ secret = ActiveSupport::SecureRandom.hex(64)
record do |m|
# Root directory and all subdirectories.
diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb
index 1b7bcfef08..4de94076e9 100644
--- a/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb
+++ b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb
@@ -1,7 +1,6 @@
require 'test_helper'
class <%= class_name %>Test < ActionMailer::TestCase
- tests <%= class_name %>
<% for action in actions -%>
test "<%= action %>" do
@expected.subject = '<%= class_name %>#<%= action %>'
diff --git a/railties/lib/rails_generator/secret_key_generator.rb b/railties/lib/rails_generator/secret_key_generator.rb
index 5ae492312e..553811d35d 100644
--- a/railties/lib/rails_generator/secret_key_generator.rb
+++ b/railties/lib/rails_generator/secret_key_generator.rb
@@ -5,160 +5,18 @@ module Rails
#
# generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name")
# generator.generate_secret # => "f3f1be90053fa851... (some long string)"
+ #
+ # This class is *deprecated* in Rails 2.2 in favor of ActiveSupport::SecureRandom.
+ # It is currently a wrapper around ActiveSupport::SecureRandom.
class SecretKeyGenerator
- GENERATORS = [ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze
-
def initialize(identifier)
- @identifier = identifier
end
# Generate a random secret key with the best possible method available on
# the current platform.
def generate_secret
- generator = GENERATORS.find do |g|
- self.class.send("supports_#{g}?")
- end
- send("generate_secret_with_#{generator}")
- end
-
- # Generate a random secret key by using the Win32 API. Raises LoadError
- # if the current platform cannot make use of the Win32 API. Raises
- # SystemCallError if some other error occurred.
- def generate_secret_with_win32_api
- # Following code is based on David Garamond's GUID library for Ruby.
- require 'Win32API'
-
- crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext",
- 'PPPII', 'L')
- crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",
- 'LIP', 'L')
- crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext",
- 'LI', 'L')
- prov_rsa_full = 1
- crypt_verifycontext = 0xF0000000
-
- hProvStr = " " * 4
- if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full,
- crypt_verifycontext) == 0
- raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
- end
- hProv, = hProvStr.unpack('L')
- bytes = " " * 64
- if crypt_gen_random.call(hProv, bytes.size, bytes) == 0
- raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
- end
- if crypt_release_context.call(hProv, 0) == 0
- raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}"
- end
- bytes.unpack("H*")[0]
- end
-
- # Generate a random secret key with Ruby 1.9's SecureRandom module.
- # Raises LoadError if the current Ruby version does not support
- # SecureRandom.
- def generate_secret_with_secure_random
- require 'securerandom'
- return SecureRandom.hex(64)
- end
-
- # Generate a random secret key with OpenSSL. If OpenSSL is not
- # already loaded, then this method will attempt to load it.
- # LoadError will be raised if that fails.
- def generate_secret_with_openssl
- require 'openssl'
- if !File.exist?("/dev/urandom")
- # OpenSSL transparently seeds the random number generator with
- # data from /dev/urandom. On platforms where that is not
- # available, such as Windows, we have to provide OpenSSL with
- # our own seed. Unfortunately there's no way to provide a
- # secure seed without OS support, so we'll have to do with
- # rand() and Time.now.usec().
- OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
- end
- data = OpenSSL::BN.rand(2048, -1, false).to_s
-
- if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
- OpenSSL::Digest::SHA512.new(data).hexdigest
- else
- generate_secret_with_prng
- end
+ ActiveSupport::SecureRandom.hex(64)
end
-
- # Generate a random secret key with /dev/urandom.
- # Raises SystemCallError on failure.
- def generate_secret_with_urandom
- return File.read("/dev/urandom", 64).unpack("H*")[0]
- end
-
- # Generate a random secret key with Ruby's pseudo random number generator,
- # as well as some environment information.
- #
- # This is the least cryptographically secure way to generate a secret key,
- # and should be avoided whenever possible.
- def generate_secret_with_prng
- require 'digest/sha2'
- sha = Digest::SHA2.new(512)
- now = Time.now
- sha << now.to_s
- sha << String(now.usec)
- sha << String(rand(0))
- sha << String($$)
- sha << @identifier
- return sha.hexdigest
- end
-
- private
- def lastWin32ErrorMessage
- # Following code is based on David Garamond's GUID library for Ruby.
- 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
-
- def self.supports_secure_random?
- begin
- require 'securerandom'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_win32_api?
- return false unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
- begin
- require 'Win32API'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_urandom?
- File.exist?('/dev/urandom')
- end
-
- def self.supports_openssl?
- begin
- require 'openssl'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_prng?
- true
- end
+ deprecate :generate_secret=>"You should use ActiveSupport::SecureRandom.hex(64)"
end
end
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index 21c81b3fb5..1431aa6944 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -28,8 +28,24 @@ namespace :db do
def create_database(config)
begin
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
+ if config['adapter'] =~ /sqlite/
+ if File.exist?(config['database'])
+ $stderr.puts "#{config['database']} already exists"
+ else
+ begin
+ # Create the SQLite database
+ ActiveRecord::Base.establish_connection(config)
+ ActiveRecord::Base.connection
+ rescue
+ $stderr.puts $!, *($!.backtrace)
+ $stderr.puts "Couldn't create database for #{config.inspect}"
+ end
+ end
+ return # Skip the else clause of begin/rescue
+ else
+ ActiveRecord::Base.establish_connection(config)
+ ActiveRecord::Base.connection
+ end
rescue
case config['adapter']
when 'mysql'
@@ -52,10 +68,6 @@ namespace :db do
$stderr.puts $!, *($!.backtrace)
$stderr.puts "Couldn't create database for #{config.inspect}"
end
- when 'sqlite'
- `sqlite "#{config['database']}"`
- when 'sqlite3'
- `sqlite3 "#{config['database']}"`
end
else
$stderr.puts "#{config['database']} already exists"
@@ -101,8 +113,16 @@ namespace :db do
end
namespace :migrate do
- desc 'Rollbacks the database one migration and re migrate up. If you want to rollback more than one step, define STEP=x'
- task :redo => [ 'db:rollback', 'db:migrate' ]
+ desc 'Rollbacks the database one migration and re migrate up. If you want to rollback more than one step, define STEP=x. Target specific version with VERSION=x.'
+ task :redo => :environment do
+ if ENV["VERSION"]
+ Rake::Task["db:migrate:down"].invoke
+ Rake::Task["db:migrate:up"].invoke
+ else
+ Rake::Task["db:rollback"].invoke
+ Rake::Task["db:migrate"].invoke
+ end
+ end
desc 'Resets your database using your migrations for the current environment'
task :reset => ["db:drop", "db:create", "db:migrate"]
diff --git a/railties/lib/tasks/documentation.rake b/railties/lib/tasks/documentation.rake
index f4927a0ef1..4ef838626a 100644
--- a/railties/lib/tasks/documentation.rake
+++ b/railties/lib/tasks/documentation.rake
@@ -1,9 +1,9 @@
namespace :doc do
- desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb"
+ desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb or title with TITLE=\"Custom Title\""
Rake::RDocTask.new("app") { |rdoc|
rdoc.rdoc_dir = 'doc/app'
rdoc.template = ENV['template'] if ENV['template']
- rdoc.title = "Rails Application Documentation"
+ rdoc.title = ENV['title'] || "Rails Application Documentation"
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
rdoc.rdoc_files.include('doc/README_FOR_APP')
diff --git a/railties/lib/tasks/misc.rake b/railties/lib/tasks/misc.rake
index 33bbba1101..5c99725203 100644
--- a/railties/lib/tasks/misc.rake
+++ b/railties/lib/tasks/misc.rake
@@ -3,10 +3,9 @@ task :environment do
require(File.join(RAILS_ROOT, 'config', 'environment'))
end
-require 'rails_generator/secret_key_generator'
-desc 'Generate a crytographically secure secret key. This is typically used to generate a secret for cookie sessions. Pass a unique identifier to the generator using ID="some unique identifier" for greater security.'
+desc 'Generate a crytographically secure secret key. This is typically used to generate a secret for cookie sessions.'
task :secret do
- puts Rails::SecretKeyGenerator.new(ENV['ID']).generate_secret
+ puts ActiveSupport::SecureRandom.hex(64)
end
require 'active_support'
@@ -54,4 +53,4 @@ namespace :time do
puts "\n"
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/secret_key_generation_test.rb b/railties/test/secret_key_generation_test.rb
index ea1b0dae31..7269f98ce5 100644
--- a/railties/test/secret_key_generation_test.rb
+++ b/railties/test/secret_key_generation_test.rb
@@ -31,14 +31,8 @@ class SecretKeyGenerationTest < Test::Unit::TestCase
end
def test_secret_key_generation
- assert @generator.generate_secret.length >= SECRET_KEY_MIN_LENGTH
- end
-
- Rails::SecretKeyGenerator::GENERATORS.each do |generator|
- if Rails::SecretKeyGenerator.send("supports_#{generator}?")
- define_method("test_secret_key_generation_with_#{generator}") do
- assert @generator.send("generate_secret_with_#{generator}").length >= SECRET_KEY_MIN_LENGTH
- end
+ assert_deprecated /ActiveSupport::SecureRandom\.hex\(64\)/ do
+ assert @generator.generate_secret.length >= SECRET_KEY_MIN_LENGTH
end
end
end
diff --git a/release.rb b/release.rb
index 2e1e8610b1..e8e6c01c35 100755
--- a/release.rb
+++ b/release.rb
@@ -3,8 +3,11 @@
VERSION = ARGV.first
PACKAGES = %w(activesupport activerecord actionpack actionmailer activeresource)
-# Checkout source
-# `rm -rf release && svn export http://dev.rubyonrails.org/svn/rails/trunk release`
+# Copy source
+`mkdir release`
+(PACKAGES + %w(railties)).each do |p|
+ `cp -R #{p} release/#{p}`
+end
# Create Rails packages
`cd release/railties && rake template=jamis package`