aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--.travis.yml16
-rw-r--r--Gemfile30
-rw-r--r--README.rdoc20
-rw-r--r--RELEASING_RAILS.rdoc186
-rwxr-xr-xRakefile9
-rw-r--r--actionmailer/README.rdoc8
-rw-r--r--actionpack/CHANGELOG14
-rw-r--r--actionpack/README.rdoc6
-rw-r--r--actionpack/actionpack.gemspec13
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb120
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb3
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb9
-rw-r--r--actionpack/lib/action_controller.rb15
-rw-r--r--actionpack/lib/action_controller/base.rb10
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb3
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb21
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb3
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb5
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb9
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb1
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb63
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb9
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb1
-rw-r--r--actionpack/lib/action_view/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb94
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb55
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb18
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb31
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb15
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb10
-rw-r--r--actionpack/lib/action_view/lookup_context.rb2
-rw-r--r--actionpack/lib/action_view/path_set.rb73
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb71
-rw-r--r--actionpack/lib/action_view/template/resolver.rb65
-rw-r--r--actionpack/lib/sprockets/assets.rake55
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb50
-rw-r--r--actionpack/lib/sprockets/railtie.rb76
-rw-r--r--actionpack/test/abstract_unit.rb6
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb31
-rw-r--r--actionpack/test/controller/default_url_options_with_filter_test.rb29
-rw-r--r--actionpack/test/controller/helper_test.rb1
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb19
-rw-r--r--actionpack/test/controller/redirect_test.rb5
-rw-r--r--actionpack/test/controller/render_test.rb28
-rw-r--r--actionpack/test/controller/resources_test.rb9
-rw-r--r--actionpack/test/controller/routing_test.rb109
-rw-r--r--actionpack/test/controller/test_test.rb23
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb183
-rw-r--r--actionpack/test/dispatch/mapper_test.rb14
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb12
-rw-r--r--actionpack/test/dispatch/request_test.rb6
-rw-r--r--actionpack/test/dispatch/routing_test.rb32
-rw-r--r--actionpack/test/dispatch/test_request_test.rb5
-rw-r--r--actionpack/test/fixtures/test/_200.html.erb1
-rw-r--r--actionpack/test/fixtures/test/hello_w*rld.erb1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb6
-rw-r--r--actionpack/test/template/compiled_templates_test.rb2
-rw-r--r--actionpack/test/template/form_helper_test.rb28
-rw-r--r--actionpack/test/template/form_options_helper_test.rb36
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb30
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb7
-rw-r--r--actionpack/test/template/javascript_helper_test.rb6
-rw-r--r--actionpack/test/template/render_test.rb41
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb114
-rw-r--r--actionpack/test/template/test_test.rb2
-rw-r--r--actionpack/test/template/text_helper_test.rb4
-rw-r--r--actionpack/test/template/url_helper_test.rb8
-rw-r--r--activemodel/CHANGELOG4
-rw-r--r--activemodel/README.rdoc27
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb33
-rw-r--r--activemodel/lib/active_model/conversion.rb28
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb14
-rw-r--r--activemodel/lib/active_model/lint.rb15
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb5
-rw-r--r--activemodel/lib/active_model/naming.rb3
-rw-r--r--activemodel/lib/active_model/observing.rb4
-rw-r--r--activemodel/lib/active_model/serialization.rb48
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb40
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb2
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb18
-rw-r--r--activemodel/lib/active_model/validations/with.rb10
-rw-r--r--activemodel/test/cases/conversion_test.rb9
-rw-r--r--activemodel/test/cases/errors_test.rb1
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb10
-rw-r--r--activemodel/test/cases/naming_test.rb16
-rw-r--r--activemodel/test/cases/observing_test.rb12
-rw-r--r--activemodel/test/cases/serialization_test.rb101
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb52
-rw-r--r--activemodel/test/cases/validations_test.rb33
-rw-r--r--activemodel/test/models/helicopter.rb3
-rw-r--r--activerecord/CHANGELOG32
-rw-r--r--activerecord/README.rdoc4
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb9
-rw-r--r--activerecord/lib/active_record/associations.rb22
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb18
-rw-r--r--activerecord/lib/active_record/associations/association.rb16
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb5
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb3
-rw-r--r--activerecord/lib/active_record/base.rb129
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb611
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb567
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb663
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb32
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb12
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/query_cache.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake8
-rw-r--r--activerecord/lib/active_record/relation.rb35
-rw-r--r--activerecord/lib/active_record/relation/batches.rb7
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb5
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb16
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/serialization.rb40
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb42
-rw-r--r--activerecord/lib/active_record/session_store.rb28
-rw-r--r--activerecord/lib/active_record/timestamp.rb14
-rw-r--r--activerecord/lib/active_record/validations/associated.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb33
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb14
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb16
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/eager_test.rb12
-rw-r--r--activerecord/test/cases/associations/extension_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb5
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb146
-rw-r--r--activerecord/test/cases/batches_test.rb16
-rw-r--r--activerecord/test/cases/calculations_test.rb7
-rw-r--r--activerecord/test/cases/column_definition_test.rb32
-rw-r--r--activerecord/test/cases/connection_management_test.rb34
-rw-r--r--activerecord/test/cases/connection_pool_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb25
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/identity_map_test.rb4
-rw-r--r--activerecord/test/cases/invalid_date_test.rb4
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb39
-rw-r--r--activerecord/test/cases/lifecycle_test.rb12
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb8
-rw-r--r--activerecord/test/cases/method_scoping_test.rb8
-rw-r--r--activerecord/test/cases/named_scope_test.rb20
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb5
-rw-r--r--activerecord/test/cases/persistence_test.rb20
-rw-r--r--activerecord/test/cases/query_cache_test.rb28
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/relations_test.rb65
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb17
-rw-r--r--activerecord/test/cases/serialization_test.rb142
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb134
-rw-r--r--activerecord/test/fixtures/pirates.yml4
-rw-r--r--activerecord/test/models/bulb.rb5
-rw-r--r--activerecord/test/models/comment.rb5
-rw-r--r--activerecord/test/models/developer.rb9
-rw-r--r--activerecord/test/models/toy.rb2
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activeresource/README.rdoc10
-rw-r--r--activeresource/lib/active_resource/base.rb2
-rw-r--r--activeresource/lib/active_resource/http_mock.rb2
-rw-r--r--activeresource/test/cases/base/schema_test.rb10
-rw-r--r--activesupport/CHANGELOG19
-rw-r--r--activesupport/README.rdoc4
-rw-r--r--activesupport/lib/active_support/cache.rb25
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/io.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb55
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb1
-rw-r--r--activesupport/lib/active_support/dependencies.rb22
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb4
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/gzip.rb1
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb3
-rw-r--r--activesupport/lib/active_support/inflections.rb1
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb1
-rw-r--r--activesupport/lib/active_support/notifications.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb1
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb8
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb34
-rw-r--r--activesupport/test/caching_test.rb50
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/blank_test.rb4
-rw-r--r--activesupport/test/core_ext/duplicable_test.rb19
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb84
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb49
-rw-r--r--activesupport/test/core_ext/io_test.rb23
-rw-r--r--activesupport/test/core_ext/module_test.rb36
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb7
-rw-r--r--activesupport/test/dependencies_test.rb19
-rw-r--r--activesupport/test/json/decoding_test.rb3
-rw-r--r--activesupport/test/multibyte_utils_test.rb1
-rw-r--r--activesupport/test/notifications_test.rb1
-rw-r--r--activesupport/test/safe_buffer_test.rb8
-rw-r--r--activesupport/test/test_test.rb1
-rw-r--r--activesupport/test/xml_mini_test.rb20
-rwxr-xr-xci/ci_build.rb181
-rw-r--r--ci/ci_setup_notes.txt140
-rw-r--r--ci/cruise_config.rb9
-rw-r--r--ci/site.css13
-rw-r--r--ci/site_config.rb72
-rwxr-xr-xci/travis.rb142
-rw-r--r--load_paths.rb8
-rw-r--r--railties/CHANGELOG17
-rw-r--r--railties/README.rdoc2
-rw-r--r--railties/guides/assets/images/rails_welcome.pngbin106417 -> 121314 bytes
-rw-r--r--railties/guides/rails_guides/generator.rb48
-rw-r--r--railties/guides/rails_guides/textile_extensions.rb60
-rw-r--r--railties/guides/source/3_0_release_notes.textile4
-rw-r--r--railties/guides/source/3_1_release_notes.textile429
-rw-r--r--railties/guides/source/action_mailer_basics.textile65
-rw-r--r--railties/guides/source/action_view_overview.textile5
-rw-r--r--railties/guides/source/active_model_basics.textile208
-rw-r--r--railties/guides/source/active_record_basics.textile1
-rw-r--r--railties/guides/source/active_record_querying.textile5
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile75
-rw-r--r--railties/guides/source/active_resource_basics.textile124
-rw-r--r--railties/guides/source/active_support_core_extensions.textile46
-rw-r--r--railties/guides/source/ajax_on_rails.textile8
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile20
-rw-r--r--railties/guides/source/asset_pipeline.textile291
-rw-r--r--railties/guides/source/association_basics.textile2
-rw-r--r--railties/guides/source/caching_with_rails.textile12
-rw-r--r--railties/guides/source/command_line.textile26
-rw-r--r--railties/guides/source/configuring.textile10
-rw-r--r--railties/guides/source/contributing_to_ruby_on_rails.textile1
-rw-r--r--railties/guides/source/form_helpers.textile84
-rw-r--r--railties/guides/source/getting_started.textile793
-rw-r--r--railties/guides/source/i18n.textile3
-rw-r--r--railties/guides/source/index.html.erb2
-rw-r--r--railties/guides/source/initialization.textile22
-rw-r--r--railties/guides/source/layouts_and_rendering.textile6
-rw-r--r--railties/guides/source/migrations.textile139
-rw-r--r--railties/guides/source/performance_testing.textile4
-rw-r--r--railties/guides/source/plugins.textile8
-rw-r--r--railties/guides/source/rails_application_templates.textile20
-rw-r--r--railties/guides/source/rails_on_rack.textile33
-rw-r--r--railties/guides/source/routing.textile114
-rw-r--r--railties/guides/source/security.textile11
-rw-r--r--railties/lib/rails.rb17
-rw-r--r--railties/lib/rails/application.rb20
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb34
-rw-r--r--railties/lib/rails/code_statistics.rb2
-rw-r--r--railties/lib/rails/commands.rb5
-rw-r--r--railties/lib/rails/engine.rb73
-rw-r--r--railties/lib/rails/engine/commands.rb5
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb5
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb18
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt10
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec29
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile20
-rwxr-xr-xrailties/lib/rails/generators/rails/plugin_new/templates/Rakefile7
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt (renamed from railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt)0
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb4
-rw-r--r--railties/lib/rails/paths.rb24
-rw-r--r--railties/lib/rails/plugin.rb3
-rw-r--r--railties/lib/rails/railtie.rb1
-rw-r--r--railties/lib/rails/tasks/documentation.rake8
-rw-r--r--railties/lib/rails/tasks/framework.rake6
-rw-r--r--railties/lib/rails/tasks/misc.rake2
-rw-r--r--railties/lib/rails/tasks/routes.rake12
-rw-r--r--railties/lib/rails/tasks/tmp.rake2
-rw-r--r--railties/lib/rails/test_unit/testing.rake4
-rw-r--r--railties/test/application/asset_debugging_test.rb56
-rw-r--r--railties/test/application/assets_test.rb44
-rw-r--r--railties/test/application/configuration_test.rb11
-rw-r--r--railties/test/application/middleware/sendfile_test.rb3
-rw-r--r--railties/test/application/middleware_test.rb8
-rw-r--r--railties/test/application/paths_test.rb1
-rw-r--r--railties/test/application/rake_test.rb88
-rw-r--r--railties/test/generators/actions_test.rb7
-rw-r--r--railties/test/generators/app_generator_test.rb12
-rw-r--r--railties/test/generators/generated_attribute_test.rb5
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb29
-rw-r--r--railties/test/railties/mounted_engine_test.rb19
-rw-r--r--tasks/release.rb14
358 files changed, 7004 insertions, 3707 deletions
diff --git a/.gitignore b/.gitignore
index be764143aa..a5bedb78e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-*.gem
pkg
.bundle
Gemfile.lock
@@ -22,4 +21,5 @@ railties/doc
railties/guides/output
railties/tmp
.rvmrc
-RDOC_MAIN.rdoc \ No newline at end of file
+.rbenv-version
+RDOC_MAIN.rdoc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..6130b69631
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+script: 'ci/travis.rb'
+rvm:
+ - 1.8.7
+ - 1.9.2
+ - 1.9.3
+env:
+ - "GEM=railties"
+ - "GEM=ap,am,amo,ares,as"
+ - "GEM=ar:mysql"
+ - "GEM=ar:mysql2"
+ - "GEM=ar:sqlite3"
+notifications:
+ email: false
+ irc:
+ - "irc.freenode.org#rails-contrib"
+bundler_args: --path vendor/bundle
diff --git a/Gemfile b/Gemfile
index e1a8b5637e..f0880926a3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,24 +4,24 @@ gemspec
if ENV['AREL']
gem "arel", :path => ENV['AREL']
-else
- gem "arel", '~> 2.1.3'
end
gem "jquery-rails"
-gem "coffee-script"
-gem "sass"
-
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
gem "uglifier", ">= 1.0.0", :require => false
-gem "rake", ">= 0.8.7"
+# Temp fix until rake 0.9.3 is out
+if RUBY_VERSION >= "1.9.3"
+ gem "rake", "0.9.3.beta.1"
+else
+ gem "rake", ">= 0.8.7"
+end
gem "mocha", ">= 0.9.8"
group :doc do
gem "rdoc", "~> 3.4"
- gem "horo", "= 1.0.3"
+ gem "sdoc", "~> 0.3"
gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
gem "w3c_validators"
end
@@ -31,13 +31,19 @@ gem "memcache-client", ">= 1.8.5"
platforms :mri_18 do
gem "system_timer"
- gem "ruby-debug", ">= 0.10.3"
+ gem "ruby-debug", ">= 0.10.3" unless ENV['TRAVIS']
gem "json"
end
platforms :mri_19 do
# TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3
- gem "ruby-debug19", :require => "ruby-debug" if RUBY_VERSION < "1.9.3"
+ gem "ruby-debug19", :require => "ruby-debug" unless RUBY_VERSION > "1.9.2" || ENV['TRAVIS']
+end
+
+platforms :mri do
+ group :test do
+ gem "ruby-prof" if RUBY_VERSION < "1.9.3"
+ end
end
platforms :ruby do
@@ -48,15 +54,11 @@ platforms :ruby do
gem "yajl-ruby"
gem "nokogiri", ">= 1.4.5"
- group :test do
- gem "ruby-prof" if RUBY_VERSION < "1.9.3"
-
- end
# AR
gem "sqlite3", "~> 1.3.3"
group :db do
- gem "pg", ">= 0.11.0"
+ gem "pg", ">= 0.11.0" unless ENV['TRAVIS'] # once pg is on travis this can be removed
gem "mysql", ">= 2.8.1"
gem "mysql2", ">= 0.3.6"
end
diff --git a/README.rdoc b/README.rdoc
index bca2126559..9f017cd05f 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -3,33 +3,33 @@
Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern.
-Understanding the MVC pattern is key to understanding Rails. MVC divides your application
+Understanding the MVC pattern is key to understanding Rails. MVC divides your application
into three layers, each with a specific responsibility.
The View layer is composed of "templates" that are responsible for providing
-appropriate representations of your application's resources. Templates
-can come in a variety of formats, but most view templates are HTML with embedded Ruby
+appropriate representations of your application's resources. Templates
+can come in a variety of formats, but most view templates are \HTML with embedded Ruby
code (.erb files).
The Model layer represents your domain model (such as Account, Product, Person, Post)
and encapsulates the business logic that is specific to your application. In Rails,
-database-backed model classes are derived from ActiveRecord::Base. ActiveRecord allows
+database-backed model classes are derived from ActiveRecord::Base. Active Record allows
you to present the data from database rows as objects and embellish these data objects
with business logic methods. Although most Rails models are backed by a database, models
can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
provided by the ActiveModel module. You can read more about Active Record in its
-{README}[link:blob/master/activerecord/README.rdoc].
+{README}[link:/rails/rails/blob/master/activerecord/README.rdoc].
The Controller layer is responsible for handling incoming HTTP requests and providing a
-suitable response. Usually this means returning HTML, but Rails controllers can also
+suitable response. Usually this means returning \HTML, but Rails controllers can also
generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models
and render view templates in order to generate the appropriate HTTP response.
In Rails, the Controller and View layers are handled together by Action Pack.
These two layers are bundled in a single package due to their heavy interdependence.
-This is unlike the relationship between the Active Record and Action Pack which are
+This is unlike the relationship between Active Record and Action Pack which are
independent. Each of these packages can be used independently outside of Rails. You
-can read more about Action Pack in its {README}[link:blob/master/actionpack/README.rdoc].
+can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc].
== Getting Started
@@ -49,7 +49,7 @@ can read more about Action Pack in its {README}[link:blob/master/actionpack/READ
Run with <tt>--help</tt> for options.
-4. Go to http://localhost:3000/ and you'll see:
+4. Go to http://localhost:3000 and you'll see:
"Welcome aboard: You're riding Ruby on Rails!"
@@ -62,7 +62,7 @@ can read more about Action Pack in its {README}[link:blob/master/actionpack/READ
* The {API Documentation}[http://api.rubyonrails.org].
-== Contributing
+== Contributing http://travis-ci.org/rails/rails.png
We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails
guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
new file mode 100644
index 0000000000..cbc9d0e1de
--- /dev/null
+++ b/RELEASING_RAILS.rdoc
@@ -0,0 +1,186 @@
+= Releasing Rails
+
+In this document, we'll cover the steps necessary to release Rails. Each
+section contains steps to take during that time before the release. The times
+suggested in each header are just that: suggestions. However, they should
+really be considered as minimums.
+
+== 10 Days before release
+
+Today is mostly coordination tasks. Here are the things you must do today:
+
+=== Is the CI green? If not, make it green. (See "Fixing the CI")
+
+Do not release with a Red CI. You can find the CI status here:
+
+ http://travis-ci.org/#!/rails/rails
+
+=== Is Sam Ruby happy? If not, make him happy.
+
+Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
+Web Development with Rails) all work. These are valuable integration tests
+for Rails. You can check the status of his tests here:
+
+ http://intertwingly.net/projects/dashboard.html
+
+Do not release with Red AWDwR tests.
+
+=== Do we have any git dependencies? If so, contact those authors.
+
+Having git dependencies indicates that we depend on unreleased code.
+Obviously rails cannot be released when it depends on unreleased code.
+Contact the authors of those particular gems and work out a release date that
+suits them.
+
+=== Contact the security team (either Koz or tenderlove)
+
+Let them know of your plans to release. There may be security issues to be
+addressed, and that can impact your release date.
+
+=== Notify implementors.
+
+Ruby implementors have high stakes in making sure Rails works. Be kind and
+give them a heads up that Rails will be released soonish.
+
+Send an email just giving a heads up about the upcoming release to these
+lists:
+
+* team@jruby.org
+* community@rubini.us
+* rubyonrails-core@googlegroups.com
+
+Implementors will love you and help you.
+
+== 3 Days before release
+
+This is when you should release the release candidate. Here are your tasks
+for today:
+
+=== Is the CI green? If not, make it green.
+
+=== Is Sam Ruby happy? If not, make him happy.
+
+=== Contact the security team. CVE emails must be sent on this day.
+
+=== Create a release branch.
+
+From the stable branch, create a release branch. For example, if you're
+releasing Rails 3.0.10, do this:
+
+ [aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
+ Switched to a new branch '3-0-10'
+ [aaron@higgins rails (3-0-10)]$
+
+=== Update each CHANGELOG.
+
+Many times commits are made without the CHANGELOG being updated. You should
+review the commits since the last release, and fill in any missing information
+for each CHANGELOG.
+
+You can review the commits for the 3.0.10 release like this:
+
+ [aaron@higgins rails (3-0-10)]$ git log v3.0.9..
+
+=== Update the RAILS_VERSION file to include the RC.
+
+=== Release the gem.
+
+IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
+to use Ruby 1.8 when releasing.
+
+Run `rake release`. This will populate the gemspecs with data from
+RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
+Here are the commands that `rake release` should use, so you can understand
+what to do in case anything goes wrong:
+
+ $ rake all:build
+ $ git commit -am'updating RAILS_VERSION'
+ $ git tag -m'tagging rc release' v3.0.10.rc1
+ $ git push
+ $ git push --tags
+ $ for i in $(ls dist); do gem push $i; done
+
+=== Send Rails release announcements
+
+Write a release announcement that includes the version, changes, and links to
+github where people can find the specific commit list. Here are the mailing
+lists where you should announce:
+
+* rubyonrails-core@googlegroups.com
+* rubyonrails-talk@googlegroups.com
+* ruby-talk@ruby-lang.org
+
+Use markdown format for your announcement. Remember to ask people to report
+issues with the release candidate to the rails-core mailing list.
+
+IMPORTANT: If any users experience regressions when using the release
+candidate, you *must* postpone the release. Bugfix releases *should not*
+break existing applications.
+
+=== Post the announcement to the Rails blog.
+
+If you used markdown format for your email, you can just paste it in to the
+blog.
+
+* http://weblog.rubyonrails.org
+
+=== Post the announcement to the Rails twitter account.
+
+== Time between release candidate and actual release
+
+Check the rails-core mailing list and the github issue list for regressions in
+the RC.
+
+If any regressions are found, fix the regressions and repeat the release
+candidate process. We will not release the final until 72 hours after the
+last release candidate has been pushed. This means that if users find
+regressions, the scheduled release date must be postponed.
+
+When you fix the regressions, do not create a new branch. Fix them on the
+stable branch, then cherry pick the commit to your release branch. No other
+commits should be added to the release branch besides regression fixing commits.
+
+== Day of release
+
+Many of these steps are the same as for the release candidate, so if you need
+more explanation on a particular step, so the RC steps.
+
+Today, do this stuff in this order:
+
+* Apply security patches to the release branch
+* Update CHANGELOG with security fixes.
+* Update RAILS_VERSION to remove the rc
+* Release the gems
+* Email security lists
+* Email general announcement lists
+
+=== Emailing the rails security announce list
+
+Email the security announce list once for each vulnerability fixed.
+
+You can do this, or ask the security team to do it.
+
+Email the security reports to:
+
+* rubyonrails-security@googlegroups.com
+* linux-distros@vs.openwall.org
+
+Be sure to note the security fixes in your announcement along with CVE numbers
+and links to each patch. Some people may not be able to upgrade right away,
+so we need to give them the security fixes in patch form.
+
+* Blog announcements
+* Twitter announcements
+* Merge the release branch to the stable branch.
+* Drink beer (or other cocktail)
+
+== Misc
+
+=== Fixing the CI
+
+There are two simple steps for fixing the CI:
+
+1. Identify the problem
+2. Fix it
+
+Repeat these steps until the CI is green.
diff --git a/Rakefile b/Rakefile
index d18dac0dbd..ae857036c5 100755
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,7 @@
#!/usr/bin/env rake
require 'rdoc/task'
+require 'sdoc'
require 'net/http'
$:.unshift File.expand_path('..', __FILE__)
@@ -74,7 +75,10 @@ RDoc::Task.new do |rdoc|
# since no autolinking happens there and RDoc displays the backslash
# otherwise.
rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
- rdoc_main.gsub!(%r{link:blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
+ rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
+
+ # Remove Travis build status image from API pages. Only GitHub README page gets this image
+ rdoc_main.gsub!("http://travis-ci.org/rails/rails.png", "")
File.open(RDOC_MAIN, 'w') do |f|
f.write(rdoc_main)
@@ -86,7 +90,8 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
- rdoc.options << '-f' << 'horo'
+ rdoc.options << '-f' << 'sdoc'
+ rdoc.options << '-T' << 'rails'
rdoc.options << '-c' << 'utf-8'
rdoc.options << '-m' << RDOC_MAIN
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 63e3893316..f48e77b4c7 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -10,7 +10,7 @@ Mail gem. It provides a way to make emails using templates in the same
way that Action Controller renders views using templates.
Additionally, an Action Mailer class can be used to process incoming email,
-such as allowing a weblog to accept new posts from an email (which could even
+such as allowing a blog to accept new posts from an email (which could even
have been sent from a phone).
== Sending emails
@@ -74,7 +74,7 @@ Or you can just chain the methods together like:
== Setting defaults
-It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you wont need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
Note that every value you set with this method will get over written if you use the same key in your mailer method.
@@ -135,13 +135,13 @@ The Base class has the full list of configuration options. Here's an example:
== Download and installation
-The latest version of Action Mailer can be installed with Rubygems:
+The latest version of Action Mailer can be installed with RubyGems:
% [sudo] gem install actionmailer
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/actionmailer/
+* https://github.com/rails/rails/tree/master/actionmailer
== License
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index b0f7d0bc11..6b654e149e 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -30,6 +30,12 @@
*Rails 3.1.0 (unreleased)*
+* Param values are `paramified` in controller tests. [David Chelimsky]
+
+* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. [Santiago Pastorino]
+
+* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti]
+
* Make sure respond_with with :js tries to render a template in all cases [José Valim]
* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
@@ -101,11 +107,11 @@
For example if you have this route:
- map '*pages' => 'pages#show'
+ match '*pages' => 'pages#show'
by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
- map '*pages' => 'pages#show', :format => false
+ match '*pages' => 'pages#show', :format => false
* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH]
@@ -2268,7 +2274,7 @@ superclass' view_paths. [Rick Olson]
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
@@ -2865,7 +2871,7 @@ superclass' view_paths. [Rick Olson]
* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index c494d78415..95301c21ee 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -283,7 +283,7 @@ methods:
The last two lines are responsible for telling ActionController where the
template files are located and actually running the controller on a new
-request from the web-server (like to be Apache).
+request from the web-server (e.g., Apache).
And the templates look like this:
@@ -316,13 +316,13 @@ an URL such as /weblog/5 (where 5 is the id of the post).
== Download and installation
-The latest version of Action Pack can be installed with Rubygems:
+The latest version of Action Pack can be installed with RubyGems:
% [sudo] gem install actionpack
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/actionpack/
+* https://github.com/rails/rails/tree/master/actionpack
== License
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 642fbcb8e6..59d5bbc5ed 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -18,13 +18,14 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.0.2')
+ s.add_dependency('rack-cache', '~> 1.0.3')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('rack', '~> 1.3.0')
- s.add_dependency('rack-test', '~> 0.6.0')
- s.add_dependency('rack-mount', '~> 0.8.1')
- s.add_dependency('sprockets', '= 2.0.0.beta.10')
- s.add_dependency('tzinfo', '~> 0.3.29')
+ s.add_dependency('rack', '~> 1.3.2')
+ s.add_dependency('rack-test', '~> 0.6.1')
+ s.add_dependency('rack-mount', '~> 0.8.2')
+ s.add_dependency('sprockets', '~> 2.0.0.beta.15')
s.add_dependency('erubis', '~> 2.7.0')
+
+ s.add_development_dependency('tzinfo', '~> 0.3.29')
end
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index ad14cd6d87..b104d34fb5 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir, :use_sprockets
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir
end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index e8426bc52b..14c984e41f 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -75,38 +75,122 @@ module AbstractController
end
end
+ ##
+ # :method: before_filter
+ #
+ # :call-seq: before_filter(names, block)
+ #
+ # Append a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_before_filter
+ #
+ # :call-seq: prepend_before_filter(names, block)
+ #
+ # Prepend a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_before_filter
+ #
+ # :call-seq: skip_before_filter(names, block)
+ #
+ # Skip a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_before_filter
+ #
+ # :call-seq: append_before_filter(names, block)
+ #
+ # Append a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: after_filter
+ #
+ # :call-seq: after_filter(names, block)
+ #
+ # Append an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_after_filter
+ #
+ # :call-seq: prepend_after_filter(names, block)
+ #
+ # Prepend an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_after_filter
+ #
+ # :call-seq: skip_after_filter(names, block)
+ #
+ # Skip an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_after_filter
+ #
+ # :call-seq: append_after_filter(names, block)
+ #
+ # Append an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: around_filter
+ #
+ # :call-seq: around_filter(names, block)
+ #
+ # Append an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_around_filter
+ #
+ # :call-seq: prepend_around_filter(names, block)
+ #
+ # Prepend an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_around_filter
+ #
+ # :call-seq: skip_around_filter(names, block)
+ #
+ # Skip an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_around_filter
+ #
+ # :call-seq: append_around_filter(names, block)
+ #
+ # Append an around filter. See _insert_callbacks for parameter details.
+
# set up before_filter, prepend_before_filter, skip_before_filter, etc.
# for each of before, after, and around.
[:before, :after, :around].each do |filter|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
# Append a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def #{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
- set_callback(:process_action, :#{filter}, name, options)
- end
- end
+ def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
+ set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# Prepend a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def prepend_#{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
- set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true))
- end
- end
+ def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
+ set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true))
+ end # end
+ end # end
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options)
- end
- end
+ def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :#{filter}, name, options)
+ end # end
+ end # end
# *_filter is the same as append_*_filter
- alias_method :append_#{filter}_filter, :#{filter}_filter
+ alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
RUBY_EVAL
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index d6f75bded0..10aa34c76b 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -139,7 +139,7 @@ module AbstractController
#
# end
#
- # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
+ # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
# be rendered directly, without wrapping a layout around the rendered view.
#
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
@@ -167,6 +167,7 @@ module AbstractController
included do
class_attribute :_layout_conditions
+ remove_possible_method :_layout_conditions
delegate :_layout_conditions, :to => :'self.class'
self._layout_conditions = {}
_write_layout_method
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 6b7aae8c74..e8394447a7 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -1,3 +1,5 @@
+require 'action_view/base'
+
module AbstractController
module ViewPaths
extend ActiveSupport::Concern
@@ -63,7 +65,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def append_view_path(path)
- self.view_paths = view_paths.dup + Array(path)
+ self._view_paths = view_paths + Array(path)
end
# Prepend a path to the list of view paths for this controller.
@@ -73,7 +75,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def prepend_view_path(path)
- self.view_paths = Array(path) + view_paths.dup
+ self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
end
# A list of all of the default view paths for this controller.
@@ -87,8 +89,7 @@ module AbstractController
# * <tt>paths</tt> - If a PathSet is provided, use that;
# otherwise, process the parameter into a PathSet.
def view_paths=(paths)
- self._view_paths = ActionView::Base.process_view_paths(paths)
- self._view_paths.freeze
+ self._view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f13fd71050..f4eaa2fd1b 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -47,21 +47,6 @@ module ActionController
eager_autoload do
autoload :RecordIdentifier
-
- # TODO: Don't autoload exceptions, setup explicit
- # requires for files that need them
- autoload_at "action_controller/metal/exceptions" do
- autoload :ActionControllerError
- autoload :RenderError
- autoload :RoutingError
- autoload :MethodNotAllowed
- autoload :NotImplemented
- autoload :UnknownController
- autoload :MissingFile
- autoload :RenderError
- autoload :SessionOverflowError
- autoload :UnknownHttpMethod
- end
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index d14c5f940b..da93c988c4 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -63,7 +63,7 @@ module ActionController
#
# == Sessions
#
- # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
+ # Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
@@ -212,16 +212,16 @@ module ActionController
# also include them at the bottom.
AbstractController::Callbacks,
+ # Append rescue at the bottom to wrap as much as possible.
+ Rescue,
+
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
Instrumentation,
# Params wrapper should come before instrumentation so they are
# properly showed in logs
- ParamsWrapper,
-
- # The same with rescue, append it at the end to wrap as much as possible.
- Rescue
+ ParamsWrapper
]
MODULES.each do |mod|
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 667ba15cc9..0031d2701f 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -38,7 +38,7 @@ module ActionController #:nodoc:
# <tt>:action => 'lists'</tt> is not the same as
# <tt>:action => 'list', :format => :xml</tt>.
#
- # You can set modify the default action cache path by passing a
+ # You can modify the default action cache path by passing a
# <tt>:cache_path</tt> option. This will be passed directly to
# <tt>ActionCachePath.path_for</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 50827d8107..5e077dd7bd 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/file/path'
+require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
@@ -16,7 +17,7 @@ module ActionController #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
+ # config.action_dispatch.x_sendfile_header.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 85d0f5f699..777a0ab343 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -58,8 +58,8 @@ module ActionController
def redirect_to(*args)
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
- payload[:status] = self.status
- payload[:location] = self.location
+ payload[:status] = response.status
+ payload[:location] = response.location
result
end
end
@@ -97,4 +97,4 @@ module ActionController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 2d8afc3a78..6acbb23907 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -6,31 +6,30 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_types'
module ActionController
- # Wraps parameters hash into nested hash. This will allow client to submit
- # POST request without having to specify a root element in it.
+ # Wraps the parameters hash into a nested hash. This will allow clients to submit
+ # POST requests without having to specify any root elements.
#
- # By default this functionality won't be enabled. You can enable
- # it globally by setting +ActionController::Base.wrap_parameters+:
- #
- # ActionController::Base.wrap_parameters = [:json]
+ # This functionality is enabled in +config/initializers/wrap_parameters.rb+
+ # and can be customized. If you are upgrading to \Rails 3.1, this file will
+ # need to be created for the functionality to be enabled.
#
# You could also turn it on per controller by setting the format array to
- # non-empty array:
+ # a non-empty array:
#
# class UsersController < ApplicationController
# wrap_parameters :format => [:json, :xml]
# end
#
- # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
# send JSON parameters like this:
#
# {"user": {"name": "Konata"}}
#
- # You can now just send a parameters like this:
+ # You can send parameters like this:
#
# {"name": "Konata"}
#
- # And it will be wrapped into a nested hash with the key name matching
+ # And it will be wrapped into a nested hash with the key name matching the
# controller's name. For example, if you're posting to +UsersController+,
# your new +params+ hash will look like this:
#
@@ -82,7 +81,7 @@ module ActionController
#
# ==== Examples
# wrap_parameters :format => :xml
- # # enables the parmeter wrapper for XML format
+ # # enables the parameter wrapper for XML format
#
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index dee7eb1ec8..4f311a1cf5 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -45,7 +45,7 @@ module ActionController
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
- # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
+ # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
# Examples:
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 2271470334..4d016271ea 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
+require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
@@ -78,7 +79,7 @@ module ActionController #:nodoc:
end
end
- # This is the method that defines the application behaviour when a request is found to be unverified.
+ # This is the method that defines the application behavior when a request is found to be unverified.
# By default, \Rails resets the session when it finds an unverified request.
def handle_unverified_request
reset_session
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index f4efeb33ba..d1813ee745 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -4,6 +4,11 @@ module ActionController
include RackDelegation
+ def recycle!
+ @_url_options = nil
+ end
+
+
# TODO: Clean this up
def process_with_new_base_test(request, response)
@_request = request
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 08132b1900..0b40b1fc4c 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -18,7 +18,7 @@
# @url = root_path # named route from the application.
# end
# end
-# =>
+#
module ActionController
module UrlFor
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 45bb641aee..40332da321 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -180,7 +180,7 @@ module ActionController
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@symbolized_path_params = nil
@method = @request_method = nil
- @fullpath = @ip = @remote_ip = nil
+ @fullpath = @ip = @remote_ip = @protocol = nil
@env['action_dispatch.request.query_parameters'] = {}
@set_cookies ||= {}
@set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
@@ -401,9 +401,7 @@ module ActionController
def paramify_values(hash_or_array_or_value)
case hash_or_array_or_value
when Hash
- hash_or_array_or_value.each do |key, value|
- hash_or_array_or_value[key] = paramify_values(value)
- end
+ Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
when Array
hash_or_array_or_value.map {|i| paramify_values(i)}
when Rack::Test::UploadedFile
@@ -416,7 +414,7 @@ module ActionController
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Ensure that numbers and symbols passed as params are converted to
# proper params, as is the case when engaging rack.
- paramify_values(parameters)
+ parameters = paramify_values(parameters)
# Sanity check for required instance variables so we can give an
# understandable error message.
@@ -450,6 +448,7 @@ module ActionController
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
+ @controller.recycle!
@controller.process_with_new_base_test(@request, @response)
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
index 22b3243104..4e1f016431 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
@@ -156,7 +156,7 @@ module HTML #:nodoc:
end
closing = ( scanner.scan(/\//) ? :close : nil )
- return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
+ return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
name.downcase!
unless closing
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 980c658ab7..5c48a60469 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -98,7 +98,8 @@ module ActionDispatch
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def valid_accept_header
- xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS)
+ (xhr? && (accept || content_mime_type)) ||
+ (accept && accept !~ BROWSER_LIKE_ACCEPTS)
end
def use_accept_header
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b22d426c1f..37d0a3e0b8 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
+require 'action_controller/metal/exceptions'
module ActionDispatch
class Request < Rack::Request
@@ -28,9 +29,9 @@ module ActionDispatch
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{env.sub(/^HTTP_/n, '').downcase}
- @env["#{env}"]
- end
+ def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
+ @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
+ end # end
METHOD
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index a15ad28f16..94fa747a79 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -11,24 +11,13 @@ module ActionDispatch
raise(ArgumentError, ':tempfile is required') unless @tempfile
end
- def open
- @tempfile.open
- end
-
- def path
- @tempfile.path
- end
-
def read(*args)
@tempfile.read(*args)
end
- def rewind
- @tempfile.rewind
- end
-
- def size
- @tempfile.size
+ # Delegate these methods to the tempfile.
+ [:open, :path, :rewind, :size].each do |method|
+ class_eval "def #{method}; @tempfile.#{method}; end"
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index c17c746096..a765c23dae 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/exception'
+require 'action_controller/metal/exceptions'
require 'active_support/notifications'
require 'action_dispatch/http/request'
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index f51cc3711b..1af89858d1 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,10 +1,9 @@
require "action_dispatch"
-require "rails"
module ActionDispatch
class Railtie < Rails::Railtie
config.action_dispatch = ActiveSupport::OrderedOptions.new
- config.action_dispatch.x_sendfile_header = ""
+ config.action_dispatch.x_sendfile_header = nil
config.action_dispatch.ip_spoofing_check = true
config.action_dispatch.show_exceptions = true
config.action_dispatch.best_standards_support = true
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 65895590bf..a5c1501f61 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,4 +1,3 @@
-require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/inclusion'
@@ -51,7 +50,7 @@ module ActionDispatch
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
SHORTHAND_REGEX = %r{^/[\w/]+$}
- WILDCARD_PATH = %r{\*([^/]+)$}
+ WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
def initialize(set, scope, path, options)
@set, @scope = set, scope
@@ -105,13 +104,13 @@ module ActionDispatch
# controllers with default routes like :controller/:action/:id(.:format), e.g:
# GET /admin/products/show/1
# => { :controller => 'admin/products', :action => 'show', :id => '1' }
- @options.reverse_merge!(:controller => /.+?/)
+ @options[:controller] ||= /.+?/
end
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
if path.match(WILDCARD_PATH) && @options[:format] != false
- @options.reverse_merge!(:"#{$1}" => /.+?/)
+ @options[$1.to_sym] ||= /.+?/
end
if @options[:format] == false
@@ -119,6 +118,8 @@ module ActionDispatch
path
elsif path.include?(":format") || path.end_with?('/')
path
+ elsif @options[:format] == true
+ "#{path}.:format"
else
"#{path}(.:format)"
end
@@ -190,13 +191,12 @@ module ActionDispatch
end
def blocks
- block = @scope[:blocks] || []
-
- if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
- block << @options[:constraints]
+ constraints = @options[:constraints]
+ if constraints.present? && !constraints.is_a?(Hash)
+ [constraints]
+ else
+ @scope[:blocks] || []
end
-
- block
end
def constraints
@@ -223,19 +223,11 @@ module ActionDispatch
end
def default_controller
- if @options[:controller]
- @options[:controller]
- elsif @scope[:controller]
- @scope[:controller]
- end
+ @options[:controller] || @scope[:controller]
end
def default_action
- if @options[:action]
- @options[:action]
- elsif @scope[:action]
- @scope[:action]
- end
+ @options[:action] || @scope[:action]
end
end
@@ -263,7 +255,7 @@ module ActionDispatch
# because this means it will be matched first. As this is the most popular route
# of most Rails applications, this is beneficial.
def root(options = {})
- match '/', options.reverse_merge(:as => :root)
+ match '/', { :as => :root }.merge(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -659,13 +651,13 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
- # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
- # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
- # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
- # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
- # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
- # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
+ # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@@ -879,9 +871,9 @@ module ActionDispatch
def initialize(entities, options = {})
@name = entities.to_s
- @path = (options.delete(:path) || @name).to_s
- @controller = (options.delete(:controller) || @name).to_s
- @as = options.delete(:as)
+ @path = (options[:path] || @name).to_s
+ @controller = (options[:controller] || @name).to_s
+ @as = options[:as]
@options = options
end
@@ -943,12 +935,11 @@ module ActionDispatch
DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
def initialize(entities, options)
+ super
+
@as = nil
- @name = entities.to_s
- @path = (options.delete(:path) || @name).to_s
- @controller = (options.delete(:controller) || plural).to_s
- @as = options.delete(:as)
- @options = options
+ @controller = (options[:controller] || plural).to_s
+ @as = options[:as]
end
def plural
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 49aef0bf72..e989a38d8b 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -124,14 +124,7 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
- if proxy
- proxy.send(named_route, *args)
- else
- # we need to use url_for, because polymorphic_url can be used in context of other than
- # current routes (e.g. engine's routes). As named routes from engine are not included
- # calling engine's named route directly would fail.
- url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args)
- end
+ (proxy || self).send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 5097f6732d..11228c597d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
+require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 480144fe9d..30048cd48a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -131,16 +131,20 @@ module ActionDispatch
#
# Examples:
#
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
+ # # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
+ # # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true
+ # # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33'
+ # # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
when String
options
when nil, Hash
- _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys)
+ _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index a2d639cd56..7381617dd7 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -55,16 +55,14 @@ module ActionDispatch
# assert_redirected_to @customer
#
def assert_redirected_to(options = {}, message=nil)
- validate_request!
-
assert_response(:redirect, message)
return true if options == @response.location
- redirected_to_after_normalization = normalize_argument_to_redirection(@response.location)
- options_after_normalization = normalize_argument_to_redirection(options)
+ redirect_is = normalize_argument_to_redirection(@response.location)
+ redirect_expected = normalize_argument_to_redirection(options)
- if redirected_to_after_normalization != options_after_normalization
- flunk "Expected response to be a redirect to <#{options_after_normalization}> but was a redirect to <#{redirected_to_after_normalization}>"
+ if redirect_is != redirect_expected
+ flunk "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 57a24a1bd9..b10aab9029 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,6 +1,7 @@
require 'uri'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'
+require 'action_controller/metal/exceptions'
module ActionDispatch
module Assertions
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 96d8fd0dfe..aae8377f8a 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -1,5 +1,6 @@
require 'zlib'
require 'active_support/core_ext/file'
+require 'action_controller/metal/exceptions'
module ActionView
class AssetPaths #:nodoc:
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 36a0a20066..43d67f2032 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/wrap'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
+require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
@@ -161,6 +162,7 @@ module ActionView #:nodoc:
value.is_a?(PathSet) ?
value.dup : ActionView::PathSet.new(Array.wrap(value))
end
+ deprecate :process_view_paths
def xss_safe? #:nodoc:
true
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 0c3f011c92..7d01e5ddb8 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,6 +1,7 @@
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
+require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Asset Tag Helpers
@@ -153,7 +154,7 @@ module ActionView
# "/release-#{RELEASE_NUMBER}#{asset_path}"
# }
#
- # This example would cause the following behaviour on all servers no
+ # This example would cause the following behavior on all servers no
# matter when they were deployed:
#
# image_tag("rails.png")
@@ -191,6 +192,7 @@ module ActionView
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
+ include TagHelper
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 0f8a63901e..25cc561608 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -83,11 +83,7 @@ module ActionView
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source)
- if config.use_sprockets
- asset_path(source, 'js')
- else
- asset_paths.compute_public_path(source, 'javascripts', 'js')
- end
+ asset_paths.compute_public_path(source, 'javascripts', 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6a724749f4..e850c258ce 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -8,8 +8,8 @@ module ActionView
module Helpers
# = Action View Date Helpers
#
- # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the
- # select-type methods share a number of common options that are as follows:
+ # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
+ # elements. All of the select-type methods share a number of common options that are as follows:
#
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
# would give birthday[month] instead of date[month] if passed to the <tt>select_month</tt> method.
@@ -18,7 +18,7 @@ module ActionView
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
# of "date[month]".
module DateHelper
- # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
+ # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
@@ -176,37 +176,37 @@ module ActionView
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
#
# ==== Examples
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute.
- # date_select("post", "written_on")
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
+ # date_select("article", "written_on")
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995.
- # date_select("post", "written_on", :start_year => 1995)
+ # date_select("article", "written_on", :start_year => 1995)
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
# # and without a day select box.
- # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
+ # date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
- # date_select("post", "written_on", :order => [:day, :month, :year])
+ # date_select("article", "written_on", :order => [:day, :month, :year])
#
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
# # lacking a year field.
# date_select("user", "birthday", :order => [:month, :day])
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # which is initially set to the date 3 days from the current date
- # date_select("post", "written_on", :default => 3.days.from_now)
+ # date_select("article", "written_on", :default => 3.days.from_now)
#
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
# # that will have a default day of 20.
# date_select("credit_card", "bill_due", :default => { :day => 20 })
#
# # Generates a date select with custom prompts.
- # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
+ # date_select("article", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
@@ -228,20 +228,20 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute.
- # time_select("post", "sunrise")
+ # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
+ # time_select("article", "sunrise")
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
# # the sunrise attribute.
- # time_select("post", "start_time", :include_seconds => true)
+ # time_select("article", "start_time", :include_seconds => true)
#
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
# time_select 'game', 'game_time', {:minute_step => 15}
#
# # Creates a time select tag with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
- # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # time_select("post", "written_on", :prompt => true) # generic prompts for all
+ # time_select("article", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
+ # time_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # time_select("article", "written_on", :prompt => true) # generic prompts for all
#
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
# time_select 'game', 'game_time', {:ampm => true}
@@ -261,36 +261,36 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on
+ # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
# # attribute.
- # datetime_select("post", "written_on")
+ # datetime_select("article", "written_on")
#
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
- # # post variable in the written_on attribute.
- # datetime_select("post", "written_on", :start_year => 1995)
+ # # article variable in the written_on attribute.
+ # datetime_select("article", "written_on", :start_year => 1995)
#
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
# # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
# # Generate a datetime select with hours in the AM/PM format
- # datetime_select("post", "written_on", :ampm => true)
+ # datetime_select("article", "written_on", :ampm => true)
#
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
# # as the written_on attribute.
- # datetime_select("post", "written_on", :discard_type => true)
+ # datetime_select("article", "written_on", :discard_type => true)
#
# # Generates a datetime select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # datetime_select("post", "written_on", :prompt => true) # generic prompts for all
+ # datetime_select("article", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # datetime_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # datetime_select("article", "written_on", :prompt => true) # generic prompts for all
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
- # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the
+ # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
@@ -343,15 +343,15 @@ module ActionView
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
- # it will be appended onto the <tt>:order</tt> passed in.
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
+ # If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # my_date = Time.today + 6.days
+ # my_date = Time.now + 6.days
#
- # # Generates a date select that defaults to the date in my_date (six days afteri today).
+ # # Generates a date select that defaults to the date in my_date (six days after today).
# select_date(my_date)
#
# # Generates a date select that defaults to today (no specified date).
@@ -422,7 +422,7 @@ module ActionView
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
- # The <tt>second</tt> can also be substituted for a second number.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
# ==== Examples
@@ -440,7 +440,7 @@ module ActionView
#
# # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
- # select_minute(14, :prompt => 'Choose seconds')
+ # select_second(14, :prompt => 'Choose seconds')
#
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
@@ -448,21 +448,21 @@ module ActionView
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
- # selected. The <tt>minute</tt> can also be substituted for a minute number.
+ # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
# ==== Examples
# my_time = Time.now + 6.hours
#
- # # Generates a select field for minutes that defaults to the minutes for the time in my_tiime.
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
# select_minute(my_time)
#
# # Generates a select field for minutes that defaults to the number given.
# select_minute(14)
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
- # # that is named 'stride' rather than 'second'.
- # select_minute(my_time, :field_name => 'stride')
+ # # that is named 'moment' rather than 'minute'.
+ # select_minute(my_time, :field_name => 'moment')
#
# # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
@@ -473,7 +473,7 @@ module ActionView
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
- # The <tt>hour</tt> can also be substituted for a hour number.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
#
# ==== Examples
@@ -485,8 +485,8 @@ module ActionView
# # Generates a select field for hours that defaults to the number given.
# select_hour(13)
#
- # # Generates a select field for hours that defaults to the minutes for the time in my_time
- # # that is named 'stride' rather than 'second'.
+ # # Generates a select field for hours that defaults to the hour for the time in my_time
+ # # that is named 'stride' rather than 'hour'.
# select_hour(my_time, :field_name => 'stride')
#
# # Generates a select field for hours with a custom prompt. Use <tt>:prompt => true</tt> for a
@@ -501,11 +501,11 @@ module ActionView
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
- # The <tt>date</tt> can also be substituted for a hour number.
+ # The <tt>date</tt> can also be substituted for a day number.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
# ==== Examples
- # my_date = Time.today + 2.days
+ # my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
# select_day(my_time)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 3a30263b49..f22c466666 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -49,7 +49,7 @@ module ActionView
# <label for="person_last_name">Last name</label>:
# <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
#
- # <input id="person_submit" name="commit" type="submit" value="Create Person" />
+ # <input name="commit" type="submit" value="Create Person" />
# </form>
#
# As you see, the HTML reflects knowledge about the resource in several spots,
@@ -80,7 +80,7 @@ module ActionView
# <label for="person_last_name">Last name</label>:
# <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
#
- # <input id="person_submit" name="commit" type="submit" value="Update Person" />
+ # <input name="commit" type="submit" value="Update Person" />
# </form>
#
# Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
@@ -233,7 +233,7 @@ module ActionView
# <% end %>
#
# If your resource has associations defined, for example, you want to add comments
- # to the post given that the routes are set correctly:
+ # to the document given that the routes are set correctly:
#
# <%= form_for([@document, @comment]) do |f| %>
# ...
@@ -259,8 +259,8 @@ module ActionView
# :remote => true
#
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
- # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular
- # POST arrangement, but ultimately the behaviour is the choice of the JavaScript driver implementor.
+ # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
+ # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
# a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
#
@@ -365,7 +365,7 @@ module ActionView
apply_form_for_options!(record, options)
end
- options[:html][:remote] = options.delete(:remote)
+ options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
@@ -859,7 +859,28 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
- # Returns a text_field of type "search".
+ # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
+ # some browsers.
+ #
+ # ==== Examples
+ #
+ # search_field(:user, :name)
+ # # => <input id="user_name" name="user[name]" size="30" type="search" />
+ # search_field(:user, :name, :autosave => false)
+ # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
+ # search_field(:user, :name, :results => 3)
+ # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
+ # # Assume request.host returns "www.example.com"
+ # search_field(:user, :name, :autosave => true)
+ # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
+ # search_field(:user, :name, :onsearch => true)
+ # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # search_field(:user, :name, :autosave => false, :onsearch => true)
+ # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # search_field(:user, :name, :autosave => true, :onsearch => true)
+ # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
+ #
def search_field(object_name, method, options = {})
options = options.stringify_keys
@@ -878,17 +899,29 @@ module ActionView
end
# Returns a text_field of type "tel".
+ #
+ # telephone_field("user", "phone")
+ # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
+ #
def telephone_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
end
alias phone_field telephone_field
# Returns a text_field of type "url".
+ #
+ # url_field("user", "homepage")
+ # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
+ #
def url_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
end
# Returns a text_field of type "email".
+ #
+ # email_field("user", "address")
+ # # => <input id="user_address" size="30" name="user[address]" type="email" />
+ #
def email_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
end
@@ -1194,8 +1227,12 @@ module ActionView
parent_builder.multipart = multipart if parent_builder
end
- def self.model_name
- @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
+ def self._to_partial_path
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
+ end
+
+ def to_partial_path
+ self.class._to_partial_path
end
def to_model
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 3dc6d65432..c677257d60 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -105,7 +105,10 @@ module ActionView
# Create a select tag and a series of contained option tags for the provided object and method.
# The option currently held by the object will be selected, provided that the object is available.
- # See options_for_select for the required format of the choices parameter.
+ #
+ # There are two possible formats for the choices parameter, corresponding to other helpers' output:
+ # * A flat collection: see options_for_select
+ # * A nested collection: see grouped_options_for_select
#
# Example with @post.person_id => 1:
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
@@ -320,12 +323,12 @@ module ActionView
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do | r |
- Array.wrap(r).map(&:to_s)
+ Array.wrap(r).map { |item| item.to_s }
end
container.map do |element|
html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map(&:to_s)
+ text, value = option_text_and_value(element).map { |item| item.to_s }
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
%(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
@@ -575,7 +578,14 @@ module ActionView
def to_select_tag(choices, options, html_options)
selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- select_content_tag(options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]), options, html_options)
+
+ if !choices.empty? && choices.try(:first).try(:second).respond_to?(:each)
+ option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
+ else
+ option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
+ end
+
+ select_content_tag(option_tags, options, html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 72bc4510b5..1ceb53fe9c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -30,7 +30,7 @@ module ActionView
# (by passing <tt>false</tt>).
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
- # submit behaviour. By default this behaviour is an ajax submit.
+ # submit behavior. By default this behavior is an ajax submit.
#
# ==== Examples
# form_tag('/posts')
@@ -56,8 +56,8 @@ module ActionView
# form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
# # form with custom authenticity token
#
- def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
- html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
+ def form_tag(url_for_options = {}, options = {}, &block)
+ html_options = html_options_for_form(url_for_options, options)
if block_given?
form_tag_in_block(html_options, &block)
else
@@ -177,9 +177,12 @@ module ActionView
# label_tag 'name', nil, :class => 'small_label'
# # => <label for="name" class="small_label">Name</label>
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options.stringify_keys!
+ if block_given? && content_or_options.is_a?(Hash)
+ options = content_or_options = content_or_options.stringify_keys
+ else
+ options ||= {}
+ options = options.stringify_keys
+ end
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
content_tag :label, content_or_options || name.to_s.humanize, options, &block
end
@@ -304,7 +307,7 @@ module ActionView
# text_area_tag 'comment', nil, :class => 'comment_input'
# # => <textarea class="comment_input" id="comment" name="comment"></textarea>
def text_area_tag(name, content = nil, options = {})
- options.stringify_keys!
+ options = options.stringify_keys
if size = options.delete("size")
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
@@ -407,7 +410,7 @@ module ActionView
# data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
- options.stringify_keys!
+ options = options.stringify_keys
if disable_with = options.delete("disable_with")
options["data-disable-with"] = disable_with
@@ -417,7 +420,7 @@ module ActionView
options["data-confirm"] = confirm
end
- tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
+ tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
end
# Creates a button element that defines a <tt>submit</tt> button,
@@ -458,7 +461,7 @@ module ActionView
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
- options.stringify_keys!
+ options = options.stringify_keys
if disable_with = options.delete("disable_with")
options["data-disable-with"] = disable_with
@@ -497,13 +500,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!
+ options = options.stringify_keys
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
+ tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
end
# Creates a field set for grouping HTML form elements.
@@ -604,12 +607,12 @@ module ActionView
end
private
- def html_options_for_form(url_for_options, options, *parameters_for_url)
+ def html_options_for_form(url_for_options, options)
options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
# responsibility of the caller to escape all the values.
- html_options["action"] = url_for(url_for_options, *parameters_for_url)
+ html_options["action"] = url_for(url_for_options)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 4484390fde..1adcd716f8 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,4 +1,5 @@
require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
@@ -10,15 +11,23 @@ module ActionView
"\n" => '\n',
"\r" => '\n',
'"' => '\\"',
- "'" => "\\'" }
+ "'" => "\\'"
+ }
- # Escape carrier returns and single and double quotes for JavaScript segments.
+ if "ruby".encoding_aware?
+ JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
+ else
+ JS_ESCAPE_MAP["\342\200\250"] = '&#x2028;'
+ end
+
+ # Escapes carriage returns and single and double quotes for JavaScript segments.
+ #
# Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
#
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- result = javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {|match| JS_ESCAPE_MAP[match] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
''
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index fe0288521f..ec6c2c8db3 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index ae71ade588..21074efe86 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -256,7 +256,7 @@ module ActionView
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options={}, options={})
text = '' if text.nil?
- text = text.dup if text.frozen?
+ text = text.dup
start_tag = tag('p', html_options, true)
text = sanitize(text) unless options[:sanitize] == false
text = text.to_str
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 26b6e8b599..be64dc823e 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -25,7 +25,7 @@ module ActionView
# * a titleized version of the last key segment as a text.
#
# E.g. the value returned for a missing translation key :"blog.post.title" will be
- # <span class="translation_missing" title="translation missing: blog.post.title">Title</span>.
+ # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
# This way your views will display rather reasonable strings but it will still
# be easy to spot missing translations.
#
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index d70ae4196b..4dbb0135f6 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -55,8 +55,8 @@ module ActionView
#
# ==== Relying on named routes
#
- # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will
- # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
+ # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will
+ # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
#
@@ -113,7 +113,7 @@ module ActionView
end
# Creates a link tag of the given +name+ using a URL created by the set of +options+.
- # See the valid options in the documentation for +url_for+. It's also possible to
+ # See the valid options in the documentation for +url_for+. It's also possible to
# pass a String instead of an options hash, which generates a link tag that uses the
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
# of an options hash will generate a link to the referrer (a JavaScript back link
@@ -268,7 +268,7 @@ module ActionView
# to change the HTTP verb used to submit the form.
#
# ==== Options
- # The +options+ hash accepts the same options as url_for.
+ # The +options+ hash accepts the same options as +url_for+.
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
@@ -278,7 +278,7 @@ module ActionView
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
- # submit behaviour. By default this behaviour is an ajax submit.
+ # submit behavior. By default this behavior is an ajax submit.
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
#
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 560df15e82..9ec410ac2b 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -78,7 +78,7 @@ module ActionView
# Whenever setting view paths, makes a copy so we can manipulate then in
# instance objects as we wish.
def view_paths=(paths)
- @view_paths = ActionView::Base.process_view_paths(paths)
+ @view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
def find(name, prefixes = [], partial = false, keys = [])
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e0cb5d6a70..bbb1af8154 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -1,11 +1,55 @@
module ActionView #:nodoc:
# = Action View PathSet
- class PathSet < Array #:nodoc:
- %w(initialize << concat insert push unshift).each do |method|
+ class PathSet #:nodoc:
+ include Enumerable
+
+ attr_reader :paths
+
+ def initialize(paths = [])
+ @paths = typecast paths
+ end
+
+ def initialize_copy(other)
+ @paths = other.paths.dup
+ self
+ end
+
+ def [](i)
+ paths[i]
+ end
+
+ def to_ary
+ paths.dup
+ end
+
+ def include?(item)
+ paths.include? item
+ end
+
+ def pop
+ paths.pop
+ end
+
+ def size
+ paths.size
+ end
+
+ def each(&block)
+ paths.each(&block)
+ end
+
+ def compact
+ PathSet.new paths.compact
+ end
+
+ def +(array)
+ PathSet.new(paths + array)
+ end
+
+ %w(<< concat push insert unshift).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(*args)
- super
- typecast!
+ paths.#{method}(*typecast(args))
end
METHOD
end
@@ -17,7 +61,7 @@ module ActionView #:nodoc:
def find_all(path, prefixes = [], *args)
prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
- each do |resolver|
+ paths.each do |resolver|
templates = resolver.find_all(path, prefix, *args)
return templates unless templates.empty?
end
@@ -25,17 +69,20 @@ module ActionView #:nodoc:
[]
end
- def exists?(*args)
- find_all(*args).any?
+ def exists?(path, prefixes, *args)
+ find_all(path, prefixes, *args).any?
end
- protected
+ private
- def typecast!
- each_with_index do |path, i|
- path = path.to_s if path.is_a?(Pathname)
- next unless path.is_a?(String)
- self[i] = OptimizedFileSystemResolver.new(path)
+ def typecast(paths)
+ paths.map do |path|
+ case path
+ when Pathname, String
+ OptimizedFileSystemResolver.new path.to_s
+ else
+ path
+ end
end
end
end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index a351fbc04f..cd0f7054a9 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -12,8 +12,7 @@ module ActionView
#
# <%= render :partial => "account" %>
#
- # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
- # +account+ to the template for display.
+ # This would render "advertiser/_account.html.erb".
#
# In another template for Advertiser#buy, we could have:
#
@@ -28,32 +27,24 @@ module ActionView
#
# == The :as and :object options
#
- # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same
- # name as the template. So, given
+ # By default <tt>ActionView::Partials::PartialRenderer</tt> doesn't have any local variables.
+ # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
#
- # <%= render :partial => "contract" %>
+ # <%= render :partial => "account", :object => @buyer %>
#
- # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written
+ # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # equivalent to:
#
- # <%= render :partial => "contract", :locals => { :contract => @contract } %>
+ # <%= render :partial => "account", :locals => { :account => @buyer } %>
#
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
- # wanted it to be +agreement+ instead of +contract+ we'd do:
+ # wanted it to be +user+ instead of +account+ we'd do:
#
- # <%= render :partial => "contract", :as => 'agreement' %>
+ # <%= render :partial => "account", :object => @buyer, :as => 'user' %>
#
- # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
- # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
+ # This is equivalent to
#
- # Revisiting a previous example we could have written this code:
- #
- # <%= render :partial => "account", :object => @buyer %>
- #
- # <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :object => ad %>
- # <% end %>
- #
- # The <tt>:object</tt> and <tt>:as</tt> options can be used together.
+ # <%= render :partial => "account", :locals => { :user => @buyer } %>
#
# == Rendering a collection of partials
#
@@ -215,11 +206,12 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer #:nodoc:
- PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
+ PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
- @partial_names = PARTIAL_NAMES[@lookup_context.prefixes.first]
+ @context_prefix = @lookup_context.prefixes.first
+ @partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
@@ -301,6 +293,12 @@ module ActionView
paths.map! { |path| retrieve_variable(path).unshift(path) }
end
+ if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
+ raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a letter or underscore, " +
+ "and is followed by any combinations of letters, numbers, or underscores.")
+ end
+
self
end
@@ -364,13 +362,32 @@ module ActionView
end
def partial_path(object = @object)
- @partial_names[object.class.name] ||= begin
- object = object.to_model if object.respond_to?(:to_model)
+ object = object.to_model if object.respond_to?(:to_model)
- object.class.model_name.partial_path.dup.tap do |partial|
- path = @lookup_context.prefixes.first
- partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
+ path = if object.respond_to?(:to_partial_path)
+ object.to_partial_path
+ else
+ ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
+ object.class.model_name.partial_path
+ end
+
+ @partial_names[path] ||= path.dup.tap do |object_path|
+ merge_prefix_into_object_path(@context_prefix, object_path)
+ end
+ end
+
+ def merge_prefix_into_object_path(prefix, object_path)
+ if prefix.include?(?/) && object_path.include?(?/)
+ overlap = []
+ prefix_array = File.dirname(prefix).split('/')
+ object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
+
+ prefix_array.each_with_index do |dir, index|
+ overlap << dir if dir == object_path_array[index]
end
+
+ object_path.gsub!(/^#{overlap.join('/')}\//,'')
+ object_path.insert(0, "#{File.dirname(prefix)}/")
end
end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 2b9427ace5..f855ea257c 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,5 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
+require "active_support/core_ext/io"
require "action_view/template"
module ActionView
@@ -68,7 +69,7 @@ module ActionView
# before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = sort_locals(locals)
+ locals = locals.map { |x| x.to_s }.sort!
if key && caching?
@cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
@@ -97,18 +98,6 @@ module ActionView
t.virtual_path ||= (cached ||= build_path(*path_info))
end
end
-
- if :symbol.respond_to?("<=>")
- def sort_locals(locals) #:nodoc:
- locals.sort.freeze
- end
- else
- def sort_locals(locals) #:nodoc:
- locals = locals.map{ |l| l.to_s }
- locals.sort!
- locals.freeze
- end
- end
end
# An abstract class that implements a Resolver with path semantics.
@@ -130,27 +119,35 @@ module ActionView
def query(path, details, formats)
query = build_query(path, details)
- templates = []
- sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
- Dir[query].each do |p|
- next if File.directory?(p) || !sanitizer[p].include?(p)
+ # deals with case-insensitive file systems.
+ sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
- handler, format = extract_handler_and_format(p, formats)
- contents = File.open(p, "rb") { |io| io.read }
+ template_paths = Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ !sanitizer[File.dirname(filename)].include?(filename)
+ }
- templates << Template.new(contents, File.expand_path(p), handler,
- :virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
- end
+ template_paths.map { |template|
+ handler, format = extract_handler_and_format(template, formats)
+ contents = File.binread template
- templates
+ Template.new(contents, File.expand_path(template), handler,
+ :virtual_path => path.virtual,
+ :format => format,
+ :updated_at => mtime(template))
+ }
end
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
- query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
- query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
+
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
+ query.gsub!(/\:prefix(\/)?/, prefix)
+
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
+ query.gsub!(/\:action/, partial)
details.each do |ext, variants|
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
@@ -159,9 +156,13 @@ module ActionView
File.expand_path(query, @path)
end
+ def escape_entry(entry)
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ end
+
# Returns the file mtime from the filesystem.
def mtime(p)
- File.stat(p).mtime
+ File.mtime(p)
end
# Extract handler and formats from path. If a format cannot be a found neither
@@ -235,15 +236,11 @@ module ActionView
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
exts = EXTENSIONS.map { |ext| details[ext] }
- query = File.join(@path, path)
-
- exts.each do |ext|
- query << "{"
- ext.compact.each { |e| query << ".#{e}," }
- query << "}"
- end
+ query = escape_entry(File.join(@path, path))
- query
+ query + exts.map { |ext|
+ "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
+ }.join
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
index 0236350576..7594ee4296 100644
--- a/actionpack/lib/sprockets/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
@@ -1,26 +1,55 @@
namespace :assets do
desc "Compile all the assets named in config.assets.precompile"
task :precompile do
- if ENV["RAILS_GROUPS"].to_s.empty?
- ENV["RAILS_GROUPS"] = "assets"
+ # We need to do this dance because RAILS_GROUPS is used
+ # too early in the boot process and changing here is already too late.
+ if ENV["RAILS_GROUPS"].to_s.empty? || ENV["RAILS_ENV"].to_s.empty?
+ ENV["RAILS_GROUPS"] ||= "assets"
+ ENV["RAILS_ENV"] ||= "production"
Kernel.exec $0, *ARGV
else
Rake::Task["environment"].invoke
- Sprockets::Helpers::RailsHelper
- assets = Rails.application.config.assets.precompile
- Rails.application.assets.precompile(*assets)
+ # Ensure that action view is loaded and the appropriate sprockets hooks get executed
+ ActionView::Base
+
+ # Always perform caching so that asset_path appends the timestamps to file references.
+ Rails.application.config.action_controller.perform_caching = true
+
+ config = Rails.application.config
+ env = Rails.application.assets
+ target = Rails.root.join("public#{config.assets.prefix}")
+
+ if env.respond_to?(:each_logical_path)
+ config.assets.precompile.each do |path|
+ env.each_logical_path do |logical_path|
+ if path.is_a?(Regexp)
+ next unless path.match(logical_path)
+ else
+ next unless File.fnmatch(path.to_s, logical_path)
+ end
+
+ if asset = env.find_asset(logical_path)
+ filename = target.join(asset.digest_path)
+ mkdir_p filename.dirname
+ asset.write_to(filename)
+ asset.write_to("#{filename}.gz") if filename.to_s =~ /\.(css|js)$/
+ end
+ end
+ end
+ else
+ # TODO: Remove this once we're depending on sprockets beta 15
+ assets = config.assets.precompile.dup
+ assets << {:to => target}
+ env.precompile(*assets)
+ end
end
end
desc "Remove compiled assets"
- task :clean => :environment do
- assets = Rails.application.config.assets
- public_asset_path = Rails.public_path + assets.prefix
- file_list = FileList.new("#{public_asset_path}/**/*")
- file_list.each do |file|
- rm_rf file
- rm_rf "#{file}.gz"
- end
+ task :clean => [:environment, 'tmp:cache:clear'] do
+ config = Rails.application.config
+ public_asset_path = File.join(Rails.public_path, config.assets.prefix)
+ rm_rf public_asset_path, :secure => true
end
end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index a95e7c0649..7a2bf8bef6 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -26,15 +26,10 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
asset.to_a.map { |dep|
- javascript_include_tag(dep, :debug => false, :body => true)
- }.join("\n").html_safe
+ super(dep.to_s, { :src => asset_path(dep, 'js', true) }.merge!(options))
+ }
else
- tag_options = {
- 'type' => "text/javascript",
- 'src' => asset_path(source, 'js', body)
- }.merge(options.stringify_keys)
-
- content_tag 'script', "", tag_options
+ super(source.to_s, { :src => asset_path(source, 'js', body) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -47,17 +42,10 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
asset.to_a.map { |dep|
- stylesheet_link_tag(dep, :debug => false, :body => true)
- }.join("\n").html_safe
+ super(dep.to_s, { :href => asset_path(dep, 'css', true, :request) }.merge!(options))
+ }
else
- tag_options = {
- 'rel' => "stylesheet",
- 'type' => "text/css",
- 'media' => "screen",
- 'href' => asset_path(source, 'css', body, :request)
- }.merge(options.stringify_keys)
-
- tag 'link', tag_options
+ super(source.to_s, { :href => asset_path(source, 'css', body, :request) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -70,8 +58,8 @@ module Sprockets
private
def debug_assets?
- params[:debug_assets] == '1' ||
- params[:debug_assets] == 'true'
+ config = Rails.application.config.assets
+ config.allow_debugging && (config.debug || params[:debug_assets])
end
# Override to specify an alternative prefix for asset path generation.
@@ -110,11 +98,22 @@ module Sprockets
asset_environment[source]
end
+ def digest_for(logical_path)
+ if asset = asset_environment[logical_path]
+ return asset.digest_path
+ end
+
+ logical_path
+ end
+
def rewrite_asset_path(source, dir)
if source[0] == ?/
source
else
- asset_environment.path(source, performing_caching?, dir)
+ source = digest_for(source) if performing_caching?
+ source = File.join(dir, source)
+ source = "/#{source}" unless source =~ /^\//
+ source
end
end
@@ -126,9 +125,14 @@ module Sprockets
end
end
- # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available
def performing_caching?
- config.action_controller.present? ? config.action_controller.perform_caching : config.perform_caching
+ # When included in Sprockets::Context, we need to ask the
+ # top-level config as the controller is not available.
+ if config.action_controller.present?
+ config.action_controller.perform_caching
+ else
+ config.perform_caching
+ end
end
end
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index c28bdc3061..c21bf57935 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -11,10 +11,27 @@ module Sprockets
load "sprockets/assets.rake"
end
- # Configure ActionController to use sprockets.
- initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app|
- ActiveSupport.on_load(:action_controller) do
- self.use_sprockets = app.config.assets.enabled
+ initializer "sprockets.environment" do |app|
+ config = app.config
+ next unless config.assets.enabled
+
+ require 'sprockets'
+
+ app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
+ env.logger = ::Rails.logger
+ env.version = ::Rails.env + "-#{config.assets.version}"
+
+ if config.assets.cache_store != false
+ env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
+ end
+ end
+
+ ActiveSupport.on_load(:action_view) do
+ include ::Sprockets::Helpers::RailsHelper
+
+ app.assets.context_class.instance_eval do
+ include ::Sprockets::Helpers::RailsHelper
+ end
end
end
@@ -23,21 +40,25 @@ module Sprockets
# are compiled, and so that other Railties have an opportunity to
# register compressors.
config.after_initialize do |app|
- assets = app.config.assets
- next unless assets.enabled
+ next unless app.assets
+ config = app.config
- app.assets = asset_environment(app)
+ config.assets.paths.each { |path| app.assets.append_path(path) }
- ActiveSupport.on_load(:action_view) do
- include ::Sprockets::Helpers::RailsHelper
+ if config.assets.compress
+ # temporarily hardcode default JS compressor to uglify. Soon, it will work
+ # the same as SCSS, where a default plugin sets the default.
+ unless config.assets.js_compressor == false
+ app.assets.js_compressor = LazyCompressor.new { expand_js_compressor(config.assets.js_compressor || :uglifier) }
+ end
- app.assets.context_class.instance_eval do
- include ::Sprockets::Helpers::RailsHelper
+ unless config.assets.css_compressor == false
+ app.assets.css_compressor = LazyCompressor.new { expand_css_compressor(config.assets.css_compressor) }
end
end
app.routes.prepend do
- mount app.assets => assets.prefix
+ mount app.assets => config.assets.prefix
end
if config.action_controller.perform_caching
@@ -46,37 +67,6 @@ module Sprockets
end
protected
- def asset_environment(app)
- require "sprockets"
-
- assets = app.config.assets
-
- env = Sprockets::Environment.new(app.root.to_s)
-
- env.static_root = File.join(app.root.join("public"), assets.prefix)
-
- if env.respond_to?(:append_path)
- assets.paths.each { |path| env.append_path(path) }
- else
- env.paths.concat assets.paths
- end
-
- env.logger = Rails.logger
-
- if env.respond_to?(:cache)
- env.cache = assets.cache_store || Rails.cache
- end
-
- if assets.compress
- # temporarily hardcode default JS compressor to uglify. Soon, it will work
- # the same as SCSS, where a default plugin sets the default.
- env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) }
- env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) }
- end
-
- env
- end
-
def expand_js_compressor(sym)
case sym
when :closure
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 24d071df39..aa7a01f6c9 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -142,7 +142,11 @@ class RoutedRackApp
end
class BasicController
- attr_accessor :request
+ attr_accessor :request, :params
+
+ def initialize
+ @params = {}
+ end
def config
@config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index f0fb113860..768ac713ca 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -225,6 +225,36 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
assert_equal session_id, cookies['_session_id']
end
end
+
+ def test_incoming_invalid_session_id_via_cookie_should_be_ignored
+ with_test_route_set do
+ open_session do |sess|
+ sess.cookies['_session_id'] = 'INVALID'
+
+ sess.get '/set_session_value'
+ new_session_id = sess.cookies['_session_id']
+ assert_not_equal 'INVALID', new_session_id
+
+ sess.get '/get_session_value'
+ new_session_id_2 = sess.cookies['_session_id']
+ assert_equal new_session_id, new_session_id_2
+ end
+ end
+ end
+
+ def test_incoming_invalid_session_id_via_parameter_should_be_ignored
+ with_test_route_set(:cookie_only => false) do
+ open_session do |sess|
+ sess.get '/set_session_value', :_session_id => 'INVALID'
+ new_session_id = sess.cookies['_session_id']
+ assert_not_equal 'INVALID', new_session_id
+
+ sess.get '/get_session_value'
+ new_session_id_2 = sess.cookies['_session_id']
+ assert_equal new_session_id, new_session_id_2
+ end
+ end
+ end
private
@@ -247,6 +277,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
session_class, ActiveRecord::SessionStore.session_class =
ActiveRecord::SessionStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
yield
+ ensure
ActiveRecord::SessionStore.session_class = session_class
end
end
diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_filter_test.rb
new file mode 100644
index 0000000000..3bbb981040
--- /dev/null
+++ b/actionpack/test/controller/default_url_options_with_filter_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+
+
+class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base
+
+ before_filter { I18n.locale = params[:locale] }
+ after_filter { I18n.locale = "en" }
+
+ def target
+ render :text => "final response"
+ end
+
+ def redirect
+ redirect_to :action => "target"
+ end
+
+ def default_url_options
+ {:locale => "de"}
+ end
+end
+
+class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase
+
+ # This test has it´s roots in issue #1872
+ test "should redirect with correct locale :de" do
+ get :redirect, :locale => "de"
+ assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de"
+ end
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 584d73668a..35a87c1aae 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/kernel/reporting'
ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 80c4fa2ee5..ccdfcb0b2c 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -6,6 +6,13 @@ module Another
class LogSubscribersController < ActionController::Base
wrap_parameters :person, :include => :name, :format => :json
+ class SpecialException < Exception
+ end
+
+ rescue_from SpecialException do
+ head :status => 406
+ end
+
def show
render :nothing => true
end
@@ -39,6 +46,10 @@ module Another
raise Exception
end
+ def with_rescued_exception
+ raise SpecialException
+ end
+
end
end
@@ -195,6 +206,14 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_match(/Completed 500/, logs.last)
end
+ def test_process_action_with_rescued_exception_includes_http_status_code
+ get :with_rescued_exception
+ wait
+
+ assert_equal 2, logs.size
+ assert_match(/Completed 406/, logs.last)
+ end
+
def logs
@logs ||= @logger.logged(:info)
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 92d4a6d98b..79041055bd 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -4,6 +4,11 @@ class WorkshopsController < ActionController::Base
end
class RedirectController < ActionController::Base
+ # empty method not used anywhere to ensure methods like
+ # `status` and `location` aren't called on `redirect_to` calls
+ def status; render :text => 'called status'; end
+ def location; render :text => 'called location'; end
+
def simple_redirect
redirect_to :action => "hello_world"
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index be59da9105..6bcd606bf4 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -14,6 +14,14 @@ module Fun
end
end
+module Quiz
+ class QuestionsController < ActionController::Base
+ def new
+ render :partial => Quiz::Question.new("Namespaced Partial")
+ end
+ end
+end
+
class TestController < ActionController::Base
protect_from_forgery
@@ -397,6 +405,14 @@ class TestController < ActionController::Base
render :template => "test/hello_world"
end
+ def render_with_explicit_unescaped_template
+ render :template => "test/h*llo_world"
+ end
+
+ def render_with_explicit_escaped_template
+ render :template => "test/hello_w*rld"
+ end
+
def render_with_explicit_string_template
render "test/hello_world"
end
@@ -1049,6 +1065,12 @@ class RenderTest < ActionController::TestCase
assert_response :success
end
+ def test_render_with_explicit_unescaped_template
+ assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template }
+ get :render_with_explicit_escaped_template
+ assert_equal "Hello w*rld!", @response.body
+ end
+
def test_render_with_explicit_string_template
get :render_with_explicit_string_template
assert_equal "<html>Hello world!</html>", @response.body
@@ -1251,6 +1273,12 @@ class RenderTest < ActionController::TestCase
assert_template('fun/games/_form')
end
+ def test_namespaced_object_partial
+ @controller = Quiz::QuestionsController.new
+ get :new
+ assert_equal "Namespaced Partial", @response.body
+ end
+
def test_partial_collection
get :partial_collection
assert_equal "Hello: davidHello: mary", @response.body
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 6ea492cf8b..3b1b5fc3ec 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -91,6 +91,15 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_multiple_resources_with_options
+ expected_options = {:controller => 'threads', :action => 'index'}
+
+ with_restful_routing :messages, :comments, expected_options.slice(:controller) do
+ assert_recognizes(expected_options, :path => 'comments')
+ assert_recognizes(expected_options, :path => 'messages')
+ end
+ end
+
def test_with_custom_conditions
with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
assert @routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index aa9d193436..5bf68decca 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,7 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
-require 'active_support/dependencies'
require 'active_support/core_ext/object/with_options'
class MilestonesController < ActionController::Base
@@ -1665,114 +1664,6 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) }
end
- def test_generate
- assert_equal '/admin/users', url_for(@routes, { :use_route => 'admin_users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users', :action => 'index' })
- assert_equal '/admin/users', url_for(@routes, { :action => 'index' }, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' })
- assert_equal '/people', url_for(@routes, { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' })
-
- assert_equal '/admin/posts', url_for(@routes, { :controller => 'admin/posts' })
- assert_equal '/admin/posts/new', url_for(@routes, { :controller => 'admin/posts', :action => 'new' })
-
- assert_equal '/blog/2009', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009 })
- assert_equal '/blog/2009/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 })
- assert_equal '/blog/2009/1/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 })
-
- assert_equal '/archive/2010', url_for(@routes, { :controller => 'archive', :action => 'index', :year => '2010' })
- assert_equal '/archive', url_for(@routes, { :controller => 'archive', :action => 'index' })
- assert_equal '/archive?year=january', url_for(@routes, { :controller => 'archive', :action => 'index', :year => 'january' })
-
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, {}, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/new', url_for(@routes, { :use_route => 'new_person' })
- assert_equal '/people/new', url_for(@routes, { :controller => 'people', :action => 'new' })
- assert_equal '/people/1', url_for(@routes, { :use_route => 'person', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1.xml', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => 1 })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => Model.new('1') })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, {}, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' })
- assert_equal '/people/1/edit', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1' })
- assert_equal '/people/1/edit.xml', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' })
- assert_equal '/people/1/edit', url_for(@routes, { :use_route => 'edit_person', :id => '1' })
- assert_equal '/people/1?legacy=true', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' })
- assert_equal '/people?legacy=true', url_for(@routes, { :controller => 'people', :action => 'index', :legacy => 'true' })
-
- assert_equal '/id_default/2', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '2' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '1' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => 1 })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default' })
- assert_equal '/optional/bar', url_for(@routes, { :controller => 'posts', :action => 'index', :optional => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
-
- assert_equal '/project', url_for(@routes, { :controller => 'project', :action => 'index' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index'}, {:project_id => '1' })
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }) }
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' })
-
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' })
- assert_equal '/clients?project_id=1', url_for(@routes, { :controller => 'projects', :action => 'index', :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' }, { :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' })
-
- assert_equal '/comment/20', url_for(@routes, { :id => 20 }, { :controller => 'comments', :action => 'show' })
- assert_equal '/comment/20', url_for(@routes, { :controller => 'comments', :id => 20, :action => 'show' })
- assert_equal '/comments/boo', url_for(@routes, { :controller => 'comments', :action => 'boo' })
-
- assert_equal '/ws/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1', :ws => true })
- assert_equal '/ws/posts', url_for(@routes, { :controller => 'posts', :action => 'index', :ws => true })
-
- assert_equal '/account', url_for(@routes, { :controller => 'account', :action => 'subscription' })
- assert_equal '/account/billing', url_for(@routes, { :controller => 'account', :action => 'billing' })
-
- assert_equal '/pages/1/notes/show/1', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/pages/1/notes/list', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'list' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'index' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :page_id => nil, :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :controller => 'notes' })
- assert_equal '/notes/print', url_for(@routes, { :controller => 'notes', :action => 'print' })
- assert_equal '/notes/print', url_for(@routes, {}, { :controller => 'notes', :action => 'print' })
-
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :action => 'index' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, {}, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/show/1', url_for(@routes, {}, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes', :id => '1' }, { :foo => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/list', url_for(@routes, { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' })
-
- assert_equal '/posts/ping', url_for(@routes, { :controller => 'posts', :action => 'ping' })
- assert_equal '/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts/create', url_for(@routes, { :action => 'create' }, { :controller => 'posts' })
- assert_equal '/posts?foo=bar', url_for(@routes, { :controller => 'posts', :foo => 'bar' })
- assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', url_for(@routes, { :controller => 'posts', :foo => ['bar', 'baz'] })
- assert_equal '/posts?page=2', url_for(@routes, { :controller => 'posts', :page => 2 })
- assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', url_for(@routes, { :controller => 'posts', :q => { :foo => { :a => 'b'}} })
-
- assert_equal '/news.rss', url_for(@routes, { :controller => 'news', :action => 'index', :format => 'rss' })
-
-
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :action => 'index' }) }
- end
-
def test_generate_extras
assert_equal ['/people', []], @routes.generate_extras(:controller => 'people')
assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar')
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 043d44500a..cba3aded2f 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -50,6 +50,10 @@ class TestTest < ActionController::TestCase
render :text => request.query_string
end
+ def test_protocol
+ render :text => request.protocol
+ end
+
def test_html_output
render :text => <<HTML
<html>
@@ -515,6 +519,12 @@ XML
)
end
+ def test_params_passing_doesnt_modify_in_place
+ page = {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ get :test_params, :page => page
+ assert_equal 2004, page[:year]
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
@@ -592,6 +602,19 @@ XML
assert_nil @request.symbolized_path_parameters[:id]
end
+ def test_request_protocol_is_reset_after_request
+ get :test_protocol
+ assert_equal "http://", @response.body
+
+ @request.env["HTTPS"] = "on"
+ get :test_protocol
+ assert_equal "https://", @response.body
+
+ @request.env.delete("HTTPS")
+ get :test_protocol
+ assert_equal "http://", @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
cookies['foo'] = 'bar'
get :no_op
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
new file mode 100644
index 0000000000..7b734ff0fb
--- /dev/null
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -0,0 +1,183 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'active_support/core_ext/object/with_options'
+
+module RoutingTestHelpers
+ def url_for(set, options, recall = nil)
+ set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
+ end
+end
+
+module ActionPack
+ class URLForIntegrationTest < ActiveSupport::TestCase
+ include RoutingTestHelpers
+
+ Model = Struct.new(:to_param)
+
+ Mapping = lambda {
+ namespace :admin do
+ resources :users, :posts
+ end
+
+ namespace 'api' do
+ root :to => 'users#index'
+ end
+
+ match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ :constraints => {
+ :year => /(19|20)\d\d/,
+ :month => /[01]?\d/,
+ :day => /[0-3]?\d/
+ },
+ :day => nil,
+ :month => nil
+
+ match 'archive/:year', :controller => 'archive', :action => 'index',
+ :defaults => { :year => nil },
+ :constraints => { :year => /\d{4}/ },
+ :as => "blog"
+
+ resources :people
+ #match 'legacy/people' => "people#index", :legacy => "true"
+
+ match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ match 'id_default(/:id)' => "foo#id_default", :id => 1
+ match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
+ match 'optional/:optional' => "posts#index"
+ match 'projects/:project_id' => "project#index", :as => "project"
+ match 'clients' => "projects#index"
+
+ match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ :postalcode => /# Postcode format
+ \d{5} #Prefix
+ (-\d{4})? #Suffix
+ /x
+ }, :as => "geocode"
+
+ match 'news(.:format)' => "news#index"
+
+ match 'comment/:id(/:action)' => "comments#show"
+ match 'ws/:controller(/:action(/:id))', :ws => true
+ match 'account(/:action)' => "account#subscription"
+ match 'pages/:page_id/:controller(/:action(/:id))'
+ match ':controller/ping', :action => 'ping'
+ match ':controller(/:action(/:id))(.:format)'
+ root :to => "news#index"
+ }
+
+ def setup
+ @routes = ActionDispatch::Routing::RouteSet.new
+ @routes.draw(&Mapping)
+ end
+
+ [
+ ['/admin/users',[ { :use_route => 'admin_users' }]],
+ ['/admin/users',[ { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]],
+ ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' }]],
+ ['/people',[ { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' }]],
+
+ ['/admin/posts',[ { :controller => 'admin/posts' }]],
+ ['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]],
+
+ ['/blog/2009',[ { :controller => 'posts', :action => 'show_date', :year => 2009 }]],
+ ['/blog/2009/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 }]],
+ ['/blog/2009/1/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 }]],
+
+ ['/archive/2010',[ { :controller => 'archive', :action => 'index', :year => '2010' }]],
+ ['/archive',[ { :controller => 'archive', :action => 'index' }]],
+ ['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]],
+
+ ['/people',[ { :controller => 'people', :action => 'index' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ {}, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/new',[ { :use_route => 'new_person' }]],
+ ['/people/new',[ { :controller => 'people', :action => 'new' }]],
+ ['/people/1',[ { :use_route => 'person', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]],
+ ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }]],
+ ['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]],
+ ['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]],
+ ['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]],
+ ['/people/1?legacy=true',[ { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' }]],
+ ['/people?legacy=true',[ { :controller => 'people', :action => 'index', :legacy => 'true' }]],
+
+ ['/id_default/2',[ { :controller => 'foo', :action => 'id_default', :id => '2' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => '1' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => 1 }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default' }]],
+ ['/optional/bar',[ { :controller => 'posts', :action => 'index', :optional => 'bar' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+
+ ['/project',[ { :controller => 'project', :action => 'index' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' }]],
+
+ ['/clients',[ { :controller => 'projects', :action => 'index' }]],
+ ['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+ ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1' }]],
+ ['/clients',[ { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+
+ ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }]],
+ ['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]],
+ ['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]],
+
+ ['/ws/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :ws => true }]],
+ ['/ws/posts',[ { :controller => 'posts', :action => 'index', :ws => true }]],
+
+ ['/account',[ { :controller => 'account', :action => 'subscription' }]],
+ ['/account/billing',[ { :controller => 'account', :action => 'billing' }]],
+
+ ['/pages/1/notes/show/1',[ { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/pages/1/notes/list',[ { :page_id => '1', :controller => 'notes', :action => 'list' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes', :action => 'index' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes' }]],
+ ['/notes',[ { :page_id => nil, :controller => 'notes' }]],
+ ['/notes',[ { :controller => 'notes' }]],
+ ['/notes/print',[ { :controller => 'notes', :action => 'print' }]],
+ ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }]],
+
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1' }]],
+ ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes', :id => '1' }, { :foo => 'bar' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+
+ ['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]],
+ ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]],
+ ['/posts',[ { :controller => 'posts' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'posts', :action => 'index' }]],
+ ['/posts/create',[ { :action => 'create' }, { :controller => 'posts' }]],
+ ['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]],
+ ['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]],
+ ['/posts?page=2', [{ :controller => 'posts', :page => 2 }]],
+ ['/posts?q%5Bfoo%5D%5Ba%5D=b', [{ :controller => 'posts', :q => { :foo => { :a => 'b'}} }]],
+
+ ['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]],
+ ].each_with_index do |(url, params), i|
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ assert_equal url, url_for(@routes, *params), params.inspect
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index b6c08ffc33..3316dd03aa 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -35,6 +35,13 @@ module ActionDispatch
Mapper.new FakeSet.new
end
+ def test_mapping_requirements
+ options = { :controller => 'foo', :action => 'bar' }
+ m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options
+ _, _, requirements, _ = m.to_route
+ assert_equal(/.+?/, requirements[:rest])
+ end
+
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
@@ -83,6 +90,13 @@ module ActionDispatch
assert_equal '/*path', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
+
+ def test_map_wildcard_with_format_true
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show', :format => true
+ assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
+ end
end
end
end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index b28a058250..93eccaecbd 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -50,7 +50,9 @@ module TestGenerationPrefix
scope "/:omg", :omg => "awesome" do
mount BlogEngine => "/blog", :as => "blog_engine"
end
+ match "/posts/:id", :to => "outside_engine_generating#post", :as => :post
match "/generate", :to => "outside_engine_generating#index"
+ match "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
match "/conflicting_url", :to => "outside_engine_generating#conflicting"
@@ -101,6 +103,7 @@ module TestGenerationPrefix
class ::OutsideEngineGeneratingController < ActionController::Base
include BlogEngine.routes.mounted_helpers
+ include RailsApplication.routes.url_helpers
def index
render :text => blog_engine.post_path(:id => 1)
@@ -110,6 +113,10 @@ module TestGenerationPrefix
render :text => blog_engine.polymorphic_path(Post.new)
end
+ def polymorphic_path_for_app
+ render :text => polymorphic_path(Post.new)
+ end
+
def polymorphic_with_url_for
render :text => blog_engine.url_for(Post.new)
end
@@ -201,6 +208,11 @@ module TestGenerationPrefix
assert_equal "/awesome/blog/posts/1", last_response.body
end
+ test "polymorphic_path_for_app" do
+ get "/polymorphic_path_for_app"
+ assert_equal "/posts/1", last_response.body
+ end
+
test "[APP] generating engine's url with url_for(@post)" do
get "/polymorphic_with_url_for"
assert_equal "http://example.org/awesome/blog/posts/1", last_response.body
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 25b1b4f745..060bcfb5ec 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -468,6 +468,12 @@ class RequestTest < ActiveSupport::TestCase
assert request.formats.empty?
end
+ test "formats with xhr request" do
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [Mime::JS], request.formats
+ end
+
test "ignore_accept_header" do
ActionDispatch::Request.ignore_accept_header = true
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index ba7506721f..9685b24c1c 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -504,6 +504,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+
+ scope '/italians' do
+ match '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ match '/sculptors', :to => 'italians#sculptors'
+ match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ end
end
end
@@ -845,6 +851,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ # tests the use of dup in url_for
+ def test_url_for_with_no_side_effects
+ # without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
+ original_options = {:controller => 'projects', :action => 'status'}
+ options = original_options.dup
+
+ url_for options
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
def test_projects_status
with_test_routes do
assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
@@ -2229,6 +2247,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
verify_redirect 'http://www.example.com/countries/all/cities'
end
+ def test_constraints_block_not_carried_to_following_routes
+ get '/italians/writers'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/sculptors'
+ assert_equal 'italians#sculptors', @response.body
+
+ get '/italians/painters/botticelli'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/painters/michelangelo'
+ assert_equal 'italians#painters', @response.body
+ end
+
def test_custom_resource_actions_defined_using_string
get '/customers/inactive'
assert_equal 'customers#inactive', @response.body
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index aa115608ef..4ee1d61146 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -40,6 +40,11 @@ class TestRequestTest < ActiveSupport::TestCase
req.cookie_jar["login"] = "XJ-122"
assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar)
+ assert_nothing_raised do
+ req.cookie_jar["login"] = nil
+ assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar)
+ end
+
req.cookie_jar.delete(:login)
assert_cookies({"user_name" => "david"}, req.cookie_jar)
diff --git a/actionpack/test/fixtures/test/_200.html.erb b/actionpack/test/fixtures/test/_200.html.erb
new file mode 100644
index 0000000000..c9f45675dc
--- /dev/null
+++ b/actionpack/test/fixtures/test/_200.html.erb
@@ -0,0 +1 @@
+<h1>Invalid partial</h1>
diff --git a/actionpack/test/fixtures/test/hello_w*rld.erb b/actionpack/test/fixtures/test/hello_w*rld.erb
new file mode 100644
index 0000000000..bc8fa5e0ca
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_w*rld.erb
@@ -0,0 +1 @@
+Hello w*rld! \ No newline at end of file
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index d5bd7256f7..d93433deac 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -443,7 +443,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_image_tag_windows_behaviour
old_asset_id, ENV["RAILS_ASSET_ID"] = ENV["RAILS_ASSET_ID"], "1"
- # This simulates the behaviour of File#exist? on windows when testing a file ending in "."
+ # This simulates the behavior of File#exist? on windows when testing a file ending in "."
# If the file "rails.png" exists, windows will return true when asked if "rails.png." exists (notice trailing ".")
# OS X, linux etc will return false in this case.
File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true)
@@ -481,7 +481,7 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_timebased_asset_id
- expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
+ expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
@@ -512,7 +512,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_timebased_asset_id_with_relative_url_root
@controller.config.relative_url_root = "/collaboration/hieraki"
- expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
+ expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 3f31edd5ce..8be0f452fb 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -42,7 +42,7 @@ class CompiledTemplatesTest < Test::Unit::TestCase
def render_without_cache(*args)
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
ActionView::Base.new(view_paths, {}).render(*args)
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index cc3d2cddf7..f898c22e1e 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -791,6 +791,23 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_remote_in_html
+ form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :put }) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_remote_without_html
@post.persisted = false
form_for(@post, :remote => true) do |f|
@@ -1874,6 +1891,17 @@ class FormHelperTest < ActionView::TestCase
assert_equal LabelledFormBuilder, klass
end
+ def test_form_for_with_labelled_builder_path
+ path = nil
+
+ form_for(@post, :builder => LabelledFormBuilder) do |f|
+ path = f.to_partial_path
+ ''
+ end
+
+ assert_equal 'labelled_form', path
+ end
+
class LabelledFormBuilderSubclass < LabelledFormBuilder; end
def test_form_for_with_labelled_builder_with_nested_fields_for_with_custom_builder
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index a4599a3f00..6aea991f7c 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -385,6 +385,42 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_grouped_collection_as_nested_array
+ @post = Post.new
+
+ countries_by_continent = [
+ ["<Africa>", [["<South Africa>", "<sa>"], ["Somalia", "so"]]],
+ ["Europe", [["Denmark", "dk"], ["Ireland", "ie"]]],
+ ]
+
+ assert_dom_equal(
+ [
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>},
+ %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>},
+ %Q{<option value="ie">Ireland</option></optgroup></select>},
+ ].join("\n"),
+ select("post", "origin", countries_by_continent)
+ )
+ end
+
+ def test_select_with_grouped_collection_as_hash
+ @post = Post.new
+
+ countries_by_continent = {
+ "<Africa>" => [["<South Africa>", "<sa>"], ["Somalia", "so"]],
+ "Europe" => [["Denmark", "dk"], ["Ireland", "ie"]],
+ }
+
+ assert_dom_equal(
+ [
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>},
+ %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>},
+ %Q{<option value="ie">Ireland</option></optgroup></select>},
+ ].join("\n"),
+ select("post", "origin", countries_by_continent)
+ )
+ end
+
def test_select_with_boolean_method
@post = Post.new
@post.allow_comments = false
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 979251bfd1..6eae9bf846 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -505,6 +505,36 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<fieldset class="format">Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
end
+
+ def test_text_area_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ text_area_tag "body", "hello world", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_submit_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ submit_tag "submit value", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_button_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ button_tag "button value", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_image_submit_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ image_submit_tag "submit source", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_image_label_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ label_tag "submit source", "title", options
+ assert_equal options, { :option => "random_option" }
+ end
def protect_against_forgery?
false
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index 678cb9eeeb..62ad6be680 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -5,6 +5,13 @@ class SanitizerTest < ActionController::TestCase
@sanitizer = nil # used by assert_sanitizer
end
+ def test_strip_tags_with_quote
+ sanitizer = HTML::FullSanitizer.new
+ string = '<" <img src="trollface.gif" onload="alert(1)"> hi'
+
+ assert_equal ' hi', sanitizer.sanitize(string)
+ end
+
def test_strip_tags
sanitizer = HTML::FullSanitizer.new
assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index dd8b7b7cd5..4b9c3c97b1 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/string/encoding'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
@@ -27,6 +28,11 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
+ if "ruby".encoding_aware?
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!)
+ else
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline))
+ end
assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 4187a0ac78..8a582030f6 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -98,6 +98,15 @@ module RenderTestCases
assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5)
end
+ def test_render_partial_with_invalid_name
+ @view.render(:partial => "test/200")
+ flunk "Render did not raise ArgumentError"
+ rescue ArgumentError => e
+ assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a letter or underscore, " +
+ "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ end
+
def test_render_partial_with_errors
@view.render(:partial => "test/raise")
flunk "Render did not raise Template::Error"
@@ -192,6 +201,36 @@ module RenderTestCases
@controller_view.render(customers, :greeting => "Hello")
end
+ class CustomerWithDeprecatedPartialPath
+ attr_reader :name
+
+ def self.model_name
+ Struct.new(:partial_path).new("customers/customer")
+ end
+
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ def test_render_partial_using_object_with_deprecated_partial_path
+ assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
+ assert_equal "Hello: nertzy",
+ @controller_view.render(CustomerWithDeprecatedPartialPath.new("nertzy"), :greeting => "Hello")
+ end
+ end
+
+ def test_render_partial_using_collection_with_deprecated_partial_path
+ assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
+ customers = [
+ CustomerWithDeprecatedPartialPath.new("nertzy"),
+ CustomerWithDeprecatedPartialPath.new("peeja")
+ ]
+ assert_equal "Hello: nertzyHello: peeja",
+ @controller_view.render(customers, :greeting => "Hello")
+ end
+ end
+
# TODO: The reason for this test is unclear, improve documentation
def test_render_partial_and_fallback_to_layout
assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
@@ -341,7 +380,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
# is not eager loaded
def setup
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first
setup_view(view_paths)
end
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
index f11d1bba15..6c1f97a44a 100644
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -4,33 +4,29 @@ require 'sprockets/helpers/rails_helper'
require 'mocha'
class SprocketsHelperTest < ActionView::TestCase
- tests Sprockets::Helpers::RailsHelper
+ include Sprockets::Helpers::RailsHelper
attr_accessor :assets
+ class MockRequest
+ def protocol() 'http://' end
+ def ssl?() false end
+ def host_with_port() 'localhost' end
+ end
+
def setup
super
- @controller = BasicController.new
- @controller.stubs(:params).returns({})
-
- @request = Class.new do
- def protocol() 'http://' end
- def ssl?() false end
- def host_with_port() 'localhost' end
- end.new
-
- @controller.request = @request
+ @controller = BasicController.new
+ @controller.request = MockRequest.new
@assets = Sprockets::Environment.new
- @assets.paths << FIXTURES.join("sprockets/app/javascripts")
- @assets.paths << FIXTURES.join("sprockets/app/stylesheets")
- @assets.paths << FIXTURES.join("sprockets/app/images")
+ @assets.append_path(FIXTURES.join("sprockets/app/javascripts"))
+ @assets.append_path(FIXTURES.join("sprockets/app/stylesheets"))
+ @assets.append_path(FIXTURES.join("sprockets/app/images"))
- application = Object.new
+ application = Struct.new(:config, :assets).new(config, @assets)
Rails.stubs(:application).returns(application)
- application.stubs(:config).returns(config)
- application.stubs(:assets).returns(@assets)
@config = config
@config.action_controller ||= ActiveSupport::InheritableOptions.new
@config.perform_caching = true
@@ -41,7 +37,7 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "asset_path" do
- assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png",
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
@@ -54,7 +50,7 @@ class SprocketsHelperTest < ActionView::TestCase
assert_equal "/dir/audio",
asset_path("/dir/audio")
end
-
+
test "asset_path with absolute urls" do
assert_equal "http://www.example.com/video/play",
asset_path("http://www.example.com/video/play")
@@ -74,7 +70,7 @@ class SprocketsHelperTest < ActionView::TestCase
assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
-
+
test "With a proc asset host that returns no protocol the url should be protocol relative" do
@controller.config.asset_host = Proc.new do |asset|
"assets-999.example.com"
@@ -101,7 +97,7 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "stylesheets served without a controller in scope cannot access the request" do
- remove_instance_variable("@controller")
+ @controller = nil
@config.action_controller.asset_host = Proc.new do |asset, request|
fail "This should not have been called."
end
@@ -111,12 +107,12 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do
- remove_instance_variable("@controller")
+ @controller = nil
@config.action_controller.asset_host = "assets-%d.example.com"
@config.action_controller.default_asset_host_protocol = :request
@config.action_controller.perform_caching = true
- assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png",
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
@@ -127,12 +123,12 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "javascript path" do
- assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js",
+ assert_match %r{/assets/application-[0-9a-f]+.js},
asset_path(:application, "js")
- assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ assert_match %r{/assets/xmlhr-[0-9a-f]+.js},
asset_path("xmlhr", "js")
- assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ assert_match %r{/assets/dir/xmlhr-[0-9a-f]+.js},
asset_path("dir/xmlhr.js", "js")
assert_equal "/dir/xmlhr.js",
@@ -145,28 +141,33 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "javascript include tag" do
- assert_equal '<script src="/assets/application-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
javascript_include_tag(:application)
- assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
javascript_include_tag("xmlhr")
- assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
javascript_include_tag("xmlhr.js")
assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>',
javascript_include_tag("http://www.example.com/xmlhr")
- assert_equal "<script src=\"/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js?body=1\" type=\"text/javascript\"></script>\n<script src=\"/assets/application-d41d8cd98f00b204e9800998ecf8427e.js?body=1\" type=\"text/javascript\"></script>",
+ assert_match %r{<script src=\"/assets/xmlhr-[0-9a-f]+.js" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-[0-9a-f]+.js" type=\"text/javascript\"></script>},
+ javascript_include_tag("xmlhr", "extra")
+
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
javascript_include_tag(:application, :debug => true)
- assert_equal "<script src=\"/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js\" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.js\" type=\"text/javascript\"></script>",
- javascript_include_tag("xmlhr", "extra")
+ @config.assets.allow_debugging = true
+ @config.assets.debug = true
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
+ javascript_include_tag(:application)
end
test "stylesheet path" do
- assert_equal "/assets/application-68b329da9893e34099c7d8ad5cb9c940.css", asset_path(:application, "css")
+ assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, "css")
- assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css")
- assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css")
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", "css")
+ assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", "css")
assert_equal "/dir/style.css", asset_path("/dir/style.css", "css")
assert_equal "http://www.example.com/css/style",
@@ -176,37 +177,58 @@ class SprocketsHelperTest < ActionView::TestCase
end
test "stylesheet link tag" do
- assert_equal '<link href="/assets/application-68b329da9893e34099c7d8ad5cb9c940.css" media="screen" rel="stylesheet" type="text/css" />',
+ assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag(:application)
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag("style")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag("style.css")
assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />',
stylesheet_link_tag("http://www.example.com/style.css")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />',
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="all" rel="stylesheet" type="text/css" />},
stylesheet_link_tag("style", :media => "all")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />',
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="print" rel="stylesheet" type="text/css" />},
stylesheet_link_tag("style", :media => "print")
- assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/application-68b329da9893e34099c7d8ad5cb9c940.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />",
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/extra-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style", "extra")
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag(:application, :debug => true)
- assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />",
- stylesheet_link_tag("style", "extra")
+ @config.assets.allow_debugging = true
+ @config.assets.debug = true
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application)
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :media => "print")
end
test "alternate asset prefix" do
stubs(:asset_prefix).returns("/themes/test")
- assert_equal "/themes/test/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css")
+ assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", "css")
end
test "alternate asset environment" do
assets = Sprockets::Environment.new
- assets.paths << FIXTURES.join("sprockets/alternate/stylesheets")
+ assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
+ stubs(:asset_environment).returns(assets)
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", "css")
+ end
+
+ test "alternate hash based on environment" do
+ assets = Sprockets::Environment.new
+ assets.version = 'development'
+ assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
stubs(:asset_environment).returns(assets)
- assert_equal "/assets/style-df0b97ad35a8e1f7f61097461f77c19a.css", asset_path("style", "css")
+ dev_path = asset_path("style", "css")
+
+ assets.version = 'production'
+ prod_path = asset_path("style", "css")
+
+ assert_not_equal prod_path, dev_path
end
end
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index 3d0bbba435..bf789cd8b7 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -39,7 +39,7 @@ class PeopleHelperTest < ActionView::TestCase
with_test_route_set do
person = mock(:name => "David")
person.class.extend ActiveModel::Naming
- _routes.url_helpers.expects(:hash_for_mocha_mock_path).with(person).returns("/people/1")
+ expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index f7c3986bb1..02f9609483 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -48,10 +48,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
end
- def test_simple_format_should_not_change_the_frozen_text_passed
+ def test_simple_format_should_not_change_the_text_passed
text = "<b>Ok</b><script>code!</script>"
text_clone = text.dup
- simple_format(text.freeze)
+ simple_format(text)
assert_equal text_clone, text
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index a70c02a429..78245c1f95 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -386,13 +386,11 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_javascript
snippet = mail_to("me@domain.com", "My email", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- assert snippet.html_safe?
end
def test_mail_to_with_javascript_unicode
snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- assert snippet.html_safe
end
def test_mail_with_options
@@ -421,6 +419,12 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
+ def test_mail_to_returns_html_safe_string
+ assert mail_to("david@loudthinking.com").html_safe?
+ assert mail_to("me@domain.com", "My email", :encode => "javascript").html_safe?
+ assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe?
+ end
+
# TODO: button_to looks at this ... why?
def protect_against_forgery?
false
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index c38349b95e..20e5816532 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,3 +1,7 @@
+* Add ability to define strict validation(with :strict => true option) that always raises exception when fails [Bogdan Gusiev]
+
+* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" [Grant Hutchins, Peter Jaros]
+
* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev]
*Rails 3.1.0 (unreleased)*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index b5b5edd52a..67701bc422 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -182,3 +182,30 @@ modules:
p.valid? # => true
{Learn more}[link:classes/ActiveModel/Validator.html]
+
+
+== Download and installation
+
+The latest version of Active Model can be installed with RubyGems:
+
+ % [sudo] gem install activemodel
+
+Source code can be downloaded as part of the Rails project on GitHub
+
+* https://github.com/rails/rails/tree/master/activemodel
+
+
+== License
+
+Active Model is released under the MIT license.
+
+
+== Support
+
+API documentation is at
+
+* http://api.rubyonrails.org
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://github.com/rails/rails/issues
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 562f07fcd7..e5075485bb 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -19,5 +19,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('bcrypt-ruby', '~> 2.1.4')
+ s.add_dependency('bcrypt-ruby', '~> 3.0.0')
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 6ee5e04267..bdc0eb4a0d 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -314,6 +314,7 @@ module ActiveModel
end
end
end
+ attribute_method_matchers_cache.clear
end
# Removes all the previously dynamically defined methods from the class
@@ -321,6 +322,7 @@ module ActiveModel
generated_attribute_methods.module_eval do
instance_methods.each { |m| undef_method(m) }
end
+ attribute_method_matchers_cache.clear
end
# Returns true if the attribute methods defined have been generated.
@@ -338,6 +340,29 @@ module ActiveModel
end
private
+ # The methods +method_missing+ and +respond_to?+ of this module are
+ # invoked often in a typical rails, both of which invoke the method
+ # +match_attribute_method?+. The latter method iterates through an
+ # array doing regular expression matches, which results in a lot of
+ # object creations. Most of the times it returns a +nil+ match. As the
+ # match result is always the same given a +method_name+, this cache is
+ # used to alleviate the GC, which ultimately also speeds up the app
+ # significantly (in our case our test suite finishes 10% faster with
+ # this cache).
+ def attribute_method_matchers_cache
+ @attribute_method_matchers_cache ||= {}
+ end
+
+ def attribute_method_matcher(method_name)
+ if attribute_method_matchers_cache.key?(method_name)
+ attribute_method_matchers_cache[method_name]
+ else
+ match = nil
+ attribute_method_matchers.detect { |method| match = method.match(method_name) }
+ attribute_method_matchers_cache[method_name] = match
+ end
+ end
+
class AttributeMethodMatcher
attr_reader :prefix, :suffix, :method_missing_target
@@ -411,12 +436,8 @@ module ActiveModel
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
- self.class.attribute_method_matchers.each do |method|
- if (match = method.match(method_name)) && attribute_method?(match.attr_name)
- return match
- end
- end
- nil
+ match = self.class.send(:attribute_method_matcher, method_name)
+ match && attribute_method?(match.attr_name) ? match : nil
end
# prevent method_missing from calling private methods with #send
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 1405b1bfe3..80a3ba51c3 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -1,9 +1,12 @@
+require 'active_support/concern'
+require 'active_support/inflector'
+
module ActiveModel
# == Active Model Conversions
#
- # Handles default conversions: to_model, to_key and to_param.
+ # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
#
- # Let's take for example this non persisted object.
+ # Let's take for example this non-persisted object.
#
# class ContactMessage
# include ActiveModel::Conversion
@@ -18,8 +21,11 @@ module ActiveModel
# cm.to_model == self # => true
# cm.to_key # => nil
# cm.to_param # => nil
+ # cm.to_path # => "contact_messages/contact_message"
#
module Conversion
+ extend ActiveSupport::Concern
+
# If your object is already designed to implement all of the Active Model
# you can use the default <tt>:to_model</tt> implementation, which simply
# returns self.
@@ -45,5 +51,23 @@ module ActiveModel
def to_param
persisted? ? to_key.join('-') : nil
end
+
+ # Returns a string identifying the path associated with the object.
+ # ActionPack uses this to find a suitable partial to represent the object.
+ def to_partial_path
+ self.class._to_partial_path
+ end
+
+ module ClassMethods #:nodoc:
+ # Provide a class level cache for the to_path. This is an
+ # internal method and should not be accessed directly.
+ def _to_partial_path #:nodoc:
+ @_to_partial_path ||= begin
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
+ collection = ActiveSupport::Inflector.tableize(self)
+ "#{collection}/#{element}".freeze
+ end
+ end
+ end
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 166cccf161..e3e71525fa 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -29,7 +29,7 @@ module ActiveModel
#
# include ActiveModel::Dirty
#
- # define_attribute_methods [:name]
+ # define_attribute_methods = [:name]
#
# def name
# @name
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 71ece15b71..843c0c3cb5 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -49,7 +49,7 @@ module ActiveModel
#
# The last three methods are required in your object for Errors to be
# able to generate error messages correctly and also handle multiple
- # languages. Of course, if you extend your object with ActiveModel::Translations
+ # languages. Of course, if you extend your object with ActiveModel::Translation
# you will not need to implement the last two. Likewise, using
# ActiveModel::Validations will handle the validation related methods
# for you.
@@ -63,7 +63,7 @@ module ActiveModel
class Errors
include Enumerable
- CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
attr_reader :messages
@@ -86,7 +86,7 @@ module ActiveModel
# Do the error messages include an error with key +error+?
def include?(error)
- messages.include? error
+ (v = messages[error]) && v.any?
end
# Get messages for +key+
@@ -174,7 +174,7 @@ module ActiveModel
to_a.size
end
- # Returns true if there are any errors, false if not.
+ # Returns true if no errors are found, false otherwise.
def empty?
all? { |k, v| v && v.empty? }
end
@@ -218,6 +218,9 @@ module ActiveModel
elsif message.is_a?(Proc)
message = message.call
end
+ if options[:strict]
+ raise ActiveModel::StrictValidationFailed, message
+ end
self[attribute] << message
end
@@ -319,4 +322,7 @@ module ActiveModel
I18n.translate(key, options)
end
end
+
+ class StrictValidationFailed < StandardError
+ end
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index b71ef4b22e..bfe7ea1869 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -43,6 +43,16 @@ module ActiveModel
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
+ # == Responds to <tt>to_partial_path</tt>
+ #
+ # Returns a string giving a relative path. This is used for looking up
+ # partials. For example, a BlogPost model might return "blog_posts/blog_post"
+ #
+ def test_to_partial_path
+ assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
+ assert_kind_of String, model.to_partial_path
+ end
+
# == Responds to <tt>valid?</tt>
#
# Returns a boolean that specifies whether the object is in a valid or invalid
@@ -66,15 +76,14 @@ module ActiveModel
# == Naming
#
- # Model.model_name must return a string with some convenience methods as
- # :human and :partial_path. Check ActiveModel::Naming for more information.
+ # Model.model_name must return a string with some convenience methods:
+ # :human, :singular, and :plural. Check ActiveModel::Naming for more information.
#
def test_model_naming
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
model_name = model.class.model_name
assert_kind_of String, model_name
assert_kind_of String, model_name.human
- assert_kind_of String, model_name.partial_path
assert_kind_of String, model_name.singular
assert_kind_of String, model_name.plural
end
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index bb0526adc3..bbdddfb50d 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -44,8 +44,13 @@ module ActiveModel
class StrictSanitizer < Sanitizer
def process_removed_attributes(attrs)
+ return if (attrs - insensitive_attributes).empty?
raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}"
end
+
+ def insensitive_attributes
+ ['id']
+ end
end
class Error < StandardError
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 4c1a82f413..f16459ede2 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,12 +1,15 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/deprecation'
module ActiveModel
class Name < String
attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
alias_method :cache_key, :collection
+ deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
+
def initialize(klass, namespace = nil, name = nil)
name ||= klass.name
super(name)
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index d48f2e8a1f..7a910d18e7 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -226,10 +226,10 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
- def update(observed_method, object) #:nodoc:
+ def update(observed_method, object, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object)
+ send(observed_method, object, &block)
end
# Special method sent by the observed class when it is inherited.
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 4a174cb62a..b9f6f6cbbf 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/array/wrap'
+
module ActiveModel
# == Active Model Serialization
@@ -69,18 +71,46 @@ module ActiveModel
def serializable_hash(options = nil)
options ||= {}
- only = Array.wrap(options[:only]).map(&:to_s)
- except = Array.wrap(options[:except]).map(&:to_s)
-
attribute_names = attributes.keys.sort
- if only.any?
- attribute_names &= only
- elsif except.any?
- attribute_names -= except
+ if only = options[:only]
+ attribute_names &= Array.wrap(only).map(&:to_s)
+ elsif except = options[:except]
+ attribute_names -= Array.wrap(except).map(&:to_s)
+ end
+
+ method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
+ hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }]
+
+ serializable_add_includes(options) do |association, records, opts|
+ hash[association] = if records.is_a?(Enumerable)
+ records.map { |a| a.serializable_hash(opts) }
+ else
+ records.serializable_hash(opts)
+ end
end
- method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact
- Hash[(attribute_names + method_names).map { |n| [n, send(n)] }]
+ hash
end
+
+ private
+ # Add associations specified via the <tt>:include</tt> option.
+ #
+ # Expects a block that takes as arguments:
+ # +association+ - name of the association
+ # +records+ - the association record(s) to be serialized
+ # +opts+ - options for the association records
+ def serializable_add_includes(options = {})
+ return unless include = options[:include]
+
+ unless include.is_a?(Hash)
+ include = Hash[Array.wrap(include).map { |n| [n, {}] }]
+ end
+
+ include.each do |association, opts|
+ if records = send(association)
+ yield association, records, opts
+ end
+ end
+ end
end
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 9812af43d6..64dda3bcee 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -101,6 +101,7 @@ module ActiveModel
@builder.tag!(*args) do
add_attributes_and_methods
+ add_includes
add_extra_behavior
add_procs
yield @builder if block_given?
@@ -120,6 +121,45 @@ module ActiveModel
end
end
+ def add_includes
+ @serializable.send(:serializable_add_includes, options) do |association, records, opts|
+ add_associations(association, records, opts)
+ end
+ end
+
+ # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
+ def add_associations(association, records, opts)
+ merged_options = opts.merge(options.slice(:builder, :indent))
+ merged_options[:skip_instruct] = true
+
+ if records.is_a?(Enumerable)
+ tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
+ type = options[:skip_types] ? { } : {:type => "array"}
+ association_name = association.to_s.singularize
+ merged_options[:root] = association_name
+
+ if records.empty?
+ @builder.tag!(tag, type)
+ else
+ @builder.tag!(tag, type) do
+ records.each do |record|
+ if options[:skip_types]
+ record_type = {}
+ else
+ record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
+ record_type = {:type => record_class}
+ end
+
+ record.to_xml merged_options.merge(record_type)
+ end
+ end
+ end
+ else
+ merged_options[:root] = association.to_s
+ records.to_xml(merged_options)
+ end
+ end
+
def add_procs
if procs = options.delete(:procs)
Array.wrap(procs).each do |proc|
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 01907ac9da..e628c6f306 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or
# false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index a9dcb0b505..6573a7d264 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index d3b8d31502..644cc814a7 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -59,6 +59,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 090e8cfbae..d3faa8c6a6 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -84,6 +84,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 9a9270d615..147e2ecb69 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -59,6 +59,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 144e73904e..eb7aac709d 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -96,6 +96,8 @@ module ActiveModel
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 0d1903362c..34d447a0fa 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -107,6 +107,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which corresponds to a method:
# * <tt>:greater_than</tt>
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index cfb4c33dcc..35af7152db 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -35,6 +35,8 @@ module ActiveModel
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
#
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 7ff42de00b..b85c2453fb 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -70,8 +70,8 @@ module ActiveModel
# validator's initializer as +options[:in]+ while other types including
# regular expressions and strings are passed as +options[:with]+
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+ and +:allow_nil+ can be given
- # to one specific validator, as a hash:
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
+ # can be given to one specific validator, as a hash:
#
# validates :password, :presence => { :if => :password_required? }, :confirmation => true
#
@@ -101,12 +101,24 @@ module ActiveModel
end
end
+ # This method is used to define validation that can not be corrected by end user
+ # and is considered exceptional.
+ # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt>
+ # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error
+ # when validation fails
+ # See <tt>validates</tt> for more information about validation itself.
+ def validates!(*attributes)
+ options = attributes.extract_options!
+ options[:strict] = true
+ validates(*(attributes << options))
+ end
+
protected
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
def _validates_default_keys
- [ :if, :unless, :on, :allow_blank, :allow_nil ]
+ [ :if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
def _parse_validates_options(options) #:nodoc:
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 65ae18a769..83aae206a6 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -61,7 +61,9 @@ module ActiveModel
# (e.g. <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- #
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
+
# If you pass any additional configuration options, they will be passed
# to the class and available as <tt>options</tt>:
#
@@ -101,7 +103,7 @@ module ActiveModel
# class Person
# include ActiveModel::Validations
#
- # validates :instance_validations
+ # validate :instance_validations
#
# def instance_validations
# validates_with MyValidator
@@ -116,7 +118,7 @@ module ActiveModel
# class Person
# include ActiveModel::Validations
#
- # validates :instance_validations, :on => :create
+ # validate :instance_validations, :on => :create
#
# def instance_validations
# validates_with MyValidator, MyOtherValidator
@@ -140,4 +142,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index 7669bf5f65..24552bcaf2 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/contact'
+require 'models/helicopter'
class ConversionTest < ActiveModel::TestCase
test "to_model default implementation returns self" do
@@ -22,4 +23,10 @@ class ConversionTest < ActiveModel::TestCase
test "to_param default implementation returns a string of ids for persisted records" do
assert_equal "1", Contact.new(:id => 1).to_param
end
-end \ No newline at end of file
+
+ test "to_path default implementation returns a string giving a relative path" do
+ assert_equal "contacts/contact", Contact.new.to_partial_path
+ assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path,
+ "ActiveModel::Conversion#to_partial_path caching should be class-specific"
+ end
+end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index a24cac40ad..85ca8ca835 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -38,6 +38,7 @@ class ErrorsTest < ActiveModel::TestCase
person.errors[:foo]
assert person.errors.empty?
assert person.errors.blank?
+ assert !person.errors.include?(:foo)
end
test "method validate! should work" do
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 62a6ec9c9b..676937b5e1 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -7,7 +7,7 @@ class SanitizerTest < ActiveModel::TestCase
class Authorizer < ActiveModel::MassAssignmentSecurity::PermissionSet
def deny?(key)
- key.in?(['admin'])
+ ['admin', 'id'].include?(key)
end
end
@@ -40,4 +40,12 @@ class SanitizerTest < ActiveModel::TestCase
end
end
+ test "mass assignment insensitive attributes" do
+ original_attributes = {'id' => 1, 'first_name' => 'allowed'}
+
+ assert_nothing_raised do
+ @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ end
+ end
+
end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index f814fcc56c..1777ce2aae 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -26,7 +26,9 @@ class NamingTest < ActiveModel::TestCase
end
def test_partial_path
- assert_equal 'post/track_backs/track_back', @model_name.partial_path
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
+ assert_equal 'post/track_backs/track_back', @model_name.partial_path
+ end
end
def test_human
@@ -56,7 +58,9 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
end
def test_partial_path
- assert_equal 'blog/posts/post', @model_name.partial_path
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
+ assert_equal 'blog/posts/post', @model_name.partial_path
+ end
end
def test_human
@@ -98,7 +102,9 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
end
def test_partial_path
- assert_equal 'blog/posts/post', @model_name.partial_path
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
+ assert_equal 'blog/posts/post', @model_name.partial_path
+ end
end
def test_human
@@ -136,7 +142,9 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
end
def test_partial_path
- assert_equal 'articles/article', @model_name.partial_path
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
+ assert_equal 'articles/article', @model_name.partial_path
+ end
end
def test_human
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 99b1f407ae..f6ec24ae57 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -17,6 +17,10 @@ class FooObserver < ActiveModel::Observer
def on_spec(record)
stub.event_with(record) if stub
end
+
+ def around_save(record)
+ yield :in_around_save
+ end
end
class Foo
@@ -133,4 +137,12 @@ class ObserverTest < ActiveModel::TestCase
foo = Foo.new
Foo.send(:notify_observers, :whatever, foo)
end
+
+ test "update passes a block on to the observer" do
+ yielded_value = nil
+ FooObserver.instance.update(:around_save, Foo.new) do |val|
+ yielded_value = val
+ end
+ assert_equal :in_around_save, yielded_value
+ end
end
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 8cc1ccb1e7..071cb9ff4e 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -1,13 +1,19 @@
require "cases/helper"
+require 'active_support/core_ext/object/instance_variables'
class SerializationTest < ActiveModel::TestCase
class User
include ActiveModel::Serialization
- attr_accessor :name, :email, :gender
+ attr_accessor :name, :email, :gender, :address, :friends
+
+ def initialize(name, email, gender)
+ @name, @email, @gender = name, email, gender
+ @friends = []
+ end
def attributes
- @attributes ||= {'name' => 'nil', 'email' => 'nil', 'gender' => 'nil'}
+ instance_values.except("address", "friends")
end
def foo
@@ -15,11 +21,25 @@ class SerializationTest < ActiveModel::TestCase
end
end
+ class Address
+ include ActiveModel::Serialization
+
+ attr_accessor :street, :city, :state, :zip
+
+ def attributes
+ instance_values
+ end
+ end
+
setup do
- @user = User.new
- @user.name = 'David'
- @user.email = 'david@example.com'
- @user.gender = 'male'
+ @user = User.new('David', 'david@example.com', 'male')
+ @user.address = Address.new
+ @user.address.street = "123 Lane"
+ @user.address.city = "Springfield"
+ @user.address.state = "CA"
+ @user.address.zip = 11111
+ @user.friends = [User.new('Joe', 'joe@example.com', 'male'),
+ User.new('Sue', 'sue@example.com', 'female')]
end
def test_method_serializable_hash_should_work
@@ -42,4 +62,73 @@ class SerializationTest < ActiveModel::TestCase
assert_equal expected , @user.serializable_hash(:methods => [:foo])
end
+ def test_method_serializable_hash_should_work_with_only_and_methods
+ expected = {:foo=>"i_am_foo"}
+ assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo])
+ end
+
+ def test_method_serializable_hash_should_work_with_except_and_methods
+ expected = {"gender"=>"male", :foo=>"i_am_foo"}
+ assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
+ end
+
+ def test_should_not_call_methods_that_dont_respond
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected , @user.serializable_hash(:methods => [:bar])
+ end
+
+ def test_include_option_with_singular_association
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
+ :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
+ assert_equal expected , @user.serializable_hash(:include => :address)
+ end
+
+ def test_include_option_with_plural_association
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected , @user.serializable_hash(:include => :friends)
+ end
+
+ def test_include_option_with_empty_association
+ @user.friends = []
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]}
+ assert_equal expected , @user.serializable_hash(:include => :friends)
+ end
+
+ def test_multiple_includes
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected , @user.serializable_hash(:include => [:address, :friends])
+ end
+
+ def test_include_with_options
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane"}}
+ assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}})
+ end
+
+ def test_nested_include
+ @user.friends.first.friends = [@user]
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
+ :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]}
+ assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}})
+ end
+
+ def test_only_include
+ expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]}
+ assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
+ end
+
+ def test_except_include
+ expected = {"name"=>"David", "email"=>"david@example.com",
+ :friends => [{"name" => 'Joe', "email" => 'joe@example.com'},
+ {"name" => "Sue", "email" => 'sue@example.com'}]}
+ assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
+ end
+
end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index f978191d22..a38ef8e223 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -7,9 +7,11 @@ class Contact
extend ActiveModel::Naming
include ActiveModel::Serializers::Xml
+ attr_accessor :address, :friends
+
def attributes
- instance_values
- end unless method_defined?(:attributes)
+ instance_values.except("address", "friends")
+ end
end
module Admin
@@ -20,6 +22,17 @@ end
class Customer < Struct.new(:name)
end
+class Address
+ extend ActiveModel::Naming
+ include ActiveModel::Serializers::Xml
+
+ attr_accessor :street, :city, :state, :zip
+
+ def attributes
+ instance_values
+ end
+end
+
class XmlSerializationTest < ActiveModel::TestCase
def setup
@contact = Contact.new
@@ -30,6 +43,12 @@ class XmlSerializationTest < ActiveModel::TestCase
customer = Customer.new
customer.name = "John"
@contact.preferences = customer
+ @contact.address = Address.new
+ @contact.address.street = "123 Lane"
+ @contact.address.city = "Springfield"
+ @contact.address.state = "CA"
+ @contact.address.zip = 11111
+ @contact.friends = [Contact.new, Contact.new]
end
test "should serialize default root" do
@@ -138,4 +157,33 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<contact type="Contact">}, xml
assert_match %r{<name>aaron stack</name>}, xml
end
+
+ test "include option with singular association" do
+ xml = @contact.to_xml :include => :address, :indent => 0
+ assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ test "include option with plural association" do
+ xml = @contact.to_xml :include => :friends, :indent => 0
+ assert_match %r{<friends type="array">}, xml
+ assert_match %r{<friend type="Contact">}, xml
+ end
+
+ test "multiple includes" do
+ xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ]
+ assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true))
+ assert_match %r{<friends type="array">}, xml
+ assert_match %r{<friend type="Contact">}, xml
+ end
+
+ test "include with options" do
+ xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } }
+ assert xml.include?(%(><address><city>Springfield</city></address>))
+ end
+
+ test "propagates skip_types option to included associations" do
+ xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true
+ assert_match %r{<friends>}, xml
+ assert_match %r{<friend>}, xml
+ end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 0b50acf913..2f4376bd41 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -297,4 +297,37 @@ class ValidationsTest < ActiveModel::TestCase
assert auto.valid?
end
+
+ def test_strict_validation_in_validates
+ Topic.validates :title, :strict => true, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_not_fails
+ Topic.validates :title, :strict => true, :presence => true
+ assert Topic.new(:title => "hello").valid?
+ end
+
+ def test_strict_validation_particular_validator
+ Topic.validates :title, :presence => {:strict => true}
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_in_custom_validator_helper
+ Topic.validates_presence_of :title, :strict => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_validates_with_bang
+ Topic.validates! :title, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
end
diff --git a/activemodel/test/models/helicopter.rb b/activemodel/test/models/helicopter.rb
new file mode 100644
index 0000000000..a52b6fb4dd
--- /dev/null
+++ b/activemodel/test/models/helicopter.rb
@@ -0,0 +1,3 @@
+class Helicopter
+ include ActiveModel::Conversion
+end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 4113a16c12..700e11ff94 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,37 @@
+*Rails 3.2.0 (unreleased)*
+
+* Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton]
+
+* If multiple parameters are sent representing a date, and some are blank, the
+resulting object is nil. In previous releases those values defaulted to 1. This
+only affects existing but blank parameters, missing ones still raise an error.
+[Akira Matsuda]
+
+* ActiveRecord::Base.establish_connection now takes a string that contains
+a URI that specifies the connection configuration. For example:
+
+ ActiveRecord::Base.establish_connection 'postgres://localhost/foo'
+
+* Active Record's dynamic finder will now raise the error if you passing in less number of arguments than what you call in method signature.
+
+ So if you were doing this and expecting the second argument to be nil:
+
+ User.find_by_username_and_group("sikachu")
+
+ You'll now get `ArgumentError: wrong number of arguments (1 for 2).` You'll then have to do this:
+
+ User.find_by_username_and_group("sikachu", nil)
+
+ [Prem Sichanugrist]
+
*Rails 3.1.0 (unreleased)*
+* Add a proxy_association method to association proxies, which can be called by association
+ extensions to access information about the association. This replaces proxy_owner etc with
+ proxy_association.owner.
+
+ [Jon Leighton]
+
* ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature.
Before: def build_association(*options)
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 822276589b..b5db57569c 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -197,13 +197,13 @@ Admit the Database:
== Download and installation
-The latest version of Active Record can be installed with Rubygems:
+The latest version of Active Record can be installed with RubyGems:
% [sudo] gem install activerecord
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/activerecord/
+* https://github.com/rails/rails/tree/master/activerecord
== License
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 8ac4d9a225..2de81c31a3 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -21,6 +21,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 2.1.3')
+ s.add_dependency('arel', '~> 2.2.1')
s.add_dependency('tzinfo', '~> 0.3.29')
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 59cf42a377..132dc12680 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -21,13 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
require 'active_support'
require 'active_support/i18n'
require 'active_model'
@@ -41,6 +34,7 @@ module ActiveRecord
eager_autoload do
autoload :ActiveRecordError, 'active_record/errors'
autoload :ConnectionNotEstablished, 'active_record/errors'
+ autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
autoload :Aggregations
autoload :Associations
@@ -71,6 +65,7 @@ module ActiveRecord
autoload :Persistence
autoload :QueryCache
autoload :Reflection
+ autoload :Result
autoload :Schema
autoload :SchemaDumper
autoload :Serialization
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 029d7a9b15..8d755b6848 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -460,6 +460,12 @@ module ActiveRecord
# * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
# the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
#
+ # However, inside the actual extension code, you will not have access to the <tt>record</tt> as
+ # above. In this case, you can access <tt>proxy_association</tt>. For example,
+ # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
+ # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
+ # association extensions.
+ #
# === Association Join Models
#
# Has Many associations can be configured with the <tt>:through</tt> option to use an
@@ -1081,7 +1087,8 @@ module ActiveRecord
#
# [:finder_sql]
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
- # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+
+ # associations that depend on multiple tables. May be supplied as a string or a proc where interpolation is
+ # required. Note: When this option is used, +find_in_collection+
# is _not_ added.
# [:counter_sql]
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
@@ -1156,11 +1163,14 @@ module ActiveRecord
# has_many :tags, :as => :taggable
# has_many :reports, :readonly => true
# has_many :subscribers, :through => :subscriptions, :source => :user
- # has_many :subscribers, :class_name => "Person", :finder_sql =>
- # 'SELECT DISTINCT people.* ' +
- # 'FROM people p, post_subscriptions ps ' +
- # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
- # 'ORDER BY p.first_name'
+ # has_many :subscribers, :class_name => "Person", :finder_sql => Proc.new {
+ # %Q{
+ # SELECT DISTINCT people.*
+ # FROM people p, post_subscriptions ps
+ # WHERE ps.post_id = #{id} AND ps.person_id = p.id
+ # ORDER BY p.first_name
+ # }
+ # }
def has_many(name, options = {}, &extension)
Builder::HasMany.build(self, name, options, &extension)
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 92ed844a2e..0248c7483c 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -53,12 +53,18 @@ module ActiveRecord
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = connection.quote_table_name(name).downcase
- table_joins.map { |join|
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- }.sum
+ counts = table_joins.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ else
+ join.left.table_name == name ? 1 : 0
+ end
+ end
+
+ counts.sum
end
def truncate(name)
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index bb519c5703..d1e3ff8e38 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -151,20 +151,20 @@ module ActiveRecord
reset
end
+ def interpolate(sql, record = nil)
+ if sql.respond_to?(:to_proc)
+ owner.send(:instance_exec, record, &sql)
+ else
+ sql
+ end
+ end
+
private
def find_target?
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
end
- def interpolate(sql, record = nil)
- if sql.respond_to?(:to_proc)
- owner.send(:instance_exec, record, &sql)
- else
- sql
- end
- end
-
def creation_attributes
attributes = {}
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c263edd2c6..adb6af7165 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -37,7 +37,7 @@ module ActiveRecord
# Checks whether record is different to the current target, without loading it
def different_target?(record)
record.nil? && owner[reflection.foreign_key] ||
- record.id != owner[reflection.foreign_key]
+ record && record.id != owner[reflection.foreign_key]
end
def replace_keys(record)
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 198ad06360..2ee5dbbd70 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -2,6 +2,11 @@ module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
+ def klass
+ type = owner[reflection.foreign_type]
+ type.presence && type.constantize
+ end
+
private
def replace_keys(record)
@@ -17,11 +22,6 @@ module ActiveRecord
reflection.polymorphic_inverse_of(record.class)
end
- def klass
- type = owner[reflection.foreign_type]
- type.presence && type.constantize
- end
-
def raise_on_type_mismatch(record)
# A polymorphic association cannot have a type mismatch, by definition
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 3e68f973e7..cec876149c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -382,7 +382,7 @@ module ActiveRecord
persisted.map! do |record|
# Unfortunately we cannot simply do memory.delete(record) since on 1.8 this returns
- # record rather than memory.at(memory.index(record)). The behaviour is fixed in 1.9.
+ # record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
mem_index = memory.index(record)
if mem_index
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 827dfb7ccb..6ba3d45aff 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -58,23 +58,27 @@ module ActiveRecord
alias_method :new, :build
+ def proxy_association
+ @association
+ end
+
def respond_to?(name, include_private = false)
super ||
(load_target && target.respond_to?(name, include_private)) ||
- @association.klass.respond_to?(name, include_private)
+ proxy_association.klass.respond_to?(name, include_private)
end
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
if match && match.instantiator?
send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
- @association.send :set_owner_attributes, r
- @association.send :add_to_target, r
+ proxy_association.send :set_owner_attributes, r
+ proxy_association.send :add_to_target, r
yield(r) if block_given?
end
end
- if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
+ if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
if load_target
if target.respond_to?(method)
target.send(method, *args, &block)
@@ -104,7 +108,7 @@ module ActiveRecord
alias_method :to_a, :to_ary
def <<(*records)
- @association.concat(records) && self
+ proxy_association.concat(records) && self
end
alias_method :push, :<<
@@ -114,7 +118,7 @@ module ActiveRecord
end
def reload
- @association.reload
+ proxy_association.reload
self
end
end
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 f7ce70db1a..1f917f58f2 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
@@ -26,7 +26,7 @@ module ActiveRecord
join_table[reflection.association_foreign_key] => record.id
)
- owner.connection.insert stmt.to_sql
+ owner.connection.insert stmt
end
record
@@ -46,7 +46,7 @@ module ActiveRecord
stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
).compile_delete
- owner.connection.delete stmt.to_sql
+ owner.connection.delete stmt
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 504f25271c..6c878f0f00 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -188,13 +188,12 @@ module ActiveRecord
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
set_target_and_inverse(join_part, association, record)
else
- return if row[join_part.aliased_primary_key].nil?
- association = join_part.instantiate(row)
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
case macro
when :has_many, :has_and_belongs_to_many
other = record.association(join_part.reflection.name)
other.loaded!
- other.target.push(association)
+ other.target.push(association) if association
other.set_inverse_instance(association)
when :belongs_to
set_target_and_inverse(join_part, association, record)
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index 24be279449..b77b667219 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -13,7 +13,7 @@ module ActiveRecord
# access the aliased column on the join table
def records_for(ids)
scope = super
- klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values)
+ klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
end
def owner_key_name
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index e3f221c773..ed71b5e7d4 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -3,8 +3,7 @@ module ActiveRecord
module PrimaryKey
extend ActiveSupport::Concern
- # Returns this record's primary key value wrapped in an Array or nil if
- # the record is not persisted? or has just been destroyed.
+ # Returns this record's primary key value wrapped in an Array if one is available
def to_key
key = send(self.class.primary_key)
[key] if key
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index eb4a16ecf5..374791deb1 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -177,6 +177,10 @@ module ActiveRecord #:nodoc:
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
# <tt>Person.find_all_by_last_name(last_name)</tt>.
#
+ # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
+ # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
+ # like <tt>Person.find_by_last_name!</tt>.
+ #
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
# Person.where(:user_name => user_name, :password => password).first
@@ -504,8 +508,7 @@ module ActiveRecord #:nodoc:
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
- object = new(attributes, options)
- yield(object) if block_given?
+ object = new(attributes, options, &block)
object.save
object
end
@@ -621,6 +624,8 @@ module ActiveRecord #:nodoc:
# Computes the table name, (re)sets it internally, and returns it.
def reset_table_name #:nodoc:
+ return if abstract_class?
+
self.table_name = compute_table_name
end
@@ -938,17 +943,6 @@ module ActiveRecord #:nodoc:
self.current_scope = nil
end
- # Specifies how the record is loaded by +Marshal+.
- #
- # +_load+ sets an instance variable for each key in the hash it takes as input.
- # Override this method if you require more complex marshalling.
- def _load(data)
- record = allocate
- record.init_with(Marshal.load(data))
- record
- end
-
-
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
# objects of different types from the same table.
@@ -1052,20 +1046,15 @@ module ActiveRecord #:nodoc:
# Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
# is first invoked, so that future attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
- if match = DynamicFinderMatch.match(method_id)
+ if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
- if match.finder?
- options = arguments.extract_options!
- relation = options.any? ? scoped(options) : scoped
- relation.send :find_by_attributes, match, attribute_names, *arguments, &block
- elsif match.instantiator?
- scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
+ if arguments.size < attribute_names.size
+ method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
+ backtrace = [method_trace] + caller
+ raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
end
- elsif match = DynamicScopeMatch.match(method_id)
- attribute_names = match.attribute_names
- super unless all_attributes_exists?(attribute_names)
- if match.scope?
+ if match.respond_to?(:scope?) && match.scope?
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
@@ -1074,6 +1063,12 @@ module ActiveRecord #:nodoc:
end # end
METHOD
send(method_id, *arguments)
+ elsif match.finder?
+ options = arguments.extract_options!
+ relation = options.any? ? scoped(options) : scoped
+ relation.send :find_by_attributes, match, attribute_names, *arguments, &block
+ elsif match.instantiator?
+ scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
end
else
super
@@ -1267,27 +1262,43 @@ MSG
self.default_scopes = default_scopes + [scope]
end
- # The @ignore_default_scope flag is used to prevent an infinite recursion situation where
- # a default scope references a scope which has a default scope which references a scope...
def build_default_scope #:nodoc:
- return if defined?(@ignore_default_scope) && @ignore_default_scope
- @ignore_default_scope = true
-
if method(:default_scope).owner != Base.singleton_class
- default_scope
+ evaluate_default_scope { default_scope }
elsif default_scopes.any?
- default_scopes.inject(relation) do |default_scope, scope|
- if scope.is_a?(Hash)
- default_scope.apply_finder_options(scope)
- elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
- default_scope.merge(scope.call)
- else
- default_scope.merge(scope)
+ evaluate_default_scope do
+ default_scopes.inject(relation) do |default_scope, scope|
+ if scope.is_a?(Hash)
+ default_scope.apply_finder_options(scope)
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
+ default_scope.merge(scope.call)
+ else
+ default_scope.merge(scope)
+ end
end
end
end
- ensure
- @ignore_default_scope = false
+ end
+
+ def ignore_default_scope? #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"]
+ end
+
+ def ignore_default_scope=(ignore) #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"] = ignore
+ end
+
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
+ # a default scope references a scope which has a default scope which references a scope...
+ def evaluate_default_scope
+ return if ignore_default_scope?
+
+ begin
+ self.ignore_default_scope = true
+ yield
+ ensure
+ self.ignore_default_scope = false
+ end
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1409,9 +1420,8 @@ MSG
attrs = expand_hash_conditions_for_aggregates(attrs)
table = Arel::Table.new(table_name).alias(default_table_name)
- viz = Arel::Visitors.for(arel_engine)
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
- viz.accept b
+ connection.visitor.accept b
}.join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -1589,16 +1599,6 @@ MSG
self
end
- # Specifies how the record is dumped by +Marshal+.
- #
- # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
- # method if you require more complex marshalling.
- def _dump(level)
- dump = {}
- encode_with(dump)
- Marshal.dump(dump)
- end
-
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -1638,7 +1638,8 @@ MSG
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
+ timestamp = timestamp.utc.to_s(:number)
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
end
@@ -1736,7 +1737,7 @@ MSG
end
# Returns an <tt>#inspect</tt>-like string for the value of the
- # attribute +attr_name+. String attributes are elided after 50
+ # attribute +attr_name+. String attributes are truncated upto 50
# characters, and Date and Time attributes are returned in the
# <tt>:db</tt> format. Other attributes return the value of
# <tt>#inspect</tt> without modification.
@@ -1848,7 +1849,7 @@ MSG
ensure_proper_type
populate_with_current_scope_attributes
- clear_timestamp_attributes
+ super
end
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
@@ -2025,15 +2026,18 @@ MSG
# If Date bits were not provided, error
raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ # If Date bits were provided but blank, then return nil
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+
set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
- # If Date bits were provided but blank, then default to 1
# If Time bits are not there, then default to 0
- [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
instantiate_time_object(name, set_values)
end
def read_date_parameter_value(name, values_hash_from_param)
- set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+ set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
begin
Date.new(*set_values)
rescue ArgumentError # if Date.new raises an exception on an invalid date
@@ -2109,14 +2113,6 @@ MSG
send("#{att}=", value) if respond_to?("#{att}=")
end
end
-
- # Clear attributes and changed_attributes
- def clear_timestamp_attributes
- all_timestamp_attributes_in_model.each do |attribute_name|
- self[attribute_name] = nil
- changed_attributes.delete(attribute_name)
- end
- end
end
Base.class_eval do
@@ -2161,6 +2157,5 @@ MSG
end
end
-# TODO: Remove this and make it work with LAZY flag
-require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/abstract/connection_specification'
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index ddfdb05297..61994d4a47 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -82,10 +82,11 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
- @connections = []
- @checked_out = []
+ @connections = []
+ @checked_out = []
@automatic_reconnect = true
- @tables = {}
+ @tables = {}
+ @visitor = nil
@columns = Hash.new do |h, table_name|
h[table_name] = with_connection do |conn|
@@ -298,8 +299,18 @@ module ActiveRecord
:connected?, :disconnect!, :with => :@connection_mutex
private
+
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
+
+ # TODO: This is a bit icky, and in the long term we may want to change the method
+ # signature for connections. Also, if we switch to have one visitor per
+ # connection (and therefore per thread), we can get rid of the thread-local
+ # variable in Arel::Visitors::ToSql.
+ @visitor ||= connection.class.visitor_for(self)
+ connection.visitor = @visitor
+
+ connection
end
def current_connection_id #:nodoc:
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 bcd3abc08d..7312e34f01 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -46,9 +46,15 @@ module ActiveRecord
# "database" => "path/to/dbfile"
# )
#
+ # Or a URL:
+ #
+ # ActiveRecord::Base.establish_connection(
+ # "postgres://myuser:mypass@localhost/somedatabase"
+ # )
+ #
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
# may be returned on an error.
- def self.establish_connection(spec = nil)
+ def self.establish_connection(spec = ENV["DATABASE_URL"])
case spec
when nil
raise AdapterNotSpecified unless defined?(Rails.env)
@@ -58,6 +64,8 @@ module ActiveRecord
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
+ elsif spec.is_a?(String) && hash = connection_url_to_hash(spec)
+ establish_connection(hash)
else
raise AdapterNotSpecified, "#{spec} database is not configured"
end
@@ -81,6 +89,24 @@ module ActiveRecord
end
end
+ def self.connection_url_to_hash(url) # :nodoc:
+ config = URI.parse url
+ adapter = config.scheme
+ adapter = "postgresql" if adapter == "postgres"
+ spec = { :adapter => adapter,
+ :username => config.user,
+ :password => config.password,
+ :port => config.port,
+ :database => config.path.sub(%r{^/},""),
+ :host => config.host }
+ spec.reject!{ |_,value| !value }
+ if config.query
+ options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
+ spec.merge!(options)
+ end
+ spec
+ end
+
class << self
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
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 777ef15dfc..dc4a53034b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -1,30 +1,39 @@
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
+ # Converts an arel AST to SQL
+ def to_sql(arel)
+ if arel.respond_to?(:ast)
+ visitor.accept(arel.ast)
+ else
+ arel
+ end
+ end
+
# Returns an array of record hashes with the column names as keys and
# column values as values.
- def select_all(sql, name = nil, binds = [])
- select(sql, name, binds)
+ def select_all(arel, name = nil, binds = [])
+ select(to_sql(arel), name, binds)
end
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(sql, name = nil)
- result = select_all(sql, name)
+ def select_one(arel, name = nil)
+ result = select_all(arel, name)
result.first if result
end
# Returns a single value from a record
- def select_value(sql, name = nil)
- if result = select_one(sql, name)
+ def select_value(arel, name = nil)
+ if result = select_one(arel, name)
result.values.first
end
end
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- def select_values(sql, name = nil)
- result = select_rows(sql, name)
+ def select_values(arel, name = nil)
+ result = select_rows(to_sql(arel), name)
result.map { |v| v[0] }
end
@@ -74,20 +83,20 @@ module ActiveRecord
#
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
value = exec_insert(sql, name, binds)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
- def update(sql, name = nil, binds = [])
- exec_update(sql, name, binds)
+ def update(arel, name = nil, binds = [])
+ exec_update(to_sql(arel), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
- def delete(sql, name = nil, binds = [])
- exec_delete(sql, name, binds)
+ def delete(arel, name = nil, binds = [])
+ exec_delete(to_sql(arel), name, binds)
end
# Checks whether there is currently no transaction active. This is done
@@ -297,6 +306,16 @@ module ActiveRecord
end
end
+ # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
+ # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
+ # an UPDATE statement, so in the mysql adapters we redefine this to do that.
+ def join_to_update(update, select) #:nodoc:
+ subselect = select.clone
+ subselect.projections = [update.key]
+
+ update.where update.key.in(subselect)
+ 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 093c30aa42..27ff13ad89 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -55,9 +55,10 @@ module ActiveRecord
@query_cache.clear
end
- def select_all(sql, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
- cache_sql(sql, binds) { super }
+ sql = to_sql(arel)
+ cache_sql(sql, binds) { super(sql, name, binds) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 65024d76f8..443e61b527 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -2,21 +2,33 @@ require 'date'
require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/benchmark'
-
-# TODO: Autoload these files
-require 'active_record/connection_adapters/column'
-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'
-require 'active_record/connection_adapters/abstract/database_limits'
-require 'active_record/result'
+require 'active_support/deprecation'
module ActiveRecord
module ConnectionAdapters # :nodoc:
+ extend ActiveSupport::Autoload
+
+ autoload :Column
+
+ autoload_under 'abstract' do
+ autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
+
+ autoload :SchemaStatements
+ autoload :DatabaseStatements
+ autoload :DatabaseLimits
+ autoload :Quoting
+
+ autoload :ConnectionPool
+ autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
+ autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
+ autoload :ConnectionSpecification
+
+ autoload :QueryCache
+ end
+
# Active Record supports multiple database systems. AbstractAdapter and
# related classes form the abstraction layer which makes this possible.
# An AbstractAdapter represents a connection to a database, and provides an
@@ -38,12 +50,34 @@ module ActiveRecord
define_callbacks :checkout, :checkin
+ attr_accessor :visitor
+
def initialize(connection, logger = nil) #:nodoc:
@active = nil
@connection, @logger = connection, logger
@query_cache_enabled = false
@query_cache = Hash.new { |h,sql| h[sql] = {} }
+ @open_transactions = 0
@instrumenter = ActiveSupport::Notifications.instrumenter
+ @visitor = nil
+ end
+
+ # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface
+ def self.visitor_for(pool) # :nodoc:
+ adapter = pool.spec.config[:adapter]
+
+ if Arel::Visitors::VISITORS[adapter]
+ ActiveSupport::Deprecation.warn(
+ "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \
+ "should define a visitor_for method which returns the appropriate visitor for " \
+ "the database. For example, MysqlAdapter.visitor_for(pool) returns " \
+ "Arel::Visitors::MySQL.new(pool)."
+ )
+
+ Arel::Visitors::VISITORS[adapter].new(pool)
+ else
+ Arel::Visitors::ToSql.new(pool)
+ end
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -177,12 +211,9 @@ module ActiveRecord
@connection
end
- def open_transactions
- @open_transactions ||= 0
- end
+ attr_reader :open_transactions
def increment_open_transactions
- @open_transactions ||= 0
@open_transactions += 1
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
new file mode 100644
index 0000000000..4b7c74e0b8
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -0,0 +1,611 @@
+require 'active_support/core_ext/object/blank'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AbstractMysqlAdapter < AbstractAdapter
+ class Column < ConnectionAdapters::Column # :nodoc:
+ def extract_default(default)
+ if sql_type =~ /blob/i || type == :text
+ if default.blank?
+ return null ? nil : ''
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ def has_default?
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
+ # Must return the relevant concrete adapter
+ def adapter
+ raise NotImplementedError
+ end
+
+ private
+
+ def simplified_type(field_type)
+ return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
+
+ case field_type
+ when /enum/i, /set/i then :string
+ when /year/i then :integer
+ when /bit/i then :binary
+ else
+ super
+ end
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ when /^bigint/i; 8
+ when /^int/i; 4
+ when /^mediumint/i; 3
+ when /^smallint/i; 2
+ when /^tinyint/i; 1
+ else
+ super
+ end
+ end
+
+ # MySQL misreports NOT NULL column default when none is given.
+ # We can't detect this for columns which may have a legitimate ''
+ # default (string) but we can for others (integer, datetime, boolean,
+ # and the rest).
+ #
+ # Test whether the column has default '', is not null, and is not
+ # a type allowing default ''.
+ def missing_default_forged_as_empty_string?(default)
+ type != :string && !null && default == ''
+ end
+ end
+
+ ##
+ # :singleton-method:
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
+ # as boolean. If you wish to disable this emulation (which was the default
+ # behavior in versions 0.13.1 and earlier) you can add the following line
+ # to your application.rb file:
+ #
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
+ class_attribute :emulate_booleans
+ self.emulate_booleans = true
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ NATIVE_DATABASE_TYPES = {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int", :limit => 4 },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+
+ # FIXME: Make the first parameter more similar for the two adapters
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ end
+
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::MySQL.new(pool)
+ end
+
+ def adapter_name #:nodoc:
+ self.class::ADAPTER_NAME
+ end
+
+ # Returns true, since this connection adapter supports migrations.
+ def supports_migrations?
+ true
+ end
+
+ def supports_primary_key?
+ true
+ end
+
+ # Returns true, since this connection adapter supports savepoints.
+ def supports_savepoints?
+ true
+ end
+
+ def supports_bulk_alter? #:nodoc:
+ true
+ end
+
+ def native_database_types
+ NATIVE_DATABASE_TYPES
+ end
+
+ # HELPER METHODS ===========================================
+
+ # The two drivers have slightly different ways of yielding hashes of results, so
+ # this method must be implemented to provide a uniform interface.
+ def each_hash(result) # :nodoc:
+ raise NotImplementedError
+ end
+
+ # Overridden by the adapters to instantiate their specific Column type.
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
+ end
+
+ # Must return the Mysql error number from the exception, if the exception has an
+ # error number.
+ def error_number(exception) # :nodoc:
+ raise NotImplementedError
+ end
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ if name == :skip_logging
+ @connection.query(sql)
+ else
+ log(sql, name) { @connection.query(sql) }
+ end
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ # MysqlAdapter has to free a result after using it, so we use this method to write
+ # stuff in a abstract way without concerning ourselves about whether it needs to be
+ # explicitly freed or not.
+ def execute_and_free(sql, name = nil) #:nodoc:
+ yield execute(sql, name)
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction #:nodoc:
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction #:nodoc:
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
+ # these, we must use a subquery. However, MySQL is too stupid to create a
+ # temporary table for this automatically, so we have to give it some prompting
+ # in the form of a subsubquery. Ugh!
+ def join_to_update(update, select) #:nodoc:
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.clone
+ subsubselect.projections = [update.key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(update.key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+
+ update.where update.key.in(subselect)
+ else
+ update.table select.source
+ update.wheres = select.constraints
+ end
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump #:nodoc:
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).map do |table|
+ table.delete('Table_type')
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
+ exec_without_stmt(sql).first['Create Table'] + ";\n\n"
+ end.join("")
+ end
+
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
+ def recreate_database(name, options = {})
+ drop_database(name)
+ create_database(name, options)
+ end
+
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ def create_database(name, options = {})
+ if options[:collation]
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ else
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ end
+ end
+
+ # Drops a MySQL database.
+ #
+ # Example:
+ # drop_database('sebastian_development')
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil, database = nil) #:nodoc:
+ sql = ["SHOW TABLES", database].compact.join(' IN ')
+
+ execute_and_free(sql, 'SCHEMA') do |result|
+ result.collect { |field| field.first }
+ end
+ end
+
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
+ end
+
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil) #:nodoc:
+ indexes = []
+ current_index = nil
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
+ each_hash(result) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
+ current_index = row[:Key_name]
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+ end
+
+ indexes.last.columns << row[:Column_name]
+ indexes.last.lengths << row[:Sub_part]
+ end
+ end
+
+ indexes
+ end
+
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
+ def columns(table_name, name = nil)#:nodoc:
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ execute_and_free(sql, 'SCHEMA') do |result|
+ each_hash(result).map do |field|
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ end
+ end
+ end
+
+ def create_table(table_name, options = {}) #:nodoc:
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def bulk_change_table(table_name, operations) #:nodoc:
+ sqls = operations.map do |command, args|
+ table, arguments = args.shift, args
+ method = :"#{command}_sql"
+
+ if respond_to?(method)
+ send(method, table, *arguments)
+ else
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
+ end
+ end.flatten.join(", ")
+
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
+ end
+
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def add_column(table_name, column_name, type, options = {})
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
+ end
+
+ def change_column_default(table_name, column_name, default)
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ column = column_for(table_name, column_name)
+
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+
+ change_column table_name, column_name, column.sql_type, :null => null
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def add_column_position!(sql, options)
+ if options[:first]
+ sql << " FIRST"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
+ end
+ end
+
+ # SHOW VARIABLES LIKE 'name'
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ # Returns a table's primary key and belonging sequence.
+ def pk_and_sequence_for(table)
+ execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result|
+ keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] }
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+ end
+
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
+
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
+ protected
+
+ def quoted_columns_for_index(column_names, options = {})
+ length = options[:length] if options.is_a?(Hash)
+
+ case length
+ when Hash
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+ when Fixnum
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+ else
+ column_names.map {|name| quote_column_name(name) }
+ end
+ end
+
+ def translate_exception(exception, message)
+ case error_number(exception)
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
+ def add_column_sql(table_name, column_name, type, options = {})
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ add_column_position!(add_column_sql, options)
+ add_column_sql
+ end
+
+ def change_column_sql(table_name, column_name, type, options = {})
+ column = column_for(table_name, column_name)
+
+ unless options_include_default?(options)
+ options[:default] = column.default
+ end
+
+ unless options.has_key?(:null)
+ options[:null] = column.null
+ end
+
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
+ change_column_sql
+ end
+
+ def rename_column_sql(table_name, column_name, new_column_name)
+ options = {}
+
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ options[:null] = column.null
+ else
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
+ end
+
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ add_column_options!(rename_column_sql, options)
+ rename_column_sql
+ end
+
+ def remove_column_sql(table_name, *column_names)
+ columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
+ end
+ alias :remove_columns_sql :remove_column
+
+ def add_index_sql(table_name, column_name, options = {})
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
+ end
+
+ def remove_index_sql(table_name, options = {})
+ index_name = index_name_for_remove(table_name, options)
+ "DROP INDEX #{index_name}"
+ end
+
+ def add_timestamps_sql(table_name)
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
+ end
+
+ def remove_timestamps_sql(table_name)
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
+ end
+
+ private
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index d6c167ad36..8b574518e5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+require 'active_record/connection_adapters/abstract_mysql_adapter'
gem 'mysql2', '~> 0.3.6'
require 'mysql2'
@@ -20,187 +20,51 @@ module ActiveRecord
end
module ConnectionAdapters
- class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
- end
+ class Mysql2Adapter < AbstractMysqlAdapter
- class Mysql2Column < Column
- BOOL = "tinyint(1)"
- def extract_default(default)
- if sql_type =~ /blob/i || type == :text
- if default.blank?
- return null ? nil : ''
- else
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- elsif missing_default_forged_as_empty_string?(default)
- nil
- else
- super
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
+ def adapter
+ Mysql2Adapter
end
end
- def has_default?
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
- super
- end
-
- private
- def simplified_type(field_type)
- return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
-
- case field_type
- when /enum/i, /set/i then :string
- when /year/i then :integer
- when /bit/i then :binary
- else
- super
- end
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /blob|text/i
- case sql_type
- when /tiny/i
- 255
- when /medium/i
- 16777215
- when /long/i
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
- else
- super # we could return 65535 here, but we leave it undecorated by default
- end
- when /^bigint/i; 8
- when /^int/i; 4
- when /^mediumint/i; 3
- when /^smallint/i; 2
- when /^tinyint/i; 1
- else
- super
- end
- end
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
- end
-
- class Mysql2Adapter < AbstractAdapter
- cattr_accessor :emulate_booleans
- self.emulate_booleans = true
-
ADAPTER_NAME = 'Mysql2'
- PRIMARY = "PRIMARY"
-
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
- NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "int", :limit => 4 },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
- }
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
- @connection_options, @config = connection_options, config
- @quoted_column_names, @quoted_table_names = {}, {}
+ super
configure_connection
end
- def adapter_name
- ADAPTER_NAME
- end
+ # HELPER METHODS ===========================================
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations?
- true
- end
-
- def supports_primary_key?
- true
- end
-
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints?
- true
- end
-
- def native_database_types
- NATIVE_DATABASE_TYPES
- end
-
- # QUOTING ==================================================
-
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
- s = column.class.string_to_binary(value).unpack("H*")[0]
- "x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
+ def each_hash(result) # :nodoc:
+ if block_given?
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
+ yield row
+ end
else
- super
+ to_enum(:each_hash, result)
end
end
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name}`"
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
end
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ def error_number(exception)
+ exception.error_number if exception.respond_to?(:error_number)
end
+ # QUOTING ==================================================
+
def quote_string(string)
@connection.escape(string)
end
- def quoted_true
- QUOTED_TRUE
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
def substitute_at(column, index)
Arel.sql "\0"
end
- # REFERENTIAL INTEGRITY ====================================
-
- def disable_referential_integrity(&block) #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
-
- begin
- update("SET FOREIGN_KEY_CHECKS = 0")
- yield
- ensure
- update("SET FOREIGN_KEY_CHECKS = #{old}")
- end
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -213,11 +77,6 @@ module ActiveRecord
connect
end
- # this is set to true in 2.3, but we don't want it to be
- def requires_reloading?
- false
- end
-
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
def disconnect!
@@ -273,17 +132,22 @@ module ActiveRecord
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
+
+ super
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [])
+ result = execute(sql, name)
+ ActiveRecord::Result.new(result.fields, result.to_a)
+ end
+
+ alias exec_without_stmt exec_query
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil, binds = [])
+ binds = binds.dup
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -312,358 +176,35 @@ module ActiveRecord
@connection.last_id
end
- def update_sql(sql, name = nil)
- super
- @connection.affected_rows
- end
-
- def begin_db_transaction
- execute "BEGIN"
- rescue Exception
- # Transactions aren't supported
- end
-
- def commit_db_transaction
- execute "COMMIT"
- rescue Exception
- # Transactions aren't supported
- end
-
- def rollback_db_transaction
- execute "ROLLBACK"
- rescue Exception
- # Transactions aren't supported
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def structure_dump
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql).inject("") do |structure, table|
- table.delete('Table_type')
- structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
- end
- end
-
- # Drops the database specified on the +name+ attribute
- # and creates it again using the provided +options+.
- def recreate_database(name, options = {})
- drop_database(name)
- create_database(name, options)
- end
-
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
- # Charset defaults to utf8.
- #
- # Example:
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
- # create_database 'matt_development'
- # create_database 'matt_development', :charset => :big5
- def create_database(name, options = {})
- if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
- else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
- end
- end
-
- # Drops a MySQL database.
- #
- # Example:
- # drop_database('sebastian_development')
- def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
- end
-
- def current_database
- select_value 'SELECT DATABASE() as db'
- end
-
- # Returns the database character set.
- def charset
- show_variable 'character_set_database'
- end
-
- # Returns the database collation strategy.
- def collation
- show_variable 'collation_database'
- end
-
- def tables(name = nil, database = nil) #:nodoc:
- sql = ["SHOW TABLES", database].compact.join(' IN ')
- execute(sql, 'SCHEMA').collect do |field|
- field.first
- end
- end
-
- def table_exists?(name)
- return true if super
-
- name = name.to_s
- schema, table = name.split('.', 2)
-
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
-
- tables(nil, schema).include? table
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
- indexes = []
- current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) do |row|
- if current_index != row[:Key_name]
- next if row[:Key_name] == PRIMARY # skip the primary key
- current_index = row[:Key_name]
- indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
- end
-
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths << row[:Sub_part]
- end
- indexes
- end
-
- # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- columns = []
- result = execute(sql, 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) { |field|
- columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
- }
- columns
- end
-
- def create_table(table_name, options = {})
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(table_name, new_name)
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
- end
-
- def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- add_column_position!(add_column_sql, options)
- execute(add_column_sql)
- end
-
- def change_column_default(table_name, column_name, default)
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, :default => default
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- column = column_for(table_name, column_name)
-
- unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
-
- change_column table_name, column_name, column.sql_type, :null => null
- end
-
- def change_column(table_name, column_name, type, options = {})
- column = column_for(table_name, column_name)
-
- unless options_include_default?(options)
- options[:default] = column.default
- end
-
- unless options.has_key?(:null)
- options[:null] = column.null
- end
-
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- add_column_position!(change_column_sql, options)
- execute(change_column_sql)
- end
+ private
- def rename_column(table_name, column_name, new_column_name)
- options = {}
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- options[:null] = column.null
- else
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
- end
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
- add_column_options!(rename_column_sql, options)
- execute(rename_column_sql)
+ def connect
+ @connection = Mysql2::Client.new(@config)
+ configure_connection
end
- # Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
- end
+ def configure_connection
+ @connection.query_options.merge!(:as => :array)
- def add_column_position!(sql, options)
- if options[:first]
- sql << " FIRST"
- elsif options[:after]
- sql << " AFTER #{quote_column_name(options[:after])}"
- end
- end
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
+ encoding = @config[:encoding]
- # SHOW VARIABLES LIKE 'name'.
- def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
- variables.first['Value'] unless variables.empty?
- end
+ # make sure we set the encoding
+ variable_assignments << "NAMES '#{encoding}'" if encoding
- # Returns a table's primary key and belonging sequence.
- def pk_and_sequence_for(table)
- keys = []
- result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) do |row|
- keys << row[:Field] if row[:Key] == "PRI"
- end
- keys.length == 1 ? [keys.first, nil] : nil
- end
-
- # Returns just a table's primary key
- def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
- end
+ # increase timeout so mysql server doesn't disconnect us
+ wait_timeout = @config[:wait_timeout]
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
- def case_sensitive_modifier(node)
- Arel::Nodes::Bin.new(node)
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
end
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- where_sql
+ def version
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
-
- protected
- def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
-
- quoted_column_names = case length
- when Hash
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
- when Fixnum
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
- else
- column_names.map {|name| quote_column_name(name) }
- end
- end
-
- def translate_exception(exception, message)
- return super unless exception.respond_to?(:error_number)
-
- case exception.error_number
- when 1062
- RecordNotUnique.new(message, exception)
- when 1452
- InvalidForeignKey.new(message, exception)
- else
- super
- end
- end
-
- private
- def connect
- @connection = Mysql2::Client.new(@config)
- configure_connection
- end
-
- def configure_connection
- @connection.query_options.merge!(:as => :array)
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
- encoding = @config[:encoding]
-
- # make sure we set the encoding
- variable_assignments << "NAMES '#{encoding}'" if encoding
-
- # increase timeout so mysql server doesn't disconnect us
- wait_timeout = @config[:wait_timeout]
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
-
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
- end
-
- # Returns an array of record hashes with the column names as keys and
- # column values as values.
- def select(sql, name = nil, binds = [])
- binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
- end
-
- def exec_query(sql, name = 'SQL', binds = [])
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
-
- log(sql, name, binds) do
- begin
- result = @connection.query(sql)
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
- end
-
- ActiveRecord::Result.new(result.fields, result.to_a)
- end
- end
-
- def supports_views?
- version[0] >= 5
- end
-
- def version
- @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
- end
-
- def column_for(table_name, column_name)
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
- raise "No such column: #{table_name}.#{column_name}"
- end
- column
- end
end
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 9e6cb13cca..e9bdcc2104 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -1,6 +1,5 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_support/core_ext/object/blank'
-require 'set'
+require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_support/core_ext/hash/keys'
gem 'mysql', '~> 2.8.1'
require 'mysql'
@@ -40,9 +39,29 @@ module ActiveRecord
end
module ConnectionAdapters
- class MysqlColumn < Column #:nodoc:
- class << self
- def string_to_time(value)
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
+ #
+ # Options:
+ #
+ # * <tt>:host</tt> - Defaults to "localhost".
+ # * <tt>:port</tt> - Defaults to 3306.
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
+ # * <tt>:username</tt> - Defaults to "root"
+ # * <tt>:password</tt> - Defaults to nothing.
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
+ #
+ class MysqlAdapter < AbstractMysqlAdapter
+
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
+ def self.string_to_time(value)
return super unless Mysql::Time === value
new_time(
value.year,
@@ -54,230 +73,69 @@ module ActiveRecord
value.second_part)
end
- def string_to_dummy_time(v)
+ def self.string_to_dummy_time(v)
return super unless Mysql::Time === v
new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
end
- def string_to_date(v)
+ def self.string_to_date(v)
return super unless Mysql::Time === v
new_date(v.year, v.month, v.day)
end
- end
- def extract_default(default)
- if sql_type =~ /blob/i || type == :text
- if default.blank?
- return null ? nil : ''
- else
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- elsif missing_default_forged_as_empty_string?(default)
- nil
- else
- super
+ def adapter
+ MysqlAdapter
end
end
- def has_default?
- return false if sql_type =~ /blob/i || 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)")
- return :string if field_type =~ /enum/i
- super
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /blob|text/i
- case sql_type
- when /tiny/i
- 255
- when /medium/i
- 16777215
- when /long/i
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
- else
- super # we could return 65535 here, but we leave it undecorated by default
- end
- when /^bigint/i; 8
- when /^int/i; 4
- when /^mediumint/i; 3
- when /^smallint/i; 2
- when /^tinyint/i; 1
- else
- super
- end
- end
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
- end
-
- # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
- # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
- #
- # Options:
- #
- # * <tt>:host</tt> - Defaults to "localhost".
- # * <tt>:port</tt> - Defaults to 3306.
- # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
- # * <tt>:username</tt> - Defaults to "root"
- # * <tt>:password</tt> - Defaults to nothing.
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
- # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
- # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
- # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
- #
- class MysqlAdapter < AbstractAdapter
-
- ##
- # :singleton-method:
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
- # as boolean. If you wish to disable this emulation (which was the default
- # behavior in versions 0.13.1 and earlier) you can add the following line
- # to your application.rb file:
- #
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
- cattr_accessor :emulate_booleans
- self.emulate_booleans = true
-
ADAPTER_NAME = 'MySQL'
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
- NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "int", :limit => 4 },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
- }
-
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
- @connection_options, @config = connection_options, config
- @quoted_column_names, @quoted_table_names = {}, {}
+ super
@statements = {}
@client_encoding = nil
connect
end
- def adapter_name #:nodoc:
- ADAPTER_NAME
- end
-
- def supports_bulk_alter? #:nodoc:
- true
- end
-
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
true
end
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations? #:nodoc:
- true
- end
+ # HELPER METHODS ===========================================
- # Returns true.
- def supports_primary_key? #:nodoc:
- true
+ def each_hash(result) # :nodoc:
+ if block_given?
+ result.each_hash do |row|
+ row.symbolize_keys!
+ yield row
+ end
+ else
+ to_enum(:each_hash, result)
+ end
end
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints? #:nodoc:
- true
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
end
- def native_database_types #:nodoc:
- NATIVE_DATABASE_TYPES
+ def error_number(exception) # :nodoc:
+ exception.errno if exception.respond_to?(:errno)
end
-
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
- s = column.class.string_to_binary(value).unpack("H*")[0]
- "x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
- else
- super
- end
- end
-
def type_cast(value, column)
return super unless value == true || value == false
value ? 1 : 0
end
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
def quote_string(string) #:nodoc:
@connection.quote(string)
end
- def quoted_true
- QUOTED_TRUE
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- # REFERENTIAL INTEGRITY ====================================
-
- def disable_referential_integrity #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
-
- begin
- update("SET FOREIGN_KEY_CHECKS = 0")
- yield
- ensure
- update("SET FOREIGN_KEY_CHECKS = #{old}")
- end
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -421,20 +279,11 @@ module ActiveRecord
end
end
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
- # the Result object after you're done using it.
- def execute(sql, name = nil) #:nodoc:
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
+ def execute_and_free(sql, name = nil)
+ result = execute(sql, name)
+ ret = yield result
+ result.free
+ ret
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
@@ -443,11 +292,6 @@ module ActiveRecord
end
alias :create :insert_sql
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.affected_rows
- end
-
def exec_delete(sql, name, binds)
log(sql, name, binds) do
exec_stmt(sql, name, binds) do |cols, stmt|
@@ -463,337 +307,8 @@ module ActiveRecord
# Transactions aren't supported
end
- def commit_db_transaction #:nodoc:
- execute "COMMIT"
- rescue Exception
- # Transactions aren't supported
- end
-
- def rollback_db_transaction #:nodoc:
- execute "ROLLBACK"
- rescue Exception
- # Transactions aren't supported
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def structure_dump #:nodoc:
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql).map do |table|
- table.delete('Table_type')
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
- end.join("")
- end
-
- # Drops the database specified on the +name+ attribute
- # and creates it again using the provided +options+.
- def recreate_database(name, options = {}) #:nodoc:
- drop_database(name)
- create_database(name, options)
- end
-
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
- # Charset defaults to utf8.
- #
- # Example:
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
- # create_database 'matt_development'
- # create_database 'matt_development', :charset => :big5
- def create_database(name, options = {})
- if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
- else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
- end
- end
-
- # Drops a MySQL database.
- #
- # Example:
- # drop_database 'sebastian_development'
- def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
- end
-
- def current_database
- select_value 'SELECT DATABASE() as db'
- end
-
- # Returns the database character set.
- def charset
- show_variable 'character_set_database'
- end
-
- # Returns the database collation strategy.
- def collation
- show_variable 'collation_database'
- end
-
- def tables(name = nil, database = nil) #:nodoc:
- result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
- tables = result.collect { |field| field[0] }
- result.free
- tables
- end
-
- def table_exists?(name)
- return true if super
-
- name = name.to_s
- schema, table = name.split('.', 2)
-
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
-
- tables(nil, schema).include? table
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)#:nodoc:
- indexes = []
- current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
- result.each do |row|
- if current_index != row[2]
- next if row[2] == "PRIMARY" # skip the primary key
- current_index = row[2]
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
- end
-
- indexes.last.columns << row[4]
- indexes.last.lengths << row[7]
- end
- result.free
- indexes
- end
-
- # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)#:nodoc:
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- result = execute(sql, 'SCHEMA')
- columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
- result.free
- columns
- end
-
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(table_name, new_name)
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
- end
-
- def bulk_change_table(table_name, operations) #:nodoc:
- sqls = operations.map do |command, args|
- table, arguments = args.shift, args
- method = :"#{command}_sql"
-
- if respond_to?(method)
- send(method, table, *arguments)
- else
- raise "Unknown method called : #{method}(#{arguments.inspect})"
- end
- end.flatten.join(", ")
-
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
- end
-
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
- def change_column_default(table_name, column_name, default) #:nodoc:
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, :default => default
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- column = column_for(table_name, column_name)
-
- unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
-
- change_column table_name, column_name, column.sql_type, :null => null
- end
-
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
- end
-
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
- end
-
- # Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
- end
-
- def add_column_position!(sql, options)
- if options[:first]
- sql << " FIRST"
- elsif options[:after]
- sql << " AFTER #{quote_column_name(options[:after])}"
- end
- end
-
- # SHOW VARIABLES LIKE 'name'
- def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
- variables.first['Value'] unless variables.empty?
- end
-
- # Returns a table's primary key and belonging sequence.
- def pk_and_sequence_for(table) #:nodoc:
- keys = []
- result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
- result.each_hash do |h|
- keys << h["Field"]if h["Key"] == "PRI"
- end
- result.free
- keys.length == 1 ? [keys.first, nil] : nil
- end
-
- # Returns just a table's primary key
- def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
- end
-
- def case_sensitive_modifier(node)
- Arel::Nodes::Bin.new(node)
- end
-
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- where_sql
- end
-
- protected
- def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
-
- case length
- when Hash
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
- when Fixnum
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
- else
- column_names.map {|name| quote_column_name(name) }
- end
- end
-
- def translate_exception(exception, message)
- return super unless exception.respond_to?(:errno)
-
- case exception.errno
- when 1062
- RecordNotUnique.new(message, exception)
- when 1452
- InvalidForeignKey.new(message, exception)
- else
- super
- end
- end
-
- def add_column_sql(table_name, column_name, type, options = {})
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- add_column_position!(add_column_sql, options)
- add_column_sql
- end
-
- def remove_column_sql(table_name, *column_names)
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
- end
- alias :remove_columns_sql :remove_column
-
- def change_column_sql(table_name, column_name, type, options = {})
- column = column_for(table_name, column_name)
-
- unless options_include_default?(options)
- options[:default] = column.default
- end
-
- unless options.has_key?(:null)
- options[:null] = column.null
- end
-
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- add_column_position!(change_column_sql, options)
- change_column_sql
- end
-
- def rename_column_sql(table_name, column_name, new_column_name)
- options = {}
-
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- options[:null] = column.null
- else
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
- end
-
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
- add_column_options!(rename_column_sql, options)
- rename_column_sql
- end
-
- def add_index_sql(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
- end
-
- def remove_index_sql(table_name, options = {})
- index_name = index_name_for_remove(table_name, options)
- "DROP INDEX #{index_name}"
- end
-
- def add_timestamps_sql(table_name)
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
- end
-
- def remove_timestamps_sql(table_name)
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
- end
-
private
+
def exec_stmt(sql, name, binds)
cache = {}
if binds.empty?
@@ -805,7 +320,6 @@ module ActiveRecord
stmt = cache[:stmt]
end
-
begin
stmt.execute(*binds.map { |col, val| type_cast(val, col) })
rescue Mysql::Error => e
@@ -834,59 +348,48 @@ module ActiveRecord
result
end
- def connect
- encoding = @config[:encoding]
- if encoding
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
- end
-
- if @config[:sslca] || @config[:sslkey]
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
- end
-
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
+ def connect
+ encoding = @config[:encoding]
+ if encoding
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
+ end
- @connection.real_connect(*@connection_options)
+ if @config[:sslca] || @config[:sslkey]
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
+ end
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
- configure_connection
- end
+ @connection.real_connect(*@connection_options)
- def configure_connection
- encoding = @config[:encoding]
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
- end
+ configure_connection
+ end
- def select(sql, name = nil, binds = [])
- @connection.query_with_result = true
- rows = exec_query(sql, name, binds).to_a
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
- rows
- end
+ def configure_connection
+ encoding = @config[:encoding]
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
- def supports_views?
- version[0] >= 5
- end
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+ end
- # Returns the version of the connected MySQL server.
- def version
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
- end
+ def select(sql, name = nil, binds = [])
+ @connection.query_with_result = true
+ rows = exec_query(sql, name, binds).to_a
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
+ rows
+ end
- def column_for(table_name, column_name)
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
- raise "No such column: #{table_name}.#{column_name}"
- end
- column
- end
+ # Returns the version of the connected MySQL server.
+ def version
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index df753d087c..ba4a6c7a78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -265,6 +265,10 @@ module ActiveRecord
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::PostgreSQL.new(pool)
+ end
+
# Clears the prepared statements cache.
def clear_cache!
@statements.each_value do |value|
@@ -614,9 +618,11 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def recreate_database(name) #:nodoc:
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
+ def recreate_database(name, options = {}) #:nodoc:
drop_database(name)
- create_database(name)
+ create_database(name, options)
end
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c3a7b039ff..0a0da0b5d3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,4 +1,6 @@
require 'active_record/connection_adapters/sqlite_adapter'
+
+gem 'sqlite3', '~> 1.3.4'
require 'sqlite3'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 724b2e6d9c..1996e49296 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -53,6 +53,10 @@ module ActiveRecord
@config = config
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::SQLite.new(pool)
+ end
+
def adapter_name #:nodoc:
'SQLite'
end
@@ -144,7 +148,7 @@ module ActiveRecord
end
def quote_column_name(name) #:nodoc:
- %Q("#{name}")
+ %Q("#{name.to_s.gsub('"', '""')}")
end
# Quote date/time values for use in SQL input. Includes microseconds
@@ -157,6 +161,26 @@ module ActiveRecord
end
end
+ if "<3".encoding_aware?
+ def type_cast(value, column) # :nodoc:
+ return value.to_f if BigDecimal === value
+ return super unless String === value
+ return super unless column && value
+
+ value = super
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
+ @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
+ value.encode! 'utf-8'
+ end
+ value
+ end
+ else
+ def type_cast(value, column) # :nodoc:
+ return super unless BigDecimal === value
+
+ value.to_f
+ end
+ end
# DATABASE STATEMENTS ======================================
@@ -233,15 +257,15 @@ module ActiveRecord
end
def begin_db_transaction #:nodoc:
- @connection.transaction
+ log('begin transaction',nil) { @connection.transaction }
end
def commit_db_transaction #:nodoc:
- @connection.commit
+ log('commit transaction',nil) { @connection.commit }
end
def rollback_db_transaction #:nodoc:
- @connection.rollback
+ log('rollback transaction',nil) { @connection.rollback }
end
# SCHEMA STATEMENTS ========================================
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 4d387565d9..3c7defedac 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -33,7 +33,7 @@ module ActiveRecord
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
arel_table[counter_name] => object.send(association).count
})
- connection.update stmt.to_sql
+ connection.update stmt
end
return true
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 6cfce6e573..d9ad7e4132 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -70,7 +70,7 @@ module ActiveRecord
# If the locking column has no default value set,
# start the lock version at zero. Note we can't use
- # <tt>locking_enabled?</tt> at this point as
+ # <tt>locking_enabled?</tt> at this point as
# <tt>@attributes</tt> may not have been initialized yet.
if result.key?(self.class.locking_column) && lock_optimistically
@@ -100,7 +100,7 @@ module ActiveRecord
)
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
- affected_rows = connection.update stmt.to_sql
+ affected_rows = connection.update stmt
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c459846264..7166f1b82a 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/class/attribute_accessors"
require "active_support/core_ext/array/wrap"
module ActiveRecord
@@ -330,6 +332,10 @@ module ActiveRecord
(delegate || superclass.delegate).send(name, *args, &block)
end
+ def self.migrate(direction)
+ new.migrate direction
+ end
+
cattr_accessor :verbose
attr_accessor :name, :version
@@ -557,7 +563,7 @@ module ActiveRecord
def get_all_versions
table = Arel::Table.new(schema_migrations_table_name)
- Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
+ Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
end
def current_version
@@ -714,11 +720,11 @@ module ActiveRecord
if down?
@migrated_versions.delete(version)
stmt = table.where(table["version"].eq(version.to_s)).compile_delete
- Base.connection.delete stmt.to_sql
+ Base.connection.delete stmt
else
@migrated_versions.push(version).sort!
stmt = table.compile_insert table["version"] => version.to_s
- Base.connection.insert stmt.to_sql
+ Base.connection.insert stmt
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 53617059d0..2dbebfcaf8 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -383,7 +383,7 @@ module ActiveRecord
attributes_collection = if keys.include?('id') || keys.include?(:id)
Array.wrap(attributes_collection)
else
- attributes_collection.sort_by { |i, _| i.to_i }.map { |_, attributes| attributes }
+ attributes_collection.values
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 5a5351b517..fdf17c003c 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -111,7 +111,7 @@ module ActiveRecord
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
klass.send(:define_method, callback_meth) do |&block|
- observer.send(callback, self, &block)
+ observer.update(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index d9cd7987b0..2dac9ea0fb 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -287,7 +287,7 @@ module ActiveRecord
private
- # A hook to be overriden by association modules.
+ # A hook to be overridden by association modules.
def destroy_associations
end
@@ -304,7 +304,7 @@ module ActiveRecord
return 0 if attributes_with_values.empty?
klass = self.class
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
- klass.connection.update stmt.to_sql
+ klass.connection.update stmt
end
# Creates a record with values matching those of the instance attributes
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index e485901440..10c0dc6f2a 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -61,6 +61,12 @@ module ActiveRecord
status, headers, body = @app.call(env)
[status, headers, BodyProxy.new(old, body)]
+ rescue Exception => e
+ ActiveRecord::Base.connection.clear_query_cache
+ unless old
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+ raise e
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 44e5520230..13c41350fb 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -94,7 +94,7 @@ db_namespace = namespace :db do
"IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
ActiveRecord::Base.establish_connection(config.merge(
'database' => nil, 'username' => 'root', 'password' => root_password))
- ActiveRecord::Base.connection.create_database(config['database'], creation_options)
+ ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
ActiveRecord::Base.connection.execute grant_statement
ActiveRecord::Base.establish_connection(config)
else
@@ -115,7 +115,8 @@ db_namespace = namespace :db do
end
end
else
- $stderr.puts "#{config['database']} already exists"
+ # Bug with 1.9.2 Calling return within begin still executes else
+ $stderr.puts "#{config['database']} already exists" unless config['adapter'] =~ /sqlite/
end
end
@@ -340,7 +341,8 @@ db_namespace = namespace :db do
desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => :load_config do
require 'active_record/schema_dumper'
- File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file|
+ filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
+ File.open(filename, "w:utf-8") do |file|
ActiveRecord::Base.establish_connection(Rails.env)
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 2d0861d5c9..15fd1a58c8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -68,7 +68,7 @@ module ActiveRecord
end
conn.insert(
- im.to_sql,
+ im,
'SQL',
primary_key,
primary_key_value,
@@ -108,10 +108,10 @@ module ActiveRecord
if default_scoped.equal?(self)
@records = if @readonly_value.nil? && !@klass.locking_enabled?
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
else
IdentityMap.without do
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
end
end
@@ -216,19 +216,21 @@ module ActiveRecord
if conditions || options.present?
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
else
- limit = nil
- order = []
- # Apply limit and order only if they're both present
- if @limit_value.present? == @order_values.present?
- limit = arel.limit
- order = arel.orders
- end
+ stmt = Arel::UpdateManager.new(arel.engine)
- stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
- stmt.take limit if limit
- stmt.order(*order)
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.table(table)
stmt.key = table[primary_key]
- @klass.connection.update stmt.to_sql, 'SQL', bind_values
+
+ if joins_values.any?
+ @klass.connection.join_to_update(stmt, arel)
+ else
+ stmt.take(arel.limit)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
+ end
+
+ @klass.connection.update stmt, 'SQL', bind_values
end
end
@@ -345,8 +347,7 @@ module ActiveRecord
where(conditions).delete_all
else
statement = arel.compile_delete
- affected = @klass.connection.delete(
- statement.to_sql, 'SQL', bind_values)
+ affected = @klass.connection.delete(statement, 'SQL', bind_values)
reset
affected
@@ -392,7 +393,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= arel.to_sql
+ @to_sql ||= klass.connection.to_sql(arel)
end
def where_values_hash
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 46ab67d1cf..ec1176e3dd 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -66,11 +66,14 @@ module ActiveRecord
records = relation.where(table[primary_key].gteq(start)).all
while records.any?
+ records_size = records.size
+ primary_key_offset = records.last.id
+
yield records
- break if records.size < batch_size
+ break if records_size < batch_size
- if primary_key_offset = records.last.id
+ if primary_key_offset
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
else
raise "Primary key not included in the custom select clause"
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 0ac821b2d7..af86771d2d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -223,7 +223,7 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
+ type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -250,6 +250,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
+ select_values += @select_values unless @having_values.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
"#{field} AS #{aliaz}"
@@ -258,7 +259,7 @@ module ActiveRecord
relation = except(:group).group(group.join(','))
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation.to_sql)
+ calculated_data = @klass.connection.select_all(relation)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 422d89060b..73368aed18 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -194,7 +194,7 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- connection.select_value(relation.to_sql) ? true : false
+ connection.select_value(relation, "#{name} Exists") ? true : false
end
protected
@@ -202,7 +202,7 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
+ rows = connection.select_all(relation, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
@@ -243,7 +243,7 @@ module ActiveRecord
end
def construct_limited_ids_condition(relation)
- orders = relation.order_values
+ orders = relation.order_values.map { |val| val.presence }.compact
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
relation = relation.dup
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 2814771002..7e8ddd1b5d 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -19,7 +19,7 @@ module ActiveRecord
case value
when ActiveRecord::Relation
- value.select_values = [value.klass.arel_table['id']] if value.select_values.empty?
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
attribute.in(value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
values = value.to_a.map { |x|
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index d17861f407..7eda9ad8e8 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -96,11 +96,11 @@ module ActiveRecord
relation
end
- def having(*args)
- return self if args.blank?
+ def having(opts, *rest)
+ return self if opts.blank?
relation = clone
- relation.having_values += build_where(*args)
+ relation.having_values += build_where(opts, rest)
relation
end
@@ -254,12 +254,12 @@ module ActiveRecord
association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || []
- join_nodes = buckets['join_node'] || []
+ join_nodes = (buckets['join_node'] || []).uniq
string_joins = (buckets['string_join'] || []).map { |x|
x.strip
}.uniq
- join_list = custom_join_ast(manager, string_joins)
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -267,10 +267,6 @@ module ActiveRecord
join_list
)
- join_nodes.each do |join|
- join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
- end
-
join_dependency.graft(*stashed_association_joins)
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
@@ -280,7 +276,6 @@ module ActiveRecord
association.join_to(manager)
end
- manager.join_sources.concat join_nodes.uniq
manager.join_sources.concat join_list
manager
@@ -311,6 +306,7 @@ module ActiveRecord
o.reverse
when String, Symbol
o.to_s.split(',').collect do |s|
+ s.strip!
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
else
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 19585f6214..6fe305f843 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -40,6 +40,10 @@ module ActiveRecord
def header(stream)
define_params = @version ? ":version => #{@version}" : ""
+ if stream.respond_to?(:external_encoding)
+ stream.puts "# encoding: #{stream.external_encoding.name}"
+ end
+
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index e3185a9c5a..5ad40d8cd9 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -10,46 +10,8 @@ module ActiveRecord #:nodoc:
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
options[:except] |= Array.wrap(self.class.inheritance_column)
- hash = super(options)
-
- serializable_add_includes(options) do |association, records, opts|
- hash[association] = records.is_a?(Enumerable) ?
- records.map { |r| r.serializable_hash(opts) } :
- records.serializable_hash(opts)
- end
-
- hash
+ super(options)
end
-
- private
- # Add associations specified via the <tt>:include</tt> option.
- #
- # Expects a block that takes as arguments:
- # +association+ - name of the association
- # +records+ - the association record(s) to be serialized
- # +opts+ - options for the association records
- def serializable_add_includes(options = {})
- return unless include_associations = options.delete(:include)
-
- include_has_options = include_associations.is_a?(Hash)
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
-
- associations.each do |association|
- records = case self.class.reflect_on_association(association).macro
- when :has_many, :has_and_belongs_to_many
- send(association).to_a
- when :has_one, :belongs_to
- send(association)
- end
-
- if records
- association_options = include_has_options ? include_associations[association] : {}
- yield(association, records, association_options)
- end
- end
-
- options[:include] = include_associations
- end
end
end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index f8e6cf958c..cbfa1ad609 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -182,48 +182,6 @@ module ActiveRecord #:nodoc:
options[:except] |= Array.wrap(@serializable.class.inheritance_column)
end
- def add_extra_behavior
- add_includes
- end
-
- def add_includes
- procs = options.delete(:procs)
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
- add_associations(association, records, opts)
- end
- options[:procs] = procs
- end
-
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
- def add_associations(association, records, opts)
- association_name = association.to_s.singularize
- merged_options = options.merge(opts).merge!(:root => association_name, :skip_instruct => true)
-
- if records.is_a?(Enumerable)
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : {:type => "array"}
-
- if records.empty?
- @builder.tag!(tag, type)
- else
- @builder.tag!(tag, type) do
- records.each do |record|
- if options[:skip_types]
- record_type = {}
- else
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = {:type => record_class}
- end
-
- record.to_xml merged_options.merge(record_type)
- end
- end
- end
- elsif record = @serializable.send(association)
- record.to_xml(merged_options)
- end
- end
-
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 929559c3ba..76c37cc367 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -183,11 +183,6 @@ module ActiveRecord
##
# :singleton-method:
- # Use the ActiveRecord::Base.connection by default.
- cattr_accessor :connection
-
- ##
- # :singleton-method:
# The table name defaults to 'sessions'.
cattr_accessor :table_name
@@table_name = 'sessions'
@@ -206,10 +201,19 @@ module ActiveRecord
class << self
alias :data_column_name :data_column
+
+ # Use the ActiveRecord::Base.connection by default.
+ attr_writer :connection
+
+ # Use the ActiveRecord::Base.connection_pool by default.
+ attr_writer :connection_pool
- remove_method :connection
def connection
- @@connection ||= ActiveRecord::Base.connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def connection_pool
+ @connection_pool ||= ActiveRecord::Base.connection_pool
end
# Look up a session by id and unmarshal its data if found.
@@ -219,6 +223,8 @@ module ActiveRecord
end
end
end
+
+ delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
attr_reader :session_id, :new_record
alias :new_record? :new_record
@@ -297,8 +303,12 @@ module ActiveRecord
private
def get_session(env, sid)
Base.silence do
- sid ||= generate_sid
- session = find_session(sid)
+ unless sid and session = @@session_class.find_by_session_id(sid)
+ # If the sid was nil or if there is no pre-existing session under the sid,
+ # force the generation of a new sid and associate a new session associated with the new sid
+ sid = generate_sid
+ session = @@session_class.new(:session_id => sid, :data => {})
+ end
env[SESSION_RECORD_KEY] = session
[sid, session.data]
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 1511c71ffc..6b8c52861e 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -37,6 +37,11 @@ module ActiveRecord
self.record_timestamps = true
end
+ def initialize_dup(other)
+ clear_timestamp_attributes
+ super
+ end
+
private
def create #:nodoc:
@@ -95,6 +100,13 @@ module ActiveRecord
def current_time_from_proper_timezone #:nodoc:
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
+
+ # Clear attributes and changed_attributes
+ def clear_timestamp_attributes
+ all_timestamp_attributes_in_model.each do |attribute_name|
+ self[attribute_name] = nil
+ changed_attributes.delete(attribute_name)
+ end
+ end
end
end
-
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 5df85304a2..7af0352a31 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -17,15 +17,7 @@ module ActiveRecord
# validates_associated :pages, :library
# end
#
- # Warning: If, after the above definition, you then wrote:
- #
- # class Page < ActiveRecord::Base
- # belongs_to :book
- #
- # validates_associated :book
- # end
- #
- # this would specify a circular dependency and cause infinite recursion.
+ # WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion.
#
# NOTE: This validation will not fail if the association hasn't been assigned. If you want to
# ensure that the association is both present and guaranteed to be valid, you also need to
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 07de544868..94497e37c7 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -81,12 +81,6 @@ class AdapterTest < ActiveRecord::TestCase
end
end
- if current_adapter?(:PostgreSQLAdapter)
- def test_encoding
- assert_not_nil @connection.encoding
- end
- end
-
def test_table_alias
def @connection.test_table_alias_length() 10; end
class << @connection
@@ -146,4 +140,31 @@ class AdapterTest < ActiveRecord::TestCase
end
end
end
+
+ def test_disable_referential_integrity
+ assert_nothing_raised do
+ @connection.disable_referential_integrity do
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
+ # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block
+ # and will fail (at least on Oracle)
+ @connection.execute "DELETE FROM fk_test_has_fk"
+ end
+ end
+ end
+
+ def test_deprecated_visitor_for
+ visitor_klass = Class.new(Arel::Visitors::ToSql)
+ Arel::Visitors::VISITORS['fuuu'] = visitor_klass
+ pool = stub(:spec => stub(:config => { :adapter => 'fuuu' }))
+ visitor = assert_deprecated {
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.visitor_for(pool)
+ }
+ assert visitor.is_a?(visitor_klass)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 509baacaef..94fc3564df 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
class ActiveSchemaTest < ActiveRecord::TestCase
def setup
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
alias_method :execute_without_stub, :execute
remove_method :execute
def execute(sql, name = nil) return sql end
@@ -10,7 +10,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
def teardown
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
remove_method :execute
alias_method :execute, :execute_without_stub
end
@@ -99,7 +99,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
private
def with_real_execute
#we need to actually modify some data, so we make execute point to the original method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
alias_method :execute_with_stub, :execute
remove_method :execute
alias_method :execute, :execute_without_stub
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
yield
ensure
#before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
remove_method :execute
alias_method :execute, :execute_with_stub
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index eee771ecff..2a89430da9 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -13,6 +13,16 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_connect_with_url
+ run_without_connection do |orig|
+ ar_config = ARTest.connection_config['arunit']
+ url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}"
+ klass = Class.new(ActiveRecord::Base)
+ klass.establish_connection(url)
+ assert_equal ar_config['database'], klass.connection.current_database
+ end
+ end
+
def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
new file mode 100644
index 0000000000..21b97b3b39
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -0,0 +1,14 @@
+require "cases/helper"
+
+module ActiveRecord
+ class PostgresqlConnectionTest < ActiveRecord::TestCase
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_encoding
+ assert_not_nil @connection.encoding
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 0d9db92447..e0152e7ccf 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -67,7 +67,7 @@ module ActiveRecord
def test_type_cast_bigdecimal
bd = BigDecimal.new '10.0'
- assert_equal bd.to_s('F'), @conn.type_cast(bd, nil)
+ assert_equal bd.to_f, @conn.type_cast(bd, nil)
end
def test_type_cast_unknown
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 6ff04e3eb3..2b598220ee 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require "cases/helper"
+require 'models/owner'
module ActiveRecord
module ConnectionAdapters
@@ -19,6 +20,21 @@ module ActiveRecord
eosql
end
+ def test_column_types
+ return skip('only test encoding on 1.9') unless "<3".encoding_aware?
+
+ owner = Owner.create!(:name => "hello".encode('ascii-8bit'))
+ owner.reload
+ select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', '
+ result = Owner.connection.exec_query <<-esql
+ SELECT #{select}
+ FROM #{Owner.table_name}
+ WHERE #{Owner.primary_key} = #{owner.id}
+ esql
+
+ assert(!result.rows.first.include?("blob"), "should not store blobs")
+ end
+
def test_exec_insert
column = @conn.columns('items').find { |col| col.name == 'number' }
vals = [[column, 10]]
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 4190f6d76d..818902beb5 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -681,4 +681,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
firm = client.create_firm! :name => "baa"
assert_equal firm.id, client.client_of
end
+
+ def test_self_referential_belongs_to_with_counter_cache_assigning_nil
+ comment = Comment.create! :post => posts(:thinking), :body => "fuu"
+ comment.parent = nil
+ comment.save!
+
+ assert_equal nil, comment.reload.parent
+ assert_equal 0, comments(:greetings).reload.children_count
+ end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index f1d061133c..c6e451fc57 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1048,4 +1048,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_not_equal 0, post.comments.to_a.count
end
end
+
+ def test_join_eager_with_empty_order_should_generate_valid_sql
+ assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first
+ end
+ end
+
+ def test_join_eager_with_nil_order_should_generate_valid_sql
+ assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 24830a661a..490fc5177e 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -39,7 +39,9 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
- david = Marshal.load(Marshal.dump(david))
+ marshalled = Marshal.dump(david)
+ david = Marshal.load(marshalled)
+
assert_equal projects(:action_controller), david.projects.find_most_recent
end
@@ -47,7 +49,9 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
- david = Marshal.load(Marshal.dump(david))
+ marshalled = Marshal.dump(david)
+ david = Marshal.load(marshalled)
+
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
end
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 0b1ba31ac2..b703c96ec1 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -813,4 +813,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !c.save
end
end
+
+ def test_preloading_empty_through_association_via_joins
+ person = Person.create!(:first_name => "Gaga")
+ person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').includes(:posts).to_a.first
+
+ assert person.posts.loaded?, 'person.posts should be loaded'
+ assert_equal [], person.posts
+ end
+
+ def test_explicitly_joining_join_table
+ assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
+ end
end
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index dd450a2a8e..80c6e41169 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -247,7 +247,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
assert_includes_and_joins_equal(
- Category.where('comments.id' => comments(:more_greetings).id).order('comments.id'),
+ Category.where('comments.id' => comments(:more_greetings).id).order('categories.id'),
[categories(:general), categories(:technology)], :post_comments
)
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 49d82ba2df..ffe2993e0f 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -203,6 +203,11 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal david.projects, david.projects.reload.reload
end
end
+
+ def test_proxy_association_accessor
+ david = developers(:david)
+ assert_equal david.association(:projects), david.projects.proxy_association
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index b0896fb236..dbf5a1ba76 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -113,6 +113,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# by inspecting it.
def test_allocated_object_can_be_inspected
topic = Topic.allocate
+ topic.instance_eval { @attributes = nil }
assert_nothing_raised { topic.inspect }
assert topic.inspect, "#<Topic not initialized>"
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 9feb7b010f..bee183cc67 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -21,6 +21,7 @@ require 'models/parrot'
require 'models/person'
require 'models/edge'
require 'models/joke'
+require 'models/bulb'
require 'rexml/document'
require 'active_support/core_ext/exception'
@@ -66,6 +67,23 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
+ def test_column_names_are_escaped
+ conn = ActiveRecord::Base.connection
+ classname = conn.class.name[/[^:]*$/]
+ badchar = {
+ 'SQLite3Adapter' => '"',
+ 'MysqlAdapter' => '`',
+ 'Mysql2Adapter' => '`',
+ 'PostgreSQLAdapter' => '"',
+ 'OracleAdapter' => '"',
+ }.fetch(classname) {
+ raise "need a bad char for #{classname}"
+ }
+
+ quoted = conn.quote_column_name "foo#{badchar}bar"
+ assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
+ end
+
def test_columns_should_obey_set_primary_key
pk = Subscriber.columns.find { |x| x.name == 'nick' }
assert pk.primary, 'nick should be primary key'
@@ -143,25 +161,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_use_table_engine_for_quoting_where
- relation = Topic.where(Topic.arel_table[:id].eq(1))
- engine = relation.table.engine
-
- fakepool = Class.new(Struct.new(:spec)) {
- def with_connection; yield self; end
- def connection_pool; self; end
- def table_exists?(name); false; end
- def quote_table_name(*args); raise "lol quote_table_name"; end
- }
-
- relation.table.engine = fakepool.new(engine.connection_pool.spec)
-
- error = assert_raises(RuntimeError) { relation.to_a }
- assert_match('lol', error.message)
- ensure
- relation.table.engine = engine
- end
-
def test_preserving_time_objects
assert_kind_of(
Time, Topic.find(1).bonus_time,
@@ -260,6 +259,18 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_create_after_initialize_without_block
+ cb = CustomBulb.create(:name => 'Dude')
+ assert_equal('Dude', cb.name)
+ assert_equal(true, cb.frickinawesome)
+ end
+
+ def test_create_after_initialize_with_block
+ cb = CustomBulb.create {|c| c.name = 'Dude' }
+ assert_equal('Dude', cb.name)
+ assert_equal(true, cb.frickinawesome)
+ end
+
def test_load
topics = Topic.find(:all, :order => 'id')
assert_equal(4, topics.size)
@@ -488,11 +499,11 @@ class BasicsTest < ActiveRecord::TestCase
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
-
+
def test_comparison
topic_1 = Topic.create!
topic_2 = Topic.create!
-
+
assert_equal [topic_2, topic_1].sort, [topic_1, topic_2]
end
@@ -540,7 +551,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_empty_month
@@ -549,7 +560,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_empty_day
@@ -558,7 +569,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_empty_day_and_year
@@ -567,7 +578,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_empty_day_and_month
@@ -576,7 +587,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_empty_year_and_month
@@ -585,7 +596,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date
+ assert_nil topic.last_read
end
def test_multiparameter_attributes_on_date_with_all_empty
@@ -678,12 +689,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal 1, topic.written_on.year
- assert_equal 1, topic.written_on.month
- assert_equal 1, topic.written_on.day
- assert_equal 0, topic.written_on.hour
- assert_equal 12, topic.written_on.min
- assert_equal 2, topic.written_on.sec
+ assert_nil topic.written_on
end
def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
@@ -693,12 +699,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal 1, topic.written_on.year
- assert_equal 1, topic.written_on.month
- assert_equal 1, topic.written_on.day
- assert_equal 16, topic.written_on.hour
- assert_equal 24, topic.written_on.min
- assert_equal 0, topic.written_on.sec
+ assert_nil topic.written_on
end
def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
attributes = {
@@ -707,12 +708,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal 1, topic.written_on.year
- assert_equal 1, topic.written_on.month
- assert_equal 1, topic.written_on.day
- assert_equal 16, topic.written_on.hour
- assert_equal 12, topic.written_on.min
- assert_equal 02, topic.written_on.sec
+ assert_nil topic.written_on
end
def test_multiparameter_attributes_on_time_with_utc
@@ -1113,6 +1109,17 @@ class BasicsTest < ActiveRecord::TestCase
self.table_name = 'numeric_data'
end
+ def test_big_decimal_conditions
+ m = NumericData.new(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3
+ )
+ assert m.save
+ assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
+ end
+
def test_numeric_fields
m = NumericData.new(
:bank_balance => 1586.43,
@@ -1618,6 +1625,10 @@ class BasicsTest < ActiveRecord::TestCase
assert !LooseDescendant.abstract_class?
end
+ def test_abstract_class_table_name
+ assert_nil AbstractCompany.table_name
+ end
+
def test_base_class
assert_equal LoosePerson, LoosePerson.base_class
assert_equal LooseDescendant, LooseDescendant.base_class
@@ -1818,11 +1829,29 @@ class BasicsTest < ActiveRecord::TestCase
def test_marshal_round_trip
expected = posts(:welcome)
- actual = Marshal.load(Marshal.dump(expected))
+ marshalled = Marshal.dump(expected)
+ actual = Marshal.load(marshalled)
assert_equal expected.attributes, actual.attributes
end
+ def test_marshal_new_record_round_trip
+ marshalled = Marshal.dump(Post.new)
+ post = Marshal.load(marshalled)
+
+ assert post.new_record?, "should be a new record"
+ end
+
+ def test_marshalling_with_associations
+ post = Post.new
+ post.comments.build
+
+ marshalled = Marshal.dump(post)
+ post = Marshal.load(marshalled)
+
+ assert_equal 1, post.comments.length
+ end
+
def test_attribute_names
assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
Company.attribute_names
@@ -1835,4 +1864,29 @@ class BasicsTest < ActiveRecord::TestCase
def test_attribtue_names_on_abstract_class
assert_equal [], AbstractCompany.attribute_names
end
+
+ def test_cache_key_for_existing_record_is_not_timezone_dependent
+ ActiveRecord::Base.time_zone_aware_attributes = true
+
+ Time.zone = "UTC"
+ utc_key = Developer.first.cache_key
+
+ Time.zone = "EST"
+ est_key = Developer.first.cache_key
+
+ assert_equal utc_key, est_key
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ end
+
+ def test_cache_key_format_for_existing_record_with_updated_at
+ dev = Developer.first
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ end
+
+ def test_cache_key_format_for_existing_record_with_nil_updated_at
+ dev = Developer.first
+ dev.update_attribute(:updated_at, nil)
+ assert_match(/\/#{dev.id}$/, dev.cache_key)
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 50ddf6c757..a35baee4ed 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -100,4 +100,20 @@ class EachTest < ActiveRecord::TestCase
end
end
end
+
+ def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
+ not_a_post = "not a post"
+ not_a_post.stubs(:id).raises(StandardError, "not_a_post had #id called on it")
+
+ assert_nothing_raised do
+ Post.find_in_batches(:batch_size => 1) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+
+ batch.map! { not_a_post }
+ end
+ end
+
+ end
+
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 224b3f3d1f..c38814713a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -170,6 +170,13 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 60, c[2]
end
+ def test_should_group_by_summed_field_having_condition_from_select
+ c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit)
+ assert_nil c[1]
+ assert_equal 60, c[2]
+ assert_equal 53, c[9]
+ end
+
def test_should_group_by_summed_association
c = Account.sum(:credit_limit, :group => :firm)
assert_equal 50, c[companies(:first_firm)]
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index d1dddd4c2c..14884e42af 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -58,68 +58,68 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = MysqlColumn.new("title", "a", "binary(1)")
+ binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = MysqlColumn.new("title", "a", "varbinary(1)")
+ varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)")
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- MysqlColumn.new("title", "a", "blob")
+ MysqlAdapter::Column.new("title", "a", "blob")
end
assert_raise ArgumentError do
- MysqlColumn.new("title", "Hello", "text")
+ MysqlAdapter::Column.new("title", "Hello", "text")
end
- text_column = MysqlColumn.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, "text")
assert_equal nil, text_column.default
- not_null_text_column = MysqlColumn.new("title", nil, "text", false)
+ not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blog_and_test_data_types
- blob_column = MysqlColumn.new("title", nil, "blob")
+ blob_column = MysqlAdapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
- text_column = MysqlColumn.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, "text")
assert !text_column.has_default?
end
end
if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = Mysql2Column.new("title", "a", "binary(1)")
+ binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = Mysql2Column.new("title", "a", "varbinary(1)")
+ varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)")
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- Mysql2Column.new("title", "a", "blob")
+ Mysql2Adapter::Column.new("title", "a", "blob")
end
assert_raise ArgumentError do
- Mysql2Column.new("title", "Hello", "text")
+ Mysql2Adapter::Column.new("title", "Hello", "text")
end
- text_column = Mysql2Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, "text")
assert_equal nil, text_column.default
- not_null_text_column = Mysql2Column.new("title", nil, "text", false)
+ not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blog_and_test_data_types
- blob_column = Mysql2Column.new("title", nil, "blob")
+ blob_column = Mysql2Adapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
- text_column = Mysql2Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, "text")
assert !text_column.has_default?
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index a1d1177289..f554ceef35 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -25,6 +25,40 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
+ class FakeBase < ActiveRecord::Base
+ def self.establish_connection spec
+ String === spec ? super : spec
+ end
+ end
+
+ def test_url_host_no_db
+ spec = FakeBase.establish_connection 'postgres://foo?encoding=utf8'
+ assert_equal({
+ :adapter => "postgresql",
+ :database => "",
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+
+ def test_url_host_db
+ spec = FakeBase.establish_connection 'postgres://foo/bar?encoding=utf8'
+ assert_equal({
+ :adapter => "postgresql",
+ :database => "bar",
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+
+ def test_url_port
+ spec = FakeBase.establish_connection 'postgres://foo:123?encoding=utf8'
+ assert_equal({
+ :adapter => "postgresql",
+ :database => "",
+ :port => 123,
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+
def test_app_delegation
manager = ConnectionManagement.new(@app)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index f92f4e62c5..8a0f453127 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -135,6 +135,10 @@ module ActiveRecord
pool.with_connection
end
end
+
+ def test_pool_sets_connection_visitor
+ assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql)
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 4e75eafe3d..5dc5f99582 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -140,23 +140,30 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_group
- developers = Developer.find(:all, :group => "salary", :select => "salary")
+ 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_group_and_having
- developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
+ developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
assert_equal 3, developers.size
assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
+ assert developers.all? { |developer| developer.salary > 10000 }
end
def test_find_with_group_and_sanitized_having
- developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary")
+ developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary")
assert_equal 3, developers.size
assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
+ assert developers.all? { |developer| developer.salary > 10000 }
+ end
+
+ def test_find_with_group_and_sanitized_having_method
+ developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all
+ assert_equal 3, developers.size
+ assert_equal 3, developers.map(&:salary).uniq.size
+ assert developers.all? { |developer| developer.salary > 10000 }
end
def test_find_with_entire_select_statement
@@ -659,6 +666,10 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
end
+ def test_find_by_two_attributes_but_passing_only_one
+ assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
+ 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")
@@ -940,6 +951,10 @@ class FinderTest < ActiveRecord::TestCase
assert !another.persisted?
end
+ def test_find_or_initialize_from_two_attributes_but_passing_only_one
+ assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
+ end
+
def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
assert_equal 123, new_customer.balance.amount
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 842e8a0049..913f6a3340 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -587,8 +587,8 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_preserves_existing_fixture_data
- assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).created_on.utc.to_date)
- assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).updated_on.utc.to_date)
+ assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date)
+ assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date)
end
def test_generates_unique_ids
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index cd9c358799..3efc8bf559 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -164,7 +164,7 @@ class IdentityMapTest < ActiveRecord::TestCase
end
##############################################################################
- # Tests checking dirty attribute behaviour with IM #
+ # Tests checking dirty attribute behavior with IM #
##############################################################################
def test_loading_new_instance_should_not_update_dirty_attributes
@@ -238,7 +238,7 @@ class IdentityMapTest < ActiveRecord::TestCase
end
##############################################################################
- # Tests checking Identity Map behaviour with preloaded associations, joins, #
+ # Tests checking Identity Map behavior with preloaded associations, joins, #
# includes etc. #
##############################################################################
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index 2de50b224c..98cda010ae 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -24,9 +24,9 @@ class InvalidDateTest < ActiveRecord::TestCase
topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s})
# Oracle DATE columns are datetime columns and Oracle adapter returns Time value
if current_adapter?(:OracleAdapter)
- assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object")
else
- assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object")
end
end
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index afec64750e..3ae7b63dff 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -27,6 +27,19 @@ module ActiveRecord
end
end
+ class LegacyMigration < ActiveRecord::Migration
+ def self.up
+ create_table("horses") do |t|
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+
+ def self.down
+ drop_table("horses")
+ end
+ end
+
def teardown
if ActiveRecord::Base.connection.table_exists?("horses")
ActiveRecord::Base.connection.drop_table("horses")
@@ -41,17 +54,39 @@ module ActiveRecord
end
end
- def test_up
+ def test_migrate_up
migration = InvertibleMigration.new
migration.migrate(:up)
assert migration.connection.table_exists?("horses"), "horses should exist"
end
- def test_down
+ def test_migrate_down
migration = InvertibleMigration.new
migration.migrate :up
migration.migrate :down
assert !migration.connection.table_exists?("horses")
end
+
+ def test_legacy_up
+ LegacyMigration.migrate :up
+ assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ end
+
+ def test_legacy_down
+ LegacyMigration.migrate :up
+ LegacyMigration.migrate :down
+ assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ end
+
+ def test_up
+ LegacyMigration.up
+ assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ end
+
+ def test_down
+ LegacyMigration.up
+ LegacyMigration.down
+ assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ end
end
end
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 643e949087..75e5dfa49b 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -231,6 +231,18 @@ class LifecycleTest < ActiveRecord::TestCase
assert_not_nil observer.topic_ids.last
end
+ test "able to disable observers" do
+ observer = DeveloperObserver.instance # activate
+ observer.calls.clear
+
+ ActiveRecord::Base.observers.disable DeveloperObserver do
+ Developer.create! :name => 'Ancestor', :salary => 100000
+ SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
+ end
+
+ assert_equal [], observer.calls
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index c6c6079490..9e8475465e 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -63,6 +63,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
+ def test_exists_query_logging
+ Developer.exists? 1
+ wait
+ assert_equal 1, @logger.logged(:debug).size
+ assert_match(/Developer Exists/, @logger.logged(:debug).last)
+ assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
+ end
+
def test_cached_queries
ActiveRecord::Base.cache do
Developer.all
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index a0cb5dbdc5..0ab4f30363 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -14,7 +14,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_set_conditions
Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do
- assert_match '(just a test...)', Developer.scoped.arel.to_sql
+ assert_match '(just a test...)', Developer.scoped.to_sql
end
end
@@ -274,7 +274,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
Developer.send(:with_scope, :find => { :limit => 10 }) do
devs = Developer.scoped
- assert_match '(salary = 80000)', devs.arel.to_sql
+ assert_match '(salary = 80000)', devs.to_sql
assert_equal 10, devs.taken
end
end
@@ -308,7 +308,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
devs = Developer.scoped
- assert_match "(name = 'David') AND (salary = 80000)", devs.arel.to_sql
+ assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql
assert_equal(1, Developer.count)
end
Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
@@ -321,7 +321,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
devs = Developer.scoped
- assert_match "(salary = 80000) AND (name = 'David')", devs.arel.to_sql
+ assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql
assert_equal 10, devs.taken
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 2afe3c8f32..ed0240cada 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -497,14 +497,24 @@ end
class DynamicScopeTest < ActiveRecord::TestCase
fixtures :posts
+ def setup
+ @test_klass = Class.new(Post) do
+ def self.name; "Post"; end
+ end
+ end
+
def test_dynamic_scope
- assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
- assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
+ assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
+ assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
end
def test_dynamic_scope_should_create_methods_after_hitting_method_missing
- assert_blank Developer.methods.grep(/scoped_by_created_at/)
- Developer.scoped_by_created_at(nil)
- assert_present Developer.methods.grep(/scoped_by_created_at/)
+ assert_blank @test_klass.methods.grep(/scoped_by_type/)
+ @test_klass.scoped_by_type(nil)
+ assert_present @test_klass.methods.grep(/scoped_by_type/)
+ end
+
+ def test_dynamic_scope_with_less_number_of_arguments
+ assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6568eb1d18..67a9ed6cd8 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -755,6 +755,11 @@ module NestedAttributesOnACollectionAssociationTests
Interest.reflect_on_association(:man).options[:inverse_of] = :interests
end
+ def test_can_use_symbols_as_object_identifier
+ @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
+ assert_nothing_raised(NoMethodError) { @pirate.save! }
+ end
+
private
def association_setter
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 57d1441128..9cd07fa8a5 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -29,6 +29,26 @@ class PersistencesTest < ActiveRecord::TestCase
end
end
+ def test_update_all_doesnt_ignore_order
+ assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
+ test_update_with_order_succeeds = lambda do |order|
+ begin
+ Author.order(order).update_all('id = id + 1')
+ rescue ActiveRecord::ActiveRecordError
+ false
+ end
+ end
+
+ if test_update_with_order_succeeds.call('id DESC')
+ assert !test_update_with_order_succeeds.call('id ASC') # test that this wasn't a fluke and using an incorrect order results in an exception
+ else
+ # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
+ assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\Z/i) do
+ test_update_with_order_succeeds.call('id DESC')
+ end
+ end
+ end
+
def test_update_all_with_order_and_limit_updates_subset_only
author = authors(:david)
assert_nothing_raised do
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index ad17f6f83a..e3ad0cad90 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -13,6 +13,32 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.disable_query_cache!
end
+ def test_exceptional_middleware_clears_and_disables_cache_on_error
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Task.find 1
+ Task.find 1
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ raise "lol borked"
+ }
+ assert_raises(RuntimeError) { mw.call({}) }
+
+ assert_equal 0, ActiveRecord::Base.connection.query_cache.length
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+ end
+
+ def test_exceptional_middleware_leaves_enabled_cache_alone
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ raise "lol borked"
+ }
+ assert_raises(RuntimeError) { mw.call({}) }
+
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ end
+
def test_middleware_delegates
called = false
mw = ActiveRecord::QueryCache.new lambda { |env|
@@ -213,4 +239,4 @@ class QueryCacheBodyProxyTest < ActiveRecord::TestCase
assert_equal proxy.to_path, "/path"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index f2d177d834..1e2093273e 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -170,7 +170,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
Developer.where('salary = 80000').scoping do
Developer.limit(10).scoping do
devs = Developer.scoped
- assert_match '(salary = 80000)', devs.arel.to_sql
+ assert_match '(salary = 80000)', devs.to_sql
assert_equal 10, devs.taken
end
end
@@ -524,4 +524,22 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
end
+
+ def test_default_scope_is_threadsafe
+ if in_memory_db?
+ skip "in memory db can't share a db between threads"
+ end
+
+ threads = []
+ assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+
+ threads << Thread.new do
+ Thread.current[:long_default_scope] = true
+ assert_equal 1, ThreadsafeDeveloper.all.count
+ end
+ threads << Thread.new do
+ assert_equal 1, ThreadsafeDeveloper.all.count
+ end
+ threads.each(&:join)
+ end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0aaa0342b1..615551a279 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -540,6 +540,29 @@ class RelationTest < ActiveRecord::TestCase
}
end
+ def test_find_all_using_where_with_relation_and_alternate_primary_key
+ cool_first = minivans(:cool_first)
+ # switching the lines below would succeed in current rails
+ # assert_queries(2) {
+ assert_queries(1) {
+ relation = Minivan.where(:minivan_id => Minivan.where(:name => cool_first.name))
+ assert_equal [cool_first], relation.all
+ }
+ end
+
+ def test_find_all_using_where_with_relation_does_not_alter_select_values
+ david = authors(:david)
+
+ subquery = Author.where(:id => david.id)
+
+ assert_queries(1) {
+ relation = Author.where(:id => subquery)
+ assert_equal [david], relation.all
+ }
+
+ assert_equal 0, subquery.select_values.size
+ end
+
def test_find_all_using_where_with_relation_with_joins
david = authors(:david)
assert_queries(1) {
@@ -961,4 +984,46 @@ class RelationTest < ActiveRecord::TestCase
assert scope.eager_loading?
end
+
+ def test_ordering_with_extra_spaces
+ assert_equal authors(:david), Author.order('id DESC , name DESC').last
+ end
+
+ def test_update_all_with_joins
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
+ count = comments.count
+
+ assert_equal count, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ end
+
+ def test_update_all_with_joins_and_limit
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_limit_and_order
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('comments.id').limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ assert_equal posts(:welcome), comments(:more_greetings).post
+ end
+
+ def test_update_all_with_joins_and_offset
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_offset_and_order
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id')
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:more_greetings).post
+ assert_equal posts(:welcome), comments(:greetings).post
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 4adecf8e83..71ff727b7f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -1,13 +1,22 @@
require "cases/helper"
-require 'stringio'
class SchemaDumperTest < ActiveRecord::TestCase
+ def setup
+ @stream = StringIO.new
+ end
+
def standard_dump
- stream = StringIO.new
+ @stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = []
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- stream.string
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, @stream)
+ @stream.string
+ end
+
+ if "string".encoding_aware?
+ def test_magic_comment
+ assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
+ end
end
def test_schema_dump
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 677d659f39..382d659289 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -1,13 +1,8 @@
require "cases/helper"
require 'models/contact'
require 'models/topic'
-require 'models/reply'
-require 'models/company'
class SerializationTest < ActiveRecord::TestCase
-
- fixtures :topics, :companies, :accounts
-
FORMATS = [ :xml, :json ]
def setup
@@ -19,8 +14,6 @@ class SerializationTest < ActiveRecord::TestCase
:awesome => false,
:preferences => { :gem => '<strong>ruby</strong>' }
}
-
- @contact = Contact.new(@contact_attributes)
end
def test_serialized_init_with
@@ -29,134 +22,6 @@ class SerializationTest < ActiveRecord::TestCase
assert_equal 'foo', topic.content
end
- def test_to_xml
- xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
- bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
- written_on_in_current_timezone = topics(:first).written_on.xmlschema
- last_read_in_current_timezone = topics(:first).last_read.xmlschema
-
- assert_equal "topic", xml.root.name
- assert_equal "The First Topic" , xml.elements["//title"].text
- assert_equal "David" , xml.elements["//author-name"].text
- assert_match "Have a nice day", xml.elements["//content"].text
-
- assert_equal "1", xml.elements["//id"].text
- assert_equal "integer" , xml.elements["//id"].attributes['type']
-
- assert_equal "1", xml.elements["//replies-count"].text
- assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
-
- assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
-
- assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
-
- assert_equal nil, xml.elements["//parent-id"].text
- assert_equal "integer", xml.elements["//parent-id"].attributes['type']
- assert_equal "true", xml.elements["//parent-id"].attributes['nil']
-
- if current_adapter?(:SybaseAdapter)
- assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
- assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
- else
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_equal "2004-04-15", xml.elements["//last-read"].text
- assert_equal "date" , xml.elements["//last-read"].attributes['type']
- end
-
- # Oracle and DB2 don't have true boolean or time-only fields
- unless current_adapter?(:OracleAdapter, :DB2Adapter)
- assert_equal "false", xml.elements["//approved"].text
- assert_equal "boolean" , xml.elements["//approved"].attributes['type']
-
- assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
- end
- end
-
- def test_to_xml_skipping_attributes
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
- assert_equal "<topic>", xml.first(7)
- assert !xml.include?(%(<title>The First Topic</title>))
- assert xml.include?(%(<author-name>David</author-name>))
-
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
- assert !xml.include?(%(<title>The First Topic</title>))
- assert !xml.include?(%(<author-name>David</author-name>))
- end
-
- def test_to_xml_including_has_many_association
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
- assert_equal "<topic>", xml.first(7)
- assert xml.include?(%(<replies type="array"><reply>))
- assert xml.include?(%(<title>The Second Topic of the day</title>))
- end
-
- def test_array_to_xml_including_has_many_association
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
- assert xml.include?(%(<replies type="array"><reply>))
- end
-
- def test_array_to_xml_including_methods
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
- assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
- assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
- end
-
- def test_array_to_xml_including_has_one_association
- xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
- assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_array_to_xml_including_belongs_to_association
- xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_to_xml_including_belongs_to_association
- xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert !xml.include?("<firm>")
-
- xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?("<firm>")
- end
-
- def test_to_xml_including_multiple_associations
- xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<account>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_multiple_associations_with_options
- xml = companies(:first_firm).to_xml(
- :indent => 0, :skip_instruct => true,
- :include => { :clients => { :only => :name } }
- )
-
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<client><name>Summit</name></client>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_methods
- xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
- end
-
- def test_to_xml_with_block
- value = "Rockin' the block"
- xml = Company.new.to_xml(:skip_instruct => true) do |_xml|
- _xml.tag! "arbitrary-element", value
- end
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
- end
-
def test_serialize_should_be_reversible
for format in FORMATS
@serialized = Contact.new.send("to_#{format}")
@@ -184,11 +49,4 @@ class SerializationTest < ActiveRecord::TestCase
assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}"
end
end
-
- def test_serialize_should_xml_skip_instruct_for_included_records
- @contact.alternative = Contact.new(:name => 'Copa Cabana')
- @serialized = @contact.to_xml(:include => [ :alternative ])
- assert_equal @serialized.index('<?xml '), 0
- assert_nil @serialized.index('<?xml ', 1)
- end
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 756c8a32eb..88751a72f9 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -5,6 +5,9 @@ require 'models/author'
require 'models/comment'
require 'models/company_in_module'
require 'models/toy'
+require 'models/topic'
+require 'models/reply'
+require 'models/company'
class XmlSerializationTest < ActiveRecord::TestCase
def test_should_serialize_default_root
@@ -50,6 +53,23 @@ class XmlSerializationTest < ActiveRecord::TestCase
end
assert_match %r{<creator>David</creator>}, @xml
end
+
+ def test_to_xml_with_block
+ value = "Rockin' the block"
+ xml = Contact.new.to_xml(:skip_instruct => true) do |_xml|
+ _xml.tag! "arbitrary-element", value
+ end
+ assert_equal "<contact>", xml.first(9)
+ assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
+ end
+
+ def test_should_skip_instruct_for_included_records
+ @contact = Contact.new
+ @contact.alternative = Contact.new(:name => 'Copa Cabana')
+ @xml = @contact.to_xml(:include => [ :alternative ])
+ assert_equal @xml.index('<?xml '), 0
+ assert_nil @xml.index('<?xml ', 1)
+ end
end
class DefaultXmlSerializationTest < ActiveRecord::TestCase
@@ -148,7 +168,63 @@ class NilXmlSerializationTest < ActiveRecord::TestCase
end
class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :projects
+ fixtures :topics, :companies, :accounts, :authors, :posts, :projects
+
+ def test_to_xml
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
+
+ assert_equal "topic", xml.root.name
+ assert_equal "The First Topic" , xml.elements["//title"].text
+ assert_equal "David" , xml.elements["//author-name"].text
+ assert_match "Have a nice day", xml.elements["//content"].text
+
+ assert_equal "1", xml.elements["//id"].text
+ assert_equal "integer" , xml.elements["//id"].attributes['type']
+
+ assert_equal "1", xml.elements["//replies-count"].text
+ assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
+
+ assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
+ assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
+
+ assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
+
+ assert_equal nil, xml.elements["//parent-id"].text
+ assert_equal "integer", xml.elements["//parent-id"].attributes['type']
+ assert_equal "true", xml.elements["//parent-id"].attributes['nil']
+
+ if current_adapter?(:SybaseAdapter)
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
+ else
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
+ end
+
+ # Oracle and DB2 don't have true boolean or time-only fields
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
+ assert_equal "false", xml.elements["//approved"].text
+ assert_equal "boolean" , xml.elements["//approved"].attributes['type']
+
+ assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
+ assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
+ end
+ end
+
+ def test_except_option
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
+ assert_equal "<topic>", xml.first(7)
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert xml.include?(%(<author-name>David</author-name>))
+
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert !xml.include?(%(<author-name>David</author-name>))
+ end
# to_xml used to mess with the hash the user provided which
# caused the builder to be reused. This meant the document kept
@@ -184,6 +260,39 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
assert_match %r{<hello-post>}, xml
end
+ def test_including_has_many_association
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
+ assert_equal "<topic>", xml.first(7)
+ assert xml.include?(%(<replies type="array"><reply>))
+ assert xml.include?(%(<title>The Second Topic of the day</title>))
+ end
+
+ def test_including_belongs_to_association
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert !xml.include?("<firm>")
+
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?("<firm>")
+ end
+
+ def test_including_multiple_associations
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<account>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
+ def test_including_association_with_options
+ xml = companies(:first_firm).to_xml(
+ :indent => 0, :skip_instruct => true,
+ :include => { :clients => { :only => :name } }
+ )
+
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<client><name>Summit</name></client>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
def test_methods_are_called_on_object
xml = authors(:david).to_xml :methods => :label, :indent => 0
assert_match %r{<label>.*</label>}, xml
@@ -265,4 +374,27 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size
end
+ def test_array_to_xml_including_has_many_association
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
+ assert xml.include?(%(<replies type="array"><reply>))
+ end
+
+ def test_array_to_xml_including_methods
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
+ assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
+ assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
+ end
+
+ def test_array_to_xml_including_has_one_association
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_array_to_xml_including_belongs_to_association
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ end
end
diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml
index abb91101da..6004f390a4 100644
--- a/activerecord/test/fixtures/pirates.yml
+++ b/activerecord/test/fixtures/pirates.yml
@@ -5,5 +5,5 @@ blackbeard:
redbeard:
catchphrase: "Avast!"
parrot: louis
- created_on: <%= 2.weeks.ago.to_s(:db) %>
- updated_on: <%= 2.weeks.ago.to_s(:db) %>
+ created_on: "<%= 2.weeks.ago.to_s(:db) %>"
+ updated_on: "<%= 2.weeks.ago.to_s(:db) %>"
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index efb98b66e7..888afc7604 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -33,4 +33,9 @@ class Bulb < ActiveRecord::Base
end
class CustomBulb < Bulb
+ after_initialize :set_awesomeness
+
+ def set_awesomeness
+ self.frickinawesome = true if name == 'Dude'
+ end
end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 43650c0427..88b139d931 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -7,10 +7,13 @@ class Comment < ActiveRecord::Base
:joins => :post,
:conditions => { "posts.author_id" => 1 }
scope :created
-
+
belongs_to :post, :counter_cache => true
has_many :ratings
+ has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
+ belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count
+
def self.what_are_you
'a comment...'
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index f182a7fa97..4dc9fff9fd 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -227,3 +227,12 @@ class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base
default_scope OpenStruct.new(:call => includes(:projects))
end
+
+class ThreadsafeDeveloper < ActiveRecord::Base
+ self.table_name = 'developers'
+
+ def self.default_scope
+ sleep 0.05 if Thread.current[:long_default_scope]
+ limit(1)
+ end
+end
diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb
index 79a88db0da..6c45e99671 100644
--- a/activerecord/test/models/toy.rb
+++ b/activerecord/test/models/toy.rb
@@ -1,4 +1,6 @@
class Toy < ActiveRecord::Base
set_primary_key :toy_id
belongs_to :pet
+
+ scope :with_pet, joins(:pet)
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index e6cd274586..64e0452100 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -155,6 +155,7 @@ ActiveRecord::Schema.define do
end
t.string :type
t.integer :taggings_count, :default => 0
+ t.integer :children_count, :default => 0
end
create_table :companies, :force => true do |t|
diff --git a/activeresource/README.rdoc b/activeresource/README.rdoc
index b03e8c4c25..c86289c5fe 100644
--- a/activeresource/README.rdoc
+++ b/activeresource/README.rdoc
@@ -22,13 +22,13 @@ received and serialized into a usable Ruby object.
== Download and installation
-The latest version of Active Support can be installed with Rubygems:
+The latest version of Active Support can be installed with RubyGems:
% [sudo] gem install activeresource
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/activeresource/
+* https://github.com/rails/rails/tree/master/activeresource
=== Configuration and Usage
@@ -36,7 +36,7 @@ Putting Active Resource to use is very similar to Active Record. It's as simple
that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
class Person < ActiveResource::Base
- self.site = "http://api.people.com:3000/"
+ self.site = "http://api.people.com:3000"
end
Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
@@ -135,8 +135,8 @@ as the id of the ARes object.
==== Update
-'save' is also used to update an existing resource - and follows the same protocol as creating a resource
-with the exception that no response headers are needed - just an empty response when the update on the
+'save' is also used to update an existing resource and follows the same protocol as creating a resource
+with the exception that no response headers are needed -- just an empty response when the update on the
server side was successful.
# <person><first>Ryan</first></person>
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 74730ca01f..693bd0592e 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -395,7 +395,7 @@ module ActiveResource
# Subclass.site.user = 'david'
# Parent.site # => 'http://david@test.com'
#
- # Without superclass_delegating_reader (expected behaviour)
+ # Without superclass_delegating_reader (expected behavior)
#
# Parent.site = 'http://anonymous@test.com'
# Subclass.site # => 'http://anonymous@test.com'
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 6167c1420e..36f52d61d3 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -149,7 +149,7 @@ module ActiveResource
# Note, by default, every time you call +respond_to+, any previous request and response pairs stored
# in HttpMock will be deleted giving you a clean slate to work on.
#
- # If you want to override this behaviour, pass in +false+ as the last argument to +respond_to+
+ # If you want to override this behavior, pass in +false+ as the last argument to +respond_to+
#
# === Example
#
diff --git a/activeresource/test/cases/base/schema_test.rb b/activeresource/test/cases/base/schema_test.rb
index 48fdeb13df..d29eaf5fb6 100644
--- a/activeresource/test/cases/base/schema_test.rb
+++ b/activeresource/test/cases/base/schema_test.rb
@@ -139,7 +139,7 @@ class SchemaTest < ActiveModel::TestCase
assert_nothing_raised {
Person.schema = new_schema
assert_equal new_schema, Person.schema, "should have saved the schema on the class"
- assert_equal new_schema, Person.new.schema, "should have mde the schema available to every instance"
+ assert_equal new_schema, Person.new.schema, "should have made the schema available to every instance"
}
end
@@ -283,8 +283,8 @@ class SchemaTest < ActiveModel::TestCase
new_attr_name_two = :another_new_schema_attribute
assert Person.schema.blank?, "sanity check - should have a blank class schema"
- assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
- assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
assert_nothing_raised do
Person.schema = {new_attr_name.to_s => 'string'}
@@ -301,8 +301,8 @@ class SchemaTest < ActiveModel::TestCase
assert Person.schema.blank?, "sanity check - should have a blank class schema"
- assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
- assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
assert_nothing_raised do
Person.schema { string new_attr_name_two }
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 034d03533f..3508ec0f34 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,8 +1,13 @@
*Rails 3.2.0 (unreleased)*
+* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< [DHH]
+
+* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
+Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira Matsuda, Damien Mathieu]
+
* The inflector understands acronyms. [dlee]
-* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
+* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH]
@@ -15,6 +20,18 @@
*Rails 3.1.0 (unreleased)*
+* ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
+return the value from `super` [Aaron Patterson]
+
+* Fixed ActiveSupport::Gzip to work properly in Ruby 1.8 [Guillermo Iguaran]
+
+* Kernel.require_library_or_gem was deprecated and will be removed in Rails 3.2.0 [Josh Kalderimis]
+
+* ActiveSupport::Duration#duplicable? was fixed for Ruby 1.8 [thedarkone]
+
+* ActiveSupport::BufferedLogger set log encoding to BINARY, but still use text
+mode to output portable newlines. [fxn]
+
* ActiveSupport::Dependencies now raises NameError if it finds an existing constant in load_missing_constant. This better reflects the nature of the error which is usually caused by calling constantize on a nested constant. [Andrew White]
* Deprecated ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton]
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index a38ad76f43..1ab8e00608 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -8,13 +8,13 @@ outside of Rails.
== Download and installation
-The latest version of Active Support can be installed with Rubygems:
+The latest version of Active Support can be installed with RubyGems:
% [sudo] gem install activesupport
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/activesupport/
+* https://github.com/rails/rails/tree/master/activesupport
== License
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index ac88c82709..95d936b32f 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -347,7 +347,7 @@ module ActiveSupport
entry = read_entry(key, options)
if entry
if entry.expired?
- delete_entry(key)
+ delete_entry(key, options)
else
results[name] = entry.value
end
@@ -557,15 +557,14 @@ module ActiveSupport
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
@created_at = Time.now.to_f
- if defined?(value)
+ if value.nil?
+ @value = nil
+ else
+ @value = Marshal.dump(value)
if should_compress?(value, options)
- @value = Zlib::Deflate.deflate(Marshal.dump(value))
+ @value = Zlib::Deflate.deflate(@value)
@compressed = true
- else
- @value = value
end
- else
- @value = nil
end
end
@@ -576,12 +575,8 @@ module ActiveSupport
# Get the value stored in the cache.
def value
- if defined?(@value)
- val = compressed? ? Marshal.load(Zlib::Inflate.inflate(@value)) : @value
- unless val.frozen?
- val.freeze rescue nil
- end
- val
+ if @value
+ Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
end
end
@@ -614,10 +609,8 @@ module ActiveSupport
def size
if @value.nil?
0
- elsif @value.respond_to?(:bytesize)
- @value.bytesize
else
- Marshal.dump(@value).bytesize
+ @value.bytesize
end
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index c4da04e532..f7c01948b4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -16,7 +16,7 @@ module ActiveSupport
def initialize(cache_path, options = nil)
super(options)
- @cache_path = cache_path
+ @cache_path = cache_path.to_s
extend Strategy::LocalCache
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 64d2c3bff6..e07294178b 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -183,6 +183,14 @@ module ActiveSupport
# Provide support for raw values in the local cache strategy.
module LocalCacheWithRaw # :nodoc:
protected
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
def write_entry(key, entry, options) # :nodoc:
retval = super
if options[:raw] && local_cache && retval
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index 4688468a8f..268c9bed4c 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -5,3 +5,4 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
require 'active_support/core_ext/array/random_access'
+require 'active_support/core_ext/array/prepend_and_append'
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
new file mode 100644
index 0000000000..27718f19d4
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -0,0 +1,7 @@
+class Array
+ # The human way of thinking about adding stuff to the end of a list is with append
+ alias_method :append, :<<
+
+ # The human way of thinking about adding stuff to the beginning of a list is with prepend
+ alias_method :prepend, :unshift
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 080604147d..391bdc925d 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -29,8 +29,11 @@ class BigDecimal
coder.represent_scalar(nil, YAML_MAPPING[string] || string)
end
- def to_d
- self
+ # Backport this method if it doesn't exist
+ unless method_defined?(:to_d)
+ def to_d
+ self
+ end
end
DEFAULT_STRING_FORMAT = 'F'
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index ca9b2c1b60..45bec264ff 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -110,12 +110,6 @@ class Class
private
def singleton_class?
- # in case somebody is crazy enough to overwrite allocate
- allocate = Class.instance_method(:allocate)
- # object.class always points to a real (non-singleton) class
- allocate.bind(self).call.class != self
- rescue TypeError
- # MRI/YARV/JRuby all disallow creating new instances of a singleton class
- true
+ !name || '' == name
end
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 6d7f771b5d..ddb4f3012f 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -20,6 +20,7 @@ module Enumerable
# "2006-02-24 -> Transcript, Transcript"
# "2006-02-23 -> Transcript"
def group_by
+ return to_enum :group_by unless block_given?
assoc = ActiveSupport::OrderedHash.new
each do |element|
@@ -75,9 +76,10 @@ module Enumerable
#
# (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
#
- def each_with_object(memo, &block)
+ def each_with_object(memo)
+ return to_enum :each_with_object, memo unless block_given?
each do |element|
- block.call(element, memo)
+ yield element, memo
end
memo
end unless [].respond_to?(:each_with_object)
@@ -90,14 +92,22 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
#
def index_by
+ return to_enum :index_by unless block_given?
Hash[map { |elem| [yield(elem), elem] }]
end
- # Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1.
+ # Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1.
# Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26.
- def many?(&block)
- size = block_given? ? select(&block).size : self.size
- size > 1
+ def many?
+ cnt = 0
+ if block_given?
+ any? do |element|
+ cnt += 1 if yield element
+ cnt > 1
+ end
+ else
+ any?{ (cnt += 1) > 1 }
+ end
end
# The negative of the Enumerable#include?. Returns true if the collection does not include the object.
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 102378a029..5f07bb4f5a 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -95,7 +95,7 @@ class Hash
case value.class.to_s
when 'Hash'
if value['type'] == 'array'
- _, entries = Array.wrap(value.detect { |k,v| k != 'type' })
+ _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
if entries.nil? || (c = value['__content__'] && c.blank?)
[]
else
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index d7fb2da0fb..0484d8e5d8 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -30,6 +30,8 @@ class Hash
omit
end
+ # Removes and returns the key/value pairs matching the given keys.
+ # {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2}
def extract!(*keys)
result = {}
keys.each {|key| result[key] = delete(key) }
diff --git a/activesupport/lib/active_support/core_ext/io.rb b/activesupport/lib/active_support/core_ext/io.rb
new file mode 100644
index 0000000000..75f1055191
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/io.rb
@@ -0,0 +1,15 @@
+if RUBY_VERSION < '1.9.2'
+
+# :stopdoc:
+class IO
+ def self.binread(name, length = nil, offset = nil)
+ return File.read name unless length || offset
+ File.open(name, 'rb') { |f|
+ f.seek offset if offset
+ f.read length
+ }
+ end
+end
+# :startdoc:
+
+end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 1777a4b32d..7de824a77f 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/module/remove_method"
-
class Module
# Provides a delegate class method to easily expose contained objects' methods
# as your own. Pass one or more methods (specified as symbols or strings)
@@ -108,39 +106,48 @@ class Module
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
end
+ prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil]
- if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
+ if prefix == true && to.to_s =~ /^[^a-z_]/
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
end
- prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : ''
+ method_prefix =
+ if prefix
+ "#{prefix == true ? to : prefix}_"
+ else
+ ''
+ end
file, line = caller.first.split(':', 2)
line = line.to_i
methods.each do |method|
- on_nil =
- if options[:allow_nil]
- 'return'
- else
- %(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- end
+ method = method.to_s
- module_eval(<<-EOS, file, line - 5)
- if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}")
- remove_possible_method("#{prefix}#{method}")
- end
+ if allow_nil
+ module_eval(<<-EOS, file, line - 2)
+ def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
+ #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ end # end
+ end # end
+ EOS
+ else
+ exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
- rescue NoMethodError # rescue NoMethodError
- if #{to}.nil? # if client.nil?
- #{on_nil} # return # depends on :allow_nil
- else # else
- raise # raise
- end # end
- end # end
- EOS
+ module_eval(<<-EOS, file, line - 1)
+ def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ rescue NoMethodError # rescue NoMethodError
+ if #{to}.nil? # if client.nil?
+ #{exception} # # add helpful message to the exception
+ else # else
+ raise # raise
+ end # end
+ end # end
+ EOS
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 07d7c9b018..b76bc16ee1 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,11 +1,16 @@
class Module
def remove_possible_method(method)
- remove_method(method)
+ if method_defined?(method) || private_method_defined?(method)
+ remove_method(method)
+ end
rescue NameError
+ # If the requested method is defined on a superclass or included module,
+ # method_defined? returns true but remove_method throws a NameError.
+ # Ignore this.
end
def redefine_method(method, &block)
remove_possible_method(method)
define_method(method, &block)
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 8221dc4abe..fe27f45295 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,3 +1,6 @@
+# encoding: utf-8
+require 'active_support/core_ext/string/encoding'
+
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, "", " ", +nil+, [], and {} are all blank.
@@ -86,14 +89,23 @@ class Hash
end
class String
+ # 0x3000: fullwidth whitespace
+ NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
+
# A string is blank if it's empty or contains whitespaces only:
#
# "".blank? # => true
# " ".blank? # => true
+ # " ".blank? # => true
# " something here ".blank? # => false
#
def blank?
- self !~ /\S/
+ # 1.8 does not takes [:space:] properly
+ if encoding_aware?
+ self !~ /[^[:space:]]/
+ else
+ self !~ NON_WHITESPACE_REGEXP
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 664537eea4..0f8933b658 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require 'date'
require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/core_ext/time/calculations'
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 41de4d6435..aae1cfccf2 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -35,7 +35,7 @@ class String
# object. Interoperability problems can be resolved easily with a +to_s+ call.
#
# For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
- # information about how to change the default Multibyte behaviour see ActiveSupport::Multibyte.
+ # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
def mb_chars
if ActiveSupport::Multibyte.proxy_class.consumes?(self)
ActiveSupport::Multibyte.proxy_class.new(self)
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 3bf4edbdef..f111c8e5a3 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -20,7 +20,7 @@ class ERB
if s.html_safe?
s
else
- s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }.html_safe
+ s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;").html_safe
end
end
@@ -86,6 +86,12 @@ module ActiveSupport #:nodoc:
end
end
+ def[](*args)
+ new_safe_buffer = super
+ new_safe_buffer.instance_eval { @dirty = false }
+ new_safe_buffer
+ end
+
def safe_concat(value)
raise SafeConcatError if dirty?
original_concat(value)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index c5fbbcf890..a15a06d0e4 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,5 +1,6 @@
require 'active_support/duration'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/time/conversions'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index d1543c4c58..c072a8e646 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -229,11 +229,15 @@ module ActiveSupport #:nodoc:
end
def load(file, *)
- load_dependency(file) { super }
+ result = false
+ load_dependency(file) { result = super }
+ result
end
def require(file, *)
- load_dependency(file) { super }
+ result = false
+ load_dependency(file) { result = super }
+ result
end
# Mark the given constant as unloadable. Unloadable constants are removed each
@@ -417,7 +421,8 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- autoload_once_paths.any? { |base| path.starts_with? base }
+ # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false
+ autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
# Attempt to autoload the provided module name by searching for a directory
@@ -478,10 +483,6 @@ module ActiveSupport #:nodoc:
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
- trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}}
- name_error = NameError.new("uninitialized constant #{qualified_name}")
- name_error.set_backtrace(trace)
-
file_path = search_for_file(path_suffix)
if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
@@ -500,11 +501,12 @@ module ActiveSupport #:nodoc:
return parent.const_missing(const_name)
rescue NameError => e
raise unless e.missing_name? qualified_name_for(parent, const_name)
- raise name_error
end
- else
- raise name_error
end
+
+ raise NameError,
+ "uninitialized constant #{qualified_name}",
+ caller.reject {|l| l.starts_with? __FILE__ }
end
# Remove the constants that have been autoloaded, and those that have been
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index da4af339fc..f9505a247c 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -7,12 +7,12 @@ module ActiveSupport
# Whether to print a backtrace along with the warning.
attr_accessor :debug
- # Returns the set behaviour or if one isn't set, defaults to +:stderr+
+ # Returns the set behavior or if one isn't set, defaults to +:stderr+
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
- # Sets the behaviour to the specified value. Can be a single value or an array.
+ # Sets the behavior to the specified value. Can be a single value or an array.
#
# Examples
#
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index a97e9d7daf..f76ddff038 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -22,7 +22,7 @@ module ActiveSupport
end
def updated_at
- paths.map { |path| File.stat(path).mtime }.max
+ paths.map { |path| File.mtime(path) }.max
end
def execute_if_updated
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 62f9c9aa2e..9651f02c73 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -1,5 +1,6 @@
require 'zlib'
require 'stringio'
+require 'active_support/core_ext/string/encoding'
module ActiveSupport
# A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index a25e951080..4c59fe9ac9 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,5 +1,4 @@
require "active_support"
-require "rails"
require "active_support/file_update_checker"
require "active_support/core_ext/array/wrap"
@@ -38,6 +37,8 @@ module I18n
protected
+ @i18n_inited = false
+
# Setup i18n configuration
def self.initialize_i18n(app)
return if @i18n_inited
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 06ceccdb22..daf2a1e1d9 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -54,6 +54,7 @@ module ActiveSupport
inflect.irregular('sex', 'sexes')
inflect.irregular('move', 'moves')
inflect.irregular('cow', 'kine')
+ inflect.irregular('zombie', 'zombies')
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 3d28d33f40..423b5abd20 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -118,8 +118,10 @@ module ActiveSupport
# +titleize+ is also aliased as as +titlecase+.
#
# Examples:
- # "man from the boondocks".titleize # => "Man From The Boondocks"
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ # "TheManWithoutAPast".titleize # => "The Man Without A Past"
+ # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
def titleize(word)
humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index f9607f1aaf..b78d92f599 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -331,8 +331,7 @@ module ActiveSupport #:nodoc:
# when the storage for a string is limited for some reason.
#
# Example:
- # s = 'こんにちは'
- # s.mb_chars.limit(7) # => "こに"
+ # 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
def limit(limit)
slice(0...translate_offset(limit))
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 513f83e445..754ca9290b 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
module ActiveSupport
module Multibyte
module Unicode
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 77696eb1db..b5a70d5933 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/delegation'
-
module ActiveSupport
# Notifications provides an instrumentation API for Ruby. To instrument an
# action in Ruby you just need to do:
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 68f4bd66da..7f70628933 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -6,7 +6,7 @@ end
require 'yaml'
YAML.add_builtin_type("omap") do |type, val|
- ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
+ ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }]
end
module ActiveSupport
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 04df2ea562..1638512af0 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,5 +1,4 @@
require "active_support"
-require "rails"
require "active_support/i18n_railtie"
module ActiveSupport
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 3864b1f5a6..f3629ada5b 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -45,15 +45,17 @@ module ActiveSupport
# post :delete, :id => ...
# end
def assert_difference(expression, difference = 1, message = nil, &block)
- exps = Array.wrap(expression).map { |e|
+ expressions = Array.wrap expression
+
+ exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
}
before = exps.map { |e| e.call }
yield
- exps.each_with_index do |e, i|
- error = "#{e.inspect} didn't change by #{difference}"
+ expressions.zip(exps).each_with_index do |(code, e), i|
+ error = "#{code.inspect} didn't change by #{difference}"
error = "#{message}.\n#{error}" if message
assert_equal(before[i] + difference, e.call, error)
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 3d092529d6..ec2c717942 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -109,7 +109,7 @@ module ActiveSupport
def xmlschema(fraction_digits = 0)
fraction = if fraction_digits > 0
- ".%i" % time.usec.to_s[0, fraction_digits]
+ (".%06i" % time.usec)[0, fraction_digits + 1]
end
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 728921a069..4fb487ade1 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -195,12 +195,8 @@ module ActiveSupport
# (GMT). Seconds were chosen as the offset unit because that is the unit that
# Ruby uses to represent time zone offsets (see Time#utc_offset).
def initialize(name, utc_offset = nil, tzinfo = nil)
- begin
- require 'tzinfo'
- rescue LoadError => e
- $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
- raise e
- end
+ self.class.send(:require_tzinfo)
+
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
@@ -337,7 +333,12 @@ module ActiveSupport
end
def zones_map
- @zones_map ||= Hash[MAPPING.map { |place, _| [place, create(place)] }]
+ @zones_map ||= begin
+ new_zones_names = MAPPING.keys - lazy_zones_map.keys
+ new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
+
+ lazy_zones_map.merge(new_zones)
+ end
end
# Locate a specific time zone object. If the argument is a string, it
@@ -349,7 +350,7 @@ module ActiveSupport
case arg
when String
begin
- zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -367,11 +368,28 @@ module ActiveSupport
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
end
+ protected
+
+ def require_tzinfo
+ require 'tzinfo'
+ rescue LoadError
+ $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
+ raise
+ end
+
private
def lookup(name)
(tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
end
+
+ def lazy_zones_map
+ require_tzinfo
+
+ @lazy_zones_map ||= Hash.new do |hash, place|
+ hash[place] = create(place) if MAPPING.has_key?(place)
+ end
+ end
end
end
end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 498127e5bc..6bb13ec9b8 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -199,12 +199,20 @@ module CacheStoreBehavior
@cache.write('fud', 'biz')
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
end
+
+ def test_read_multi_with_expires
+ @cache.write('foo', 'bar', :expires_in => 0.001)
+ @cache.write('fu', 'baz')
+ @cache.write('fud', 'biz')
+ sleep(0.002)
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
+ end
def test_read_and_write_compressed_small_data
@cache.write('foo', 'bar', :compress => true)
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
assert_equal 'bar', @cache.read('foo')
- assert_equal 'bar', raw_value
+ assert_equal 'bar', Marshal.load(raw_value)
end
def test_read_and_write_compressed_large_data
@@ -270,10 +278,12 @@ module CacheStoreBehavior
assert !@cache.exist?('foo')
end
- def test_store_objects_should_be_immutable
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
@cache.write('foo', 'bar')
- assert_raise(ActiveSupport::FrozenObjectError) { @cache.read('foo').gsub!(/.*/, 'baz') }
- assert_equal 'bar', @cache.read('foo')
+ assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
+ value = @cache.read('foo')
+ value << 'bingo'
+ assert_not_equal value, @cache.read('foo')
end
def test_original_store_objects_should_not_be_immutable
@@ -521,6 +531,7 @@ class FileStoreTest < ActiveSupport::TestCase
Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
@cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
@peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
+ @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60)
end
def teardown
@@ -540,11 +551,18 @@ class FileStoreTest < ActiveSupport::TestCase
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
end
+
+ def test_key_transformation_with_pathname
+ FileUtils.touch(File.join(cache_dir, "foo"))
+ key = @cache_with_pathname.send(:key_file_path, "views/index?id=1")
+ assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
+ end
end
class MemoryStoreTest < ActiveSupport::TestCase
def setup
- @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => 100)
+ @record_size = Marshal.dump("aaaaaaaaaa").bytesize
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10)
end
include CacheStoreBehavior
@@ -559,7 +577,7 @@ class MemoryStoreTest < ActiveSupport::TestCase
@cache.write(5, "eeeeeeeeee") && sleep(0.001)
@cache.read(2) && sleep(0.001)
@cache.read(4)
- @cache.prune(30)
+ @cache.prune(@record_size * 3)
assert_equal true, @cache.exist?(5)
assert_equal true, @cache.exist?(4)
assert_equal false, @cache.exist?(3)
@@ -635,7 +653,14 @@ uses_memcached 'memcached backed store' do
cache.write("foo", 2)
assert_equal "2", cache.read("foo")
end
-
+
+ def test_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
+ cache.clear
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+
def test_local_cache_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
cache.clear
@@ -644,6 +669,15 @@ uses_memcached 'memcached backed store' do
assert_equal "2", cache.read("foo")
end
end
+
+ def test_local_cache_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
+ cache.clear
+ cache.with_local_cache do
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+ end
end
end
@@ -696,7 +730,7 @@ class CacheEntryTest < ActiveSupport::TestCase
def test_non_compress_values
entry = ActiveSupport::Cache::Entry.new("value")
assert_equal "value", entry.value
- assert_equal "value", entry.raw_value
+ assert_equal "value", Marshal.load(entry.raw_value)
assert_equal false, entry.compressed?
end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index e532010b18..52231aaeb0 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -465,3 +465,13 @@ class ArrayWrapperTests < Test::Unit::TestCase
assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
end
end
+
+class ArrayPrependAppendTest < Test::Unit::TestCase
+ def test_append
+ assert_equal [1, 2], [1].append(2)
+ end
+
+ def test_prepend
+ assert_equal [2, 1], [1].prepend(2)
+ end
+end
diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/blank_test.rb
index 97c6b213ba..a2cf298905 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/blank_test.rb
@@ -1,8 +1,10 @@
+# encoding: utf-8
+
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
class BlankTest < Test::Unit::TestCase
- BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ]
+ BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ]
NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ]
def test_blank
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb
index 24e0ccd9b3..e48e6a7c45 100644
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ b/activesupport/test/core_ext/duplicable_test.rb
@@ -4,22 +4,27 @@ require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/numeric/time'
class DuplicableTest < Test::Unit::TestCase
- NO = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56'), Class.new, Module.new, 5.seconds]
+ RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56'), 5.seconds]
YES = ['1', Object.new, /foo/, [], {}, Time.now]
+ NO = [Class.new, Module.new]
def test_duplicable
- NO.each do |v|
+ (RAISE_DUP + NO).each do |v|
assert !v.duplicable?
- begin
- v.dup
- fail
- rescue Exception
- end
end
YES.each do |v|
assert v.duplicable?
+ end
+
+ (YES + NO).each do |v|
assert_nothing_raised { v.dup }
end
+
+ RAISE_DUP.each do |v|
+ assert_raises(TypeError) do
+ v.dup
+ end
+ end
end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 4655bfe519..cdfa991a34 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -8,6 +8,19 @@ class SummablePayment < Payment
end
class EnumerableTests < Test::Unit::TestCase
+ Enumerator = [].each.class
+
+ class GenericEnumerable
+ include Enumerable
+ def initialize(values = [1, 2, 3])
+ @values = values
+ end
+
+ def each
+ @values.each{|v| yield v}
+ end
+ end
+
def test_group_by
names = %w(marcel sam david jeremy)
klass = Struct.new(:name)
@@ -17,7 +30,8 @@ class EnumerableTests < Test::Unit::TestCase
people << p
end
- grouped = objects.group_by { |object| object.name }
+ enum = GenericEnumerable.new(objects)
+ grouped = enum.group_by { |object| object.name }
grouped.each do |name, group|
assert group.all? { |person| person.name == name }
@@ -25,20 +39,24 @@ class EnumerableTests < Test::Unit::TestCase
assert_equal objects.uniq.map(&:name), grouped.keys
assert({}.merge(grouped), "Could not convert ActiveSupport::OrderedHash into Hash")
+ assert_equal Enumerator, enum.group_by.class
+ assert_equal grouped, enum.group_by.each(&:name)
end
def test_sums
- assert_equal 30, [5, 15, 10].sum
- assert_equal 30, [5, 15, 10].sum { |i| i }
+ enum = GenericEnumerable.new([5, 15, 10])
+ assert_equal 30, enum.sum
+ assert_equal 60, enum.sum { |i| i * 2}
- assert_equal 'abc', %w(a b c).sum
- assert_equal 'abc', %w(a b c).sum { |i| i }
+ enum = GenericEnumerable.new(%w(a b c))
+ assert_equal 'abc', enum.sum
+ assert_equal 'aabbcc', enum.sum { |i| i * 2 }
- payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ]
+ payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal 30, payments.sum(&:price)
assert_equal 60, payments.sum { |p| p.price * 2 }
- payments = [ SummablePayment.new(5), SummablePayment.new(15) ]
+ payments = GenericEnumerable.new([ SummablePayment.new(5), SummablePayment.new(15) ])
assert_equal SummablePayment.new(20), payments.sum
assert_equal SummablePayment.new(20), payments.sum { |p| p }
end
@@ -46,21 +64,21 @@ class EnumerableTests < Test::Unit::TestCase
def test_nil_sums
expected_raise = TypeError
- assert_raise(expected_raise) { [5, 15, nil].sum }
+ assert_raise(expected_raise) { GenericEnumerable.new([5, 15, nil]).sum }
- payments = [ Payment.new(5), Payment.new(15), Payment.new(10), Payment.new(nil) ]
+ payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10), Payment.new(nil) ])
assert_raise(expected_raise) { payments.sum(&:price) }
assert_equal 60, payments.sum { |p| p.price.to_i * 2 }
end
def test_empty_sums
- assert_equal 0, [].sum
- assert_equal 0, [].sum { |i| i }
- assert_equal Payment.new(0), [].sum(Payment.new(0))
+ assert_equal 0, GenericEnumerable.new([]).sum
+ assert_equal 0, GenericEnumerable.new([]).sum { |i| i + 10 }
+ assert_equal Payment.new(0), GenericEnumerable.new([]).sum(Payment.new(0))
end
- def test_enumerable_sums
+ def test_range_sums
assert_equal 20, (1..4).sum { |i| i * 2 }
assert_equal 10, (1..4).sum
assert_equal 10, (1..4.5).sum
@@ -69,29 +87,43 @@ class EnumerableTests < Test::Unit::TestCase
end
def test_each_with_object
- result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
+ enum = GenericEnumerable.new(%w(foo bar))
+ result = enum.each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result)
+ assert_equal Enumerator, enum.each_with_object({}).class
+ result2 = enum.each_with_object({}).each{|str, hsh| hsh[str] = str.upcase}
+ assert_equal result, result2
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] },
+ payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
+ assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by { |p| p.price })
+ assert_equal Enumerator, payments.index_by.class
+ assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
+ payments.index_by.each { |p| p.price })
end
def test_many
- assert ![].many?
- assert ![ 1 ].many?
- assert [ 1, 2 ].many?
-
- assert ![].many? {|x| x > 1 }
- assert ![ 2 ].many? {|x| x > 1 }
- assert ![ 1, 2 ].many? {|x| x > 1 }
- assert [ 1, 2, 2 ].many? {|x| x > 1 }
+ assert_equal false, GenericEnumerable.new([] ).many?
+ assert_equal false, GenericEnumerable.new([ 1 ] ).many?
+ assert_equal true, GenericEnumerable.new([ 1, 2 ] ).many?
+
+ assert_equal false, GenericEnumerable.new([] ).many? {|x| x > 1 }
+ assert_equal false, GenericEnumerable.new([ 2 ] ).many? {|x| x > 1 }
+ assert_equal false, GenericEnumerable.new([ 1, 2 ] ).many? {|x| x > 1 }
+ assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? {|x| x > 1 }
+ end
+
+ def test_many_iterates_only_on_what_is_needed
+ infinity = 1.0/0.0
+ very_long_enum = 0..infinity
+ assert_equal true, very_long_enum.many?
+ assert_equal true, very_long_enum.many?{|x| x > 100}
end
def test_exclude?
- assert [ 1 ].exclude?(2)
- assert ![ 1 ].exclude?(1)
+ assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2)
+ assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1)
end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 0b3f18faec..1813ba2a4d 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -670,6 +670,55 @@ class HashToXmlTest < Test::Unit::TestCase
assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
end
+ def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding
+ topics_xml = <<-EOT
+ <topics type="array" page="1" page-count="1000" per-page="2">
+ <topic>
+ <title>The First Topic</title>
+ <author-name>David</author-name>
+ <id type="integer">1</id>
+ <approved type="boolean">false</approved>
+ <replies-count type="integer">0</replies-count>
+ <replies-close-in type="integer">2592000000</replies-close-in>
+ <written-on type="date">2003-07-16</written-on>
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id nil="true"></parent-id>
+ </topic>
+ <topic>
+ <title>The Second Topic</title>
+ <author-name>Jason</author-name>
+ <id type="integer">1</id>
+ <approved type="boolean">false</approved>
+ <replies-count type="integer">0</replies-count>
+ <replies-close-in type="integer">2592000000</replies-close-in>
+ <written-on type="date">2003-07-16</written-on>
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id></parent-id>
+ </topic>
+ </topics>
+ EOT
+
+ expected_topic_hash = {
+ :title => "The First Topic",
+ :author_name => "David",
+ :id => 1,
+ :approved => false,
+ :replies_count => 0,
+ :replies_close_in => 2592000000,
+ :written_on => Date.new(2003, 7, 16),
+ :viewed_at => Time.utc(2003, 7, 16, 9, 28),
+ :content => "Have a nice day",
+ :author_email_address => "david@loudthinking.com",
+ :parent_id => nil
+ }.stringify_keys
+
+ assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
+ end
+
def test_single_record_from_xml
topic_xml = <<-EOT
<topic>
diff --git a/activesupport/test/core_ext/io_test.rb b/activesupport/test/core_ext/io_test.rb
new file mode 100644
index 0000000000..b9abf685da
--- /dev/null
+++ b/activesupport/test/core_ext/io_test.rb
@@ -0,0 +1,23 @@
+require 'abstract_unit'
+
+require 'active_support/core_ext/io'
+
+class IOTest < Test::Unit::TestCase
+ def test_binread_one_arg
+ assert_equal File.read(__FILE__), IO.binread(__FILE__)
+ end
+
+ def test_binread_two_args
+ assert_equal File.read(__FILE__).bytes.first(10).pack('C*'),
+ IO.binread(__FILE__, 10)
+ end
+
+ def test_binread_three_args
+ actual = IO.binread(__FILE__, 5, 10)
+ expected = File.open(__FILE__, 'rb') { |f|
+ f.seek 10
+ f.read 5
+ }
+ assert_equal expected, actual
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a95cf1591f..449d3810e2 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -26,11 +26,20 @@ module Yz
end
end
-Somewhere = Struct.new(:street, :city)
+Somewhere = Struct.new(:street, :city) do
+ attr_accessor :name
+end
-Someone = Struct.new(:name, :place) do
+class Someone < Struct.new(:name, :place)
delegate :street, :city, :to_f, :to => :place
+ delegate :name=, :to => :place, :prefix => true
delegate :upcase, :to => "place.city"
+
+ FAILED_DELEGATE_LINE = __LINE__ + 1
+ delegate :foo, :to => :place
+
+ FAILED_DELEGATE_LINE_2 = __LINE__ + 1
+ delegate :bar, :to => :place, :allow_nil => true
end
Invoice = Struct.new(:client) do
@@ -69,6 +78,11 @@ class ModuleTest < Test::Unit::TestCase
assert_equal "Chicago", @david.city
end
+ def test_delegation_to_assignment_method
+ @david.place_name = "Fred"
+ assert_equal "Fred", @david.place.name
+ end
+
def test_delegation_down_hierarchy
assert_equal "CHICAGO", @david.upcase
end
@@ -164,6 +178,24 @@ class ModuleTest < Test::Unit::TestCase
end
end
+ def test_delegation_exception_backtrace
+ someone = Someone.new("foo", "bar")
+ someone.foo
+ rescue NoMethodError => e
+ file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}"
+ assert e.backtrace.first.include?(file_and_line),
+ "[#{e.backtrace.first}] did not include [#{file_and_line}]"
+ end
+
+ def test_delegation_exception_backtrace_with_allow_nil
+ someone = Someone.new("foo", "bar")
+ someone.bar
+ rescue NoMethodError => e
+ file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}"
+ assert e.backtrace.first.include?(file_and_line),
+ "[#{e.backtrace.first}] did not include [#{file_and_line}]"
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 4000cc913a..81a284dded 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -6,12 +6,18 @@ require 'inflector_test_cases'
require 'active_support/inflector'
require 'active_support/core_ext/string'
require 'active_support/time'
-require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/string/strip'
+require 'active_support/core_ext/string/output_safety'
class StringInflectionsTest < Test::Unit::TestCase
include InflectorTestCases
+ def test_erb_escape
+ string = [192, 60].pack('CC')
+ expected = 192.chr + "&lt;"
+ assert_equal expected, ERB::Util.html_escape(string)
+ end
+
def test_strip_heredoc_on_an_empty_string
assert_equal '', ''.strip_heredoc
end
@@ -158,7 +164,7 @@ class StringInflectionsTest < Test::Unit::TestCase
assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local)
assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time
assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local)
- assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 22:50 -0100".to_time
+ assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time
assert_nil "".to_time
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 72b55183ba..b2309ae806 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -111,6 +111,13 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(12)
end
+ def test_xmlschema_with_fractional_seconds_lower_than_hundred_thousand
+ @twz += 0.001234 # advance the time by a fraction
+ assert_equal "1999-12-31T19:00:00.001-05:00", @twz.xmlschema(3)
+ assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(6)
+ assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12)
+ end
+
def test_to_yaml
assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml)
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index b4edf0f51d..fe8f51e11a 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -1,7 +1,6 @@
require 'abstract_unit'
require 'pp'
require 'active_support/dependencies'
-require 'active_support/core_ext/kernel/reporting'
module ModuleWithMissing
mattr_accessor :missing_count
@@ -521,6 +520,24 @@ class DependenciesTest < Test::Unit::TestCase
ActiveSupport::Dependencies.autoload_once_paths = []
end
+ def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants
+ with_autoloading_fixtures do
+ pathnames = ActiveSupport::Dependencies.autoload_paths.collect{|p| Pathname.new(p)}
+ ActiveSupport::Dependencies.autoload_paths = pathnames
+ ActiveSupport::Dependencies.autoload_once_paths = pathnames
+
+ assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
+ assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
+ assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
+
+ 1 if ModuleFolder::NestedClass # 1 if to avoid warning
+ assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
+ end
+ ensure
+ Object.class_eval { remove_const :ModuleFolder }
+ ActiveSupport::Dependencies.autoload_once_paths = []
+ end
+
def test_application_should_special_case_application_controller
with_autoloading_fixtures do
require_dependency 'application'
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 6ccffa59b1..d1454902e5 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -1,8 +1,7 @@
-# encoding: UTF-8
+# encoding: utf-8
require 'abstract_unit'
require 'active_support/json'
require 'active_support/time'
-require 'active_support/core_ext/kernel/reporting'
class TestJSONDecoding < ActiveSupport::TestCase
TESTS = {
diff --git a/activesupport/test/multibyte_utils_test.rb b/activesupport/test/multibyte_utils_test.rb
index 1dff944922..0a2f20d282 100644
--- a/activesupport/test/multibyte_utils_test.rb
+++ b/activesupport/test/multibyte_utils_test.rb
@@ -2,7 +2,6 @@
require 'abstract_unit'
require 'multibyte_test_helpers'
-require 'active_support/core_ext/kernel/reporting'
class MultibyteUtilsTest < ActiveSupport::TestCase
include MultibyteTestHelpers
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index cc0dc564f7..884ee61547 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/module/delegation'
module Notifications
class TestCase < ActiveSupport::TestCase
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 46bc0d7a34..8f77999d25 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -80,14 +80,12 @@ class SafeBufferTest < ActiveSupport::TestCase
end
test "Should escape dirty buffers on add" do
- dirty = @buffer
clean = "hello".html_safe
@buffer.gsub!('', '<>')
assert_equal "hello&lt;&gt;", clean + @buffer
end
test "Should concat as a normal string when dirty" do
- dirty = @buffer
clean = "hello".html_safe
@buffer.gsub!('', '<>')
assert_equal "<>hello", @buffer + clean
@@ -108,4 +106,10 @@ class SafeBufferTest < ActiveSupport::TestCase
test "should not fail if the returned object is not a string" do
assert_kind_of NilClass, @buffer.slice("chipchop")
end
+
+ test "Should initialize @dirty to false for new instance when sliced" do
+ dirty = @buffer[0,0].send(:dirty?)
+ assert_not_nil dirty
+ assert !dirty
+ end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 5bd995aa32..f880052786 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/kernel/reporting'
class AssertDifferenceTest < ActiveSupport::TestCase
def setup
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index e2b90ae16e..dde17ea403 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -50,49 +50,49 @@ module XmlMiniTest
def test_rename_key_does_not_dasherize_multiple_trailing_underscores
assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
- end
+ end
end
class ToTagTest < ActiveSupport::TestCase
def assert_xml(xml)
assert_equal xml, @options[:builder].target!
end
-
- setup do
+
+ def setup
@xml = ActiveSupport::XmlMini
@options = {:skip_instruct => true, :builder => Builder::XmlMarkup.new}
end
-
+
test "#to_tag accepts a callable object and passes options with the builder" do
@xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options)
assert_xml "<br/>"
end
-
+
test "#to_tag accepts a callable object and passes options and tag name" do
@xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options)
assert_xml "<b>tag</b>"
end
-
+
test "#to_tag accepts an object responding to #to_xml and passes the options, where :root is key" do
obj = Object.new
obj.instance_eval do
def to_xml(options) options[:builder].yo(options[:root].to_s) end
end
-
+
@xml.to_tag(:tag, obj, @options)
assert_xml "<yo>tag</yo>"
end
-
+
test "#to_tag accepts arbitrary objects responding to #to_str" do
@xml.to_tag(:b, "Howdy", @options)
assert_xml "<b>Howdy</b>"
end
-
+
test "#to_tag should dasherize the space when passed a string with spaces as a key" do
@xml.to_tag("New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
end
-
+
test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do
@xml.to_tag(:"New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
deleted file mode 100755
index 50f7f410fa..0000000000
--- a/ci/ci_build.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/env ruby
-require 'fileutils'
-include FileUtils
-
-def root_dir
- @root_dir ||= File.expand_path('../..', __FILE__)
-end
-
-def rake(*tasks)
- tasks.each do |task|
- cmd = "bundle exec rake #{task}"
- puts "Running command: #{cmd}"
- return false unless system(cmd)
- end
- true
-end
-
-puts "[CruiseControl] Rails build"
-build_results = {}
-
-# Install required version of bundler.
-bundler_install_cmd = "sudo gem install bundler --no-ri --no-rdoc"
-puts "Running command: #{bundler_install_cmd}"
-build_results[:install_bundler] = system bundler_install_cmd
-
-cd root_dir do
- puts
- puts "[CruiseControl] Bundling gems"
- puts
- build_results[:bundle] = system 'bundle update'
-end
-
-cd "#{root_dir}/activesupport" do
- puts
- puts "[CruiseControl] Building Active Support"
- puts
- build_results[:activesupport] = rake 'test'
- build_results[:activesupport_isolated] = rake 'test:isolated'
-end
-
-system "sudo rm -R #{root_dir}/railties/tmp"
-cd "#{root_dir}/railties" do
- puts
- puts "[CruiseControl] Building Railties"
- puts
- build_results[:railties] = rake 'test'
-end
-
-cd "#{root_dir}/actionpack" do
- puts
- puts "[CruiseControl] Building Action Pack"
- puts
- build_results[:actionpack] = rake 'test'
- build_results[:actionpack_isolated] = rake 'test:isolated'
-end
-
-cd "#{root_dir}/actionmailer" do
- puts
- puts "[CruiseControl] Building Action Mailer"
- puts
- build_results[:actionmailer] = rake 'test'
- build_results[:actionmailer_isolated] = rake 'test:isolated'
-end
-
-cd "#{root_dir}/activemodel" do
- puts
- puts "[CruiseControl] Building Active Model"
- puts
- build_results[:activemodel] = rake 'test'
- build_results[:activemodel_isolated] = rake 'test:isolated'
-end
-
-rm_f "#{root_dir}/activeresource/debug.log"
-cd "#{root_dir}/activeresource" do
- puts
- puts "[CruiseControl] Building Active Resource"
- puts
- build_results[:activeresource] = rake 'test'
- build_results[:activeresource_isolated] = rake 'test:isolated'
-end
-
-rm_f "#{root_dir}/activerecord/debug.log"
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with MySQL IM enabled"
- puts
- ENV['IM'] = 'true'
- build_results[:activerecord_mysql_IM] = rake 'mysql:rebuild_databases', 'mysql:test'
- build_results[:activerecord_mysql_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with MySQL IM disabled"
- puts
- ENV['IM'] = 'false'
- build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'mysql:test'
- build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with MySQL2 IM enabled"
- puts
- ENV['IM'] = 'true'
- build_results[:activerecord_mysql2_IM] = rake 'mysql:rebuild_databases', 'mysql2:test'
- build_results[:activerecord_mysql2_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with MySQL2 IM disabled"
- puts
- ENV['IM'] = 'false'
- build_results[:activerecord_mysql2] = rake 'mysql:rebuild_databases', 'mysql2:test'
- build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with PostgreSQL IM enabled"
- puts
- ENV['IM'] = 'true'
- build_results[:activerecord_postgresql8_IM] = rake 'postgresql:rebuild_databases', 'postgresql:test'
- build_results[:activerecord_postgresql8_isolated_IM] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with PostgreSQL IM disabled"
- puts
- ENV['IM'] = 'false'
- build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'postgresql:test'
- build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with SQLite 3 IM enabled"
- puts
- ENV['IM'] = 'true'
- build_results[:activerecord_sqlite3_IM] = rake 'sqlite3:test'
- build_results[:activerecord_sqlite3_isolated_IM] = rake 'sqlite3:isolated_test'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building Active Record with SQLite 3 IM disabled"
- puts
- ENV['IM'] = 'false'
- build_results[:activerecord_sqlite3] = rake 'sqlite3:test'
- build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test'
-end
-
-
-puts
-puts "[CruiseControl] Build environment:"
-puts "[CruiseControl] #{`cat /etc/issue`}"
-puts "[CruiseControl] #{`uname -a`}"
-puts "[CruiseControl] #{`ruby -v`}"
-puts "[CruiseControl] #{`mysql --version`}"
-puts "[CruiseControl] #{`pg_config --version`}"
-puts "[CruiseControl] SQLite3: #{`sqlite3 -version`}"
-`gem env`.each_line {|line| print "[CruiseControl] #{line}"}
-puts "[CruiseControl] Bundled gems:"
-`bundle show`.each_line {|line| print "[CruiseControl] #{line}"}
-puts "[CruiseControl] Local gems:"
-`gem list`.each_line {|line| print "[CruiseControl] #{line}"}
-
-failures = build_results.select { |key, value| value == false }
-
-if failures.empty?
- puts
- puts "[CruiseControl] Rails build finished sucessfully"
- exit(0)
-else
- puts
- puts "[CruiseControl] Rails build FAILED"
- puts "[CruiseControl] Failed components: #{failures.map { |component| component.first }.join(', ')}"
- exit(-1)
-end
diff --git a/ci/ci_setup_notes.txt b/ci/ci_setup_notes.txt
deleted file mode 100644
index 890f9e8ef6..0000000000
--- a/ci/ci_setup_notes.txt
+++ /dev/null
@@ -1,140 +0,0 @@
-# Rails Continuous Integration Server Setup Notes
-# This procedure was used to set up http://ci.rubyonrails.org on Ubuntu 8.04
-# It can be used as a guideline for setting up your own CI server against your local rails branches
-
-* Set up ci user:
-# log in as root
-$ adduser ci
-enter user info and password
-$ visudo
-# give ci user same sudo rights as root
-
-* Disable root login:
-# log in as ci
-$ sudo vi /etc/shadow
-# overwrite and disable encrypted root password to disable root login:
-root:*:14001:0:99999:7:::
-
-* Change Hostname:
-$ sudo vi /etc/hostname
-change to correct hostname
-$ sudo vi /etc/hosts
-replace old hostname with the correct hostname
-# reboot to use new hostname (and test reboot)
-$ sudo shutdown -r now
-
-* Update aptitude:
-$ sudo aptitude update
-
-* Use cinabox to perform rest of ruby/ccrb setup:
-* https://github.com/thewoolleyman/cinabox/tree/master/README.txt
-
-# This is not yet properly supported by RubyGems...
-# * Configure RubyGems to not require root access for gem installation
-# $ vi ~/.profile
-# # add this line at bottom:
-# PATH="$HOME/.gem/ruby/1.8/bin:$PATH"
-# $ sudo vi /etc/init.d/cruise
-# # edit the start_cruise line to source CRUISE_USER/.profile:
-# start_cruise "cd #{CRUISE_HOME} && source /home/#{CRUISE_USER}/.profile && ./cruise start -d"
-# $ vi ~/.gemrc
-# # add these lines:
-# ---
-# gemhome: /home/ci/.gem/ruby/1.8
-# gempath:
-# - /home/ci/.gem/ruby/1.8
-
-* If you did not configure no-root-gem installation via ~/.gemrc as shown above, then allow no-password sudo for gem installation:
-$ sudo visudo
-# add this line to bottom:
-ci ALL=(ALL) NOPASSWD: ALL
-
-* Start ccrb via init script and check for default homepage at port 3333
-
-* Install/setup nginx:
-$ sudo aptitude install nginx
-$ sudo vi /etc/nginx/sites-available/default
-# Add the following entry at the top of the file above the 'server {' line:
-upstream mongrel {
- server 127.0.0.1:3333;
-}
-
-# Change server_name entry to match server name
-
-# replace the contents of the root 'location / {}' block with the following entries:
- proxy_pass http://mongrel;
- proxy_redirect off;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Client-Verify SUCCESS;
- proxy_read_timeout 65;
-
-# also comment default locations for /doc and /images
-$ sudo /etc/init.d/nginx start
-
-* Add project to cruise (It will still fail until everything is set up):
-$ cd ~/ccrb
-$ ./cruise add rails -s git -r git://github.com/rails/rails.git # or the URI of your branch
-
-* Copy and configure cruise site config file:
-$ cp ~/.cruise/projects/rails/work/ci/site_config.rb ~/.cruise/site_config.rb
-# Edit ~/.cruise/site_config.rb as desired, for example:
-ActionMailer::Base.smtp_settings = {
- :address => "localhost",
- :domain => "ci.yourdomain.com",
-}
-Configuration.dashboard_refresh_interval = 60.seconds
-Configuration.dashboard_url = 'http://ci.yourdomain.com/'
-Configuration.serialize_builds = true
-Configuration.serialized_build_timeout = 1.hours
-BuildReaper.number_of_builds_to_keep = 100
-
-* Copy and configure cruise project config file
-$ cp ~/.cruise/projects/rails/work/ci/cruise_config.rb ~/.cruise/projects/rails
-$ vi ~/.cruise/projects/rails/cruise_config.rb:
-# Edit ~/.cruise/projects/rails/cruise_config.rb as desired, for example:
-Project.configure do |project|
- project.build_command = 'ruby ci/ci_build.rb'
- project.email_notifier.emails = ['recipient@yourdomain.com']
- project.email_notifier.from = 'sender@yourdomain.com'
-end
-
-* Set up mysql
-$ sudo aptitude install mysql-server-5.0 libmysqlclient-dev
-# no password for mysql root user
-
-* setup sqlite 3
-$ sudo aptitude install sqlite3 libsqlite3-dev
-# Note: there's some installation bugs with sqlite3-ruby 1.2.2 gem file permissions:
-# http://www.icoretech.org/2008/07/06/no-such-file-to-load-sqlite3-database
-# cd /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.2 && sudo find . -perm 0662 -exec chmod 664 {} \;
-
-* setup postgres
-$ sudo aptitude install postgresql postgresql-server-dev-8.3
-$ sudo su - postgres -c 'createuser -s ci'
-
-* Install fcgi libraries
-$ sudo apt-get install libfcgi-dev
-
-* Install memcached and start for first time (should start on reboot automatically)
-$ sudo aptitude install memcached
-$ sudo /etc/init.d/memcached start
-
-* Install and run GemInstaller to get all dependency gems
-$ sudo gem install geminstaller
-$ cd ~/.cruise/projects/rails/work
-$ sudo geminstaller --config=ci/geminstaller.yml # turn up debugging with these options: --geminstaller-output=all --rubygems-output=all
-
-* Create ActiveRecord test databases for mysql
-$ mysql -uroot -e 'grant all on *.* to rails@localhost;'
-$ mysql -urails -e 'create database activerecord_unittest;'
-$ mysql -urails -e 'create database activerecord_unittest2;'
-
-* Create ActiveRecord test databases for postgres
-# cd to rails activerecord dir
-$ rake postgresql:build_databases
-
-* Reboot and make sure everything is working
-$ sudo shutdown -r now
-$ http://ci.yourdomain.com
diff --git a/ci/cruise_config.rb b/ci/cruise_config.rb
deleted file mode 100644
index b64cd8aaea..0000000000
--- a/ci/cruise_config.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-Project.configure do |project|
- project.build_command = 'sudo gem update --system && ruby ci/ci_build.rb'
- project.email_notifier.from = 'rails-ci@wyeworks.com'
-
- # project.campfire_notifier.account = 'rails'
- # project.campfire_notifier.token = ''
- # project.campfire_notifier.room = 'Rails 3'
- # project.campfire_notifier.ssl = true
-end
diff --git a/ci/site.css b/ci/site.css
deleted file mode 100644
index e771c5d1fd..0000000000
--- a/ci/site.css
+++ /dev/null
@@ -1,13 +0,0 @@
-/* this is a copy of /home/ci/.cruise/site.css, please make any changes to it there */
-
-/* this is a copy of /home/ci/.cruise/site.css, please make any changes to it there */
-
-/* if you'd like to add custom styles to cruise, add them here */
-/* the following will make successful builds green */
-a.success, a.success:visited {
- color: #0A0;
-}
-
-.build_success {
- background-image: url(/images/green_gradient.png);
-}
diff --git a/ci/site_config.rb b/ci/site_config.rb
deleted file mode 100644
index f9db39ed57..0000000000
--- a/ci/site_config.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# site_config.rb contains examples of various configuration options for the local installation
-# of CruiseControl.rb.
-
-# YOU MUST RESTART YOUR CRUISE CONTROL SERVER FOR ANY CHANGES MADE HERE TO TAKE EFFECT!!!
-
-# EMAIL NOTIFICATION
-# ------------------
-
-# CruiseControl.rb can notify you about build status via email. It uses the Action Mailer component of Ruby on Rails
-# framework. Obviously, Action Mailer needs to know how to send out email messages.
-# If you have an SMTP server on your network, and it needs no authentication, write this in your site_config.rb:
-#
-ActionMailer::Base.smtp_settings = {
- :address => "localhost",
- :domain => "ci.rubyonrails.org",
-}
-#
-# If you have no SMTP server at hand, you can configure email notification to use GMail SMTP server, as follows
-# (of course, you'll need to create a GMail account):
-#
-# ActionMailer::Base.smtp_settings = {
-# :address => "smtp.gmail.com",
-# :port => 587,
-# :domain => "yourdomain.com",
-# :authentication => :plain,
-# :user_name => "yourgmailaccount",
-# :password => "yourgmailpassword"
-# }
-#
-# The same approach works for other SMTP servers thet require authentication. Note that GMail's SMTP server runs on a
-# non-standard port 587 (standard port for SMTP is 25).
-#
-# For further details about configuration of outgoing email, see Ruby On Rails documentation for ActionMailer::Base.
-
-# Other site-wide options are available through Configuration class:
-
-# Change how often CC.rb pings Subversion for new requests. Default is 10.seconds, which should be OK for a local
-# SVN repository, but probably isn't very polite for a public repository, such as RubyForge. This can also be set for
-# each project individually, through project.scheduler.polling_interval option:
-# Configuration.default_polling_interval = 1.minute
-
-# How often the dashboard page refreshes itself. If you have more than 10-20 dashboards open,
-# it is advisable to set it to something higher than the default 5 seconds:
-Configuration.dashboard_refresh_interval = 60.seconds
-
-# Site-wide setting for the email "from" field. This can also be set on per-project basis,
-# through project.email.notifier.from attribute
-Configuration.email_from = 'rails-ci@wyeworks.com'
-
-# Root URL of the dashboard application. Setting this attribute allows various notifiers to include a link to the
-# build page in the notification message.
-Configuration.dashboard_url = 'http://rails-ci.wyeworks.com/'
-
-# If you don't want to allow triggering builds through dashboard Build Now button. Useful when you host CC.rb as a
-# public web site (such as http://cruisecontrolrb.thoughtworks.com/projects - try clicking on Build Now button there
-# and see what happens):
-Configuration.disable_build_now = true
-
-# If you want to only allow one project to build at a time, uncomment this line
-# by default, cruise allows multiple projects to build at a time
-Configuration.serialize_builds = true
-
-# Amount of time a project will wait to build before failing when build serialization is on
-Configuration.serialized_build_timeout = 3.hours
-
-# To delete build when there are more than a certain number present, uncomment this line - it will make the dashboard
-# perform better
-BuildReaper.number_of_builds_to_keep = 100
-
-# any files that you'd like to override in cruise, keep in ~/.cruise, and copy over when this file is loaded like this
-site_css = CRUISE_DATA_ROOT + "/site.css"
-FileUtils.cp site_css, Rails.root + "/public/stylesheets/site.css" if File.exists? site_css
diff --git a/ci/travis.rb b/ci/travis.rb
new file mode 100755
index 0000000000..8087c72f90
--- /dev/null
+++ b/ci/travis.rb
@@ -0,0 +1,142 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+commands = [
+ 'mysql -e "create database activerecord_unittest;"',
+ 'mysql -e "create database activerecord_unittest2;"',
+ 'psql -c "create database activerecord_unittest;" -U postgres',
+ 'psql -c "create database activerecord_unittest2;" -U postgres'
+]
+
+commands.each do |command|
+ system("#{command} > /dev/null 2>&1")
+end
+
+class Build
+ MAP = {
+ 'railties' => 'railties',
+ 'ap' => 'actionpack',
+ 'am' => 'actionmailer',
+ 'amo' => 'activemodel',
+ 'ares' => 'activeresource',
+ 'as' => 'activesupport',
+ 'ar' => 'activerecord'
+ }
+
+ attr_reader :component, :options
+
+ def initialize(component, options = {})
+ @component = component
+ @options = options
+ end
+
+ def run!(options = {})
+ self.options.update(options)
+ Dir.chdir(dir) do
+ announce(heading)
+ ENV['IM'] = identity_map?.inspect
+ rake(*tasks)
+ end
+ end
+
+ def announce(heading)
+ puts "\n\e[1;33m[Travis CI] #{heading}\e[m\n"
+ end
+
+ def heading
+ heading = [gem]
+ heading << "with #{adapter} IM #{identity_map? ? 'enabled' : 'disabled'}" if activerecord?
+ heading << "in isolation" if isolated?
+ heading.join(' ')
+ end
+
+ def tasks
+ if activerecord?
+ ['mysql:rebuild_databases', "#{adapter}:#{'isolated_' if isolated?}test"]
+ else
+ ["test#{':isolated' if isolated?}"]
+ end
+ end
+
+ def key
+ key = [gem]
+ key << adapter if activerecord?
+ key << 'IM' if identity_map?
+ key << 'isolated' if isolated?
+ key.join(':')
+ end
+
+ def activerecord?
+ gem == 'activerecord'
+ end
+
+ def identity_map?
+ options[:identity_map]
+ end
+
+ def isolated?
+ options[:isolated]
+ end
+
+ def gem
+ MAP[component.split(':').first]
+ end
+ alias :dir :gem
+
+ def adapter
+ component.split(':').last
+ end
+
+ def rake(*tasks)
+ tasks.each do |task|
+ cmd = "bundle exec rake #{task}"
+ puts "Running command: #{cmd}"
+ return false unless system(cmd)
+ end
+ true
+ end
+end
+
+results = {}
+
+ENV['GEM'].split(',').each do |gem|
+ [false, true].each do |isolated|
+ next if gem == 'railties' && isolated
+
+ build = Build.new(gem, :isolated => isolated)
+ results[build.key] = build.run!
+
+ if build.activerecord?
+ build.options[:identity_map] = true
+ results[build.key] = build.run!
+ end
+ end
+end
+
+# puts
+# puts "Build environment:"
+# puts " #{`cat /etc/issue`}"
+# puts " #{`uname -a`}"
+# puts " #{`ruby -v`}"
+# puts " #{`mysql --version`}"
+# # puts " #{`pg_config --version`}"
+# puts " SQLite3: #{`sqlite3 -version`}"
+# `gem env`.each_line {|line| print " #{line}"}
+# puts " Bundled gems:"
+# `bundle show`.each_line {|line| print " #{line}"}
+# puts " Local gems:"
+# `gem list`.each_line {|line| print " #{line}"}
+
+failures = results.select { |key, value| value == false }
+
+if failures.empty?
+ puts
+ puts "Rails build finished sucessfully"
+ exit(true)
+else
+ puts
+ puts "Rails build FAILED"
+ puts "Failed components: #{failures.map { |component| component.first }.join(', ')}"
+ exit(false)
+end
diff --git a/load_paths.rb b/load_paths.rb
index 590af69330..17f5ce180d 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,8 +1,4 @@
# bust gem prelude
-if defined? Gem
- gem 'bundler'
-else
- require 'rubygems'
-end
+require 'rubygems' unless defined? Gem
require 'bundler'
-Bundler.setup
+Bundler.setup \ No newline at end of file
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index a9b4deb551..6ed76974b4 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.2.0 (unreleased)*
+* Added destroy alias to Rails engines. [Guillermo Iguaran]
+
+* Added destroy alias for Rails command line. This allows the following: `rails d model post`. [Andrey Ognevsky]
+
* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim]
* Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran]
@@ -8,6 +12,19 @@
*Rails 3.1.0 (unreleased)*
+* The default database schema file is written as UTF-8. [Aaron Patterson]
+
+* Generated apps with --dev or --edge flags depend on git versions of
+sass-rails and coffee-rails. [Santiago Pastorino]
+
+* Rack::Sendfile middleware is used only if x_sendfile_header is present. [Santiago Pastorino]
+
+* Add JavaScript Runtime name to the Rails Info properties. [DHH]
+
+* Make pp enabled by default in Rails console. [Akira Matsuda]
+
+* Add alias `r` for rails runner. [Jordi Romero]
+
* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim]
* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim]
diff --git a/railties/README.rdoc b/railties/README.rdoc
index 0457227473..eb7ed961e3 100644
--- a/railties/README.rdoc
+++ b/railties/README.rdoc
@@ -11,7 +11,7 @@ Railties is responsible to glue all frameworks together. Overall, it:
== Download
-The latest version of Railties can be installed with Rubygems:
+The latest version of Railties can be installed with RubyGems:
* gem install railties
diff --git a/railties/guides/assets/images/rails_welcome.png b/railties/guides/assets/images/rails_welcome.png
index 0e02cf5a8c..f2aa210d19 100644
--- a/railties/guides/assets/images/rails_welcome.png
+++ b/railties/guides/assets/images/rails_welcome.png
Binary files differ
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb
index 14d671c8f3..4682ead66e 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/railties/guides/rails_guides/generator.rb
@@ -199,50 +199,10 @@ module RailsGuides
end
def textile(body, lite_mode=false)
- # If the issue with notextile is fixed just remove the wrapper.
- with_workaround_for_notextile(body) do |body|
- t = RedCloth.new(body)
- t.hard_breaks = false
- t.lite_mode = lite_mode
- t.to_html(:notestuff, :plusplus, :code, :tip)
- end
- end
-
- # For some reason the notextile tag does not always turn off textile. See
- # LH ticket of the security guide (#7). As a temporary workaround we deal
- # with code blocks by hand.
- def with_workaround_for_notextile(body)
- code_blocks = []
-
- body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
- brush = case $1
- when 'ruby', 'sql', 'plain'
- $1
- when 'erb'
- 'ruby; html-script: true'
- when 'html'
- 'xml' # html is understood, but there are .xml rules in the CSS
- else
- 'plain'
- end
-
- code_blocks.push(<<HTML)
-<notextile>
-<div class="code_container">
-<pre class="brush: #{brush}; gutter: false; toolbar: false">
-#{ERB::Util.h($2).strip}
-</pre>
-</div>
-</notextile>
-HTML
- "\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n"
- end
-
- body = yield body
-
- body.gsub(%r{<p>dirty_workaround_for_notextile_(\d+)</p>}) do |_|
- code_blocks[$1.to_i]
- end
+ t = RedCloth.new(body)
+ t.hard_breaks = false
+ t.lite_mode = lite_mode
+ t.to_html(:notestuff, :plusplus, :code)
end
def warn_about_broken_links(html)
diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb
index 352c5e91dd..4677fae504 100644
--- a/railties/guides/rails_guides/textile_extensions.rb
+++ b/railties/guides/rails_guides/textile_extensions.rb
@@ -3,23 +3,24 @@ require 'active_support/core_ext/object/inclusion'
module RailsGuides
module TextileExtensions
def notestuff(body)
- body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*)$/) do |m|
- css_class = $1.downcase
- css_class = 'warning' if css_class.in?(['caution', 'important'])
-
- result = "<div class='#{css_class}'><p>"
- result << $2.strip
- result << '</p></div>'
- result
- end
- end
-
- def tip(body)
- body.gsub!(/^TIP[.:](.*)$/) do |m|
- result = "<div class='info'><p>"
- result << $1.strip
- result << '</p></div>'
- result
+ # The following regexp detects special labels followed by a
+ # paragraph, perhaps at the end of the document.
+ #
+ # It is important that we do not eat more than one newline
+ # because formatting may be wrong otherwise. For example,
+ # if a bulleted list follows the first item is not rendered
+ # as a list item, but as a paragraph starting with a plain
+ # asterisk.
+ body.gsub!(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*?)(\n(?=\n)|\Z)/m) do |m|
+ css_class = case $1
+ when 'CAUTION', 'IMPORTANT'
+ 'warning'
+ when 'TIP'
+ 'info'
+ else
+ $1.downcase
+ end
+ %Q(<div class="#{css_class}"><p>#{$2.strip}</p></div>)
end
end
@@ -32,11 +33,30 @@ module RailsGuides
body.gsub!('<plus>', '+')
end
+ def brush_for(code_type)
+ case code_type
+ when 'ruby', 'sql', 'plain'
+ code_type
+ when 'erb'
+ 'ruby; html-script: true'
+ when 'html'
+ 'xml' # html is understood, but there are .xml rules in the CSS
+ else
+ 'plain'
+ end
+ end
+
def code(body)
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
- es = ERB::Util.h($2)
- css_class = $1.in?(['erb', 'shell']) ? 'html' : $1
- %{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>}
+ <<HTML
+<notextile>
+<div class="code_container">
+<pre class="brush: #{brush_for($1)}; gutter: false; toolbar: false">
+#{ERB::Util.h($2).strip}
+</pre>
+</div>
+</notextile>
+HTML
end
end
end
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index fbb684978a..d22c76dd81 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -59,12 +59,12 @@ The +config.gem+ method is gone and has been replaced by using +bundler+ and a +
h4. Upgrade Process
-To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/jm/rails_upgrade has been created to automate part of it.
+To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of it.
Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:
<shell>
-$ ruby script/plugin install git://github.com/jm/rails_upgrade.git
+$ ruby script/plugin install git://github.com/rails/rails_upgrade.git
</shell>
You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin
diff --git a/railties/guides/source/3_1_release_notes.textile b/railties/guides/source/3_1_release_notes.textile
new file mode 100644
index 0000000000..087926f98d
--- /dev/null
+++ b/railties/guides/source/3_1_release_notes.textile
@@ -0,0 +1,429 @@
+h2. Ruby on Rails 3.1 Release Notes
+
+Highlights in Rails 3.1:
+
+* Streaming
+* Reversible Migrations
+* Assets Pipeline
+* jQuery as the default JavaScript library
+
+This release notes cover the major changes, but don't include every little bug fix and change. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/master in the main Rails repository on GitHub.
+
+endprologue.
+
+h3. Upgrading to Rails 3.1
+
+If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3 in case you haven't and make sure your application still runs as expected before attempting to update to Rails 3.1. Then take heed of the following changes:
+
+h4. Rails 3.1 requires at least Ruby 1.8.7
+
+Rails 3.1 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.1 is also compatible with Ruby 1.9.2.
+
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x jump on 1.9.2 for smooth sailing.
+
+h3. Creating a Rails 3.1 application
+
+<shell>
+# You should have the 'rails' rubygem installed
+$ rails new myapp
+$ cd myapp
+</shell>
+
+h4. Vendoring Gems
+
+Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":https://github.com/carlhuda/bundler gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
+
+More information: - "bundler homepage":http://gembundler.com
+
+h4. Living on the Edge
+
++Bundler+ and +Gemfile+ makes freezing your Rails application easy as pie with the new dedicated +bundle+ command. If you want to bundle straight from the Git repository, you can pass the +--edge+ flag:
+
+<shell>
+$ rails new myapp --edge
+</shell>
+
+If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
+
+<shell>
+$ ruby /path/to/rails/bin/rails new myapp --dev
+</shell>
+
+h3. Rails Architectural Changes
+
+h4. Assets Pipeline
+
+The major change in Rails 3.1 is the Assets Pipeline. It makes CSS and JavaScript first-class code citizens and enables proper organization, including use in plugins and engines.
+
+The assets pipeline is powered by "Sprockets":https://github.com/sstephenson/sprockets and is covered in the "Asset Pipeline":asset_pipeline.html guide.
+
+h4. HTTP Streaming
+
+HTTP Streaming is another change that is new in Rails 3.1. This lets the browser download your stylesheets and JavaScript files while the server is still generating the response. This requires Ruby 1.9.2, is opt-in and requires support from the web server as well, but the popular combo of nginx and unicorn is ready to take advantage of it.
+
+h4. Default JS library is now jQuery
+
+jQuery is the default JavaScript library that ships with Rails 3.1. But if you use Prototype, it's simple to switch.
+
+<shell>
+$ rails new myapp -j prototype
+</shell>
+
+h4. Identity Map
+
+Active Record has an Identity Map in Rails 3.1. An identity map keeps previously instantiated records and returns the object associated with the record if accessed again. The identity map is created on a per-request basis and is flushed at request completion.
+
+Rails 3.1 comes with the identity map turned off by default.
+
+h3. Railties
+
+* jQuery is the new default JavaScript library.
+
+* jQuery and Prototype are no longer vendored and is provided from now on by the jquery-rails and prototype-rails gems.
+
+* The application generator accepts an option +-j+ which can be an arbitrary string. If passed "foo", the gem "foo-rails" is added to the +Gemfile+, and the application JavaScript manifest requires "foo" and "foo_ujs". Currently only "prototype-rails" and "jquery-rails" exist and provide those files via the asset pipeline.
+
+* Generating an application or a plugin runs +bundle install+ unless +--skip-gemfile+ or +--skip-bundle+ is specified.
+
+* The controller and resource generators will now automatically produce asset stubs (this can be turned off with +--skip-assets+). These stubs will use CoffeeScript and Sass, if those libraries are available.
+
+* Scaffold and app generators use the Ruby 1.9 style hash when running on Ruby 1.9. To generate old style hash, +--old-style-hash+ can be passed.
+
+* Scaffold controller generator creates format block for JSON instead of XML.
+
+* Active Record logging is directed to STDOUT and shown inline in the console.
+
+* Added +config.force_ssl+ configuration which loads <tt>Rack::SSL</tt> middleware and force all requests to be under HTTPS protocol.
+
+* Added +rails plugin new+ command which generates a Rails plugin with gemspec, tests and a dummy application for testing.
+
+* Added <tt>Rack::Etag</tt> and <tt>Rack::ConditionalGet</tt> to the default middleware stack.
+
+* Added <tt>Rack::Cache</tt> to the default middleware stack.
+
+* Engines received a major update - You can mount them at any path, enable assets, run generators etc.
+
+h3. Action Pack
+
+h4. Action Controller
+
+* A warning is given out if the CSRF token authenticity cannot be verified.
+
+* Specify +force_ssl+ in a controller to force the browser to transfer data via HTTPS protocol on that particular controller. To limit to specific actions, +:only+ or +:except+ can be used.
+
+* Sensitive query string parameters specified in <tt>config.filter_parameters</tt> will now be filtered out from the request paths in the log.
+
+* URL parameters which return +nil+ for +to_param+ are now removed from the query string.
+
+* Added <tt>ActionController::ParamsWrapper</tt> to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default. This can be customized in <tt>config/initializers/wrap_parameters.rb</tt>.
+
+* Added <tt>config.action_controller.include_all_helpers</tt>. By default <tt>helper :all</tt> is done in <tt>ActionController::Base</tt>, which includes all the helpers by default. Setting +include_all_helpers+ to +false+ will result in including only application_helper and the helper corresponding to controller (like foo_helper for foo_controller).
+
+* +url_for+ and named url helpers now accept +:subdomain+ and +:domain+ as options.
+
+* Added +Base.http_basic_authenticate_with+ to do simple http basic authentication with a single class method call.
+
+<ruby>
+class PostsController < ApplicationController
+ USER_NAME, PASSWORD = "dhh", "secret"
+
+ before_filter :authenticate, :except => [ :index ]
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+
+ private
+ def authenticate
+ authenticate_or_request_with_http_basic do |user_name, password|
+ user_name == USER_NAME && password == PASSWORD
+ end
+ end
+end
+</ruby>
+
+..can now be written as
+
+<ruby>
+class PostsController < ApplicationController
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+end
+</ruby>
+
+* Added streaming support, you can enable it with:
+
+<ruby>
+class PostsController < ActionController::Base
+ stream
+end
+</ruby>
+
+You can restrict it to some actions by using +:only+ or +:except+. Please read the docs at "<tt>ActionController::Streaming</tt>":http://edgeapi.rubyonrails.org/classes/ActionController/Streaming.html for more information.
+
+* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused.
+
+h4. Action Dispatch
+
+* <tt>config.action_dispatch.x_sendfile_header</tt> now defaults to +nil+ and <tt>config/environments/production.rb</tt> doesn't set any particular value for it. This allows servers to set it through <tt>X-Sendfile-Type</tt>.
+
+* <tt>ActionDispatch::MiddlewareStack</tt> now uses composition over inheritance and is no longer an array.
+
+* Added <tt>ActionDispatch::Request.ignore_accept_header</tt> to ignore accept headers.
+
+* Added <tt>Rack::Cache</tt> to the default stack.
+
+* Moved etag responsibility from <tt>ActionDispatch::Response</tt> to the middleware stack.
+
+* Rely on <tt>Rack::Session</tt> stores API for more compatibility across the Ruby world. This is backwards incompatible since <tt>Rack::Session</tt> expects <tt>#get_session</tt> to accept four arguments and requires <tt>#destroy_session</tt> instead of simply <tt>#destroy</tt>.
+
+* Template lookup now searches further up in the inheritance chain.
+
+h4. Action View
+
+* Added an +:authenticity_token+ option to +form_tag+ for custom handling or to omit the token by passing <tt>:authenticity_token => false</tt>.
+
+* Created <tt>ActionView::Renderer</tt> and specified an API for <tt>ActionView::Context</tt>.
+
+* In place +SafeBuffer+ mutation is prohibited in Rails 3.1.
+
+* Added HTML5 +button_tag+ helper.
+
+* +file_field+ automatically adds <tt>:multipart => true</tt> to the enclosing form.
+
+* Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a +:data+ hash of options:
+
+<plain>
+tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
+# => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
+</plain>
+
+Keys are dasherized. Values are JSON-encoded, except for strings and symbols.
+
+* +csrf_meta_tag+ is renamed to +csrf_meta_tags+ and aliases +csrf_meta_tag+ for backwards compatibility.
+
+* The old template handler API is deprecated and the new API simply requires a template handler to respond to call.
+
+* rhtml and rxml are finally removed as template handlers.
+
+* <tt>config.action_view.cache_template_loading</tt> is brought back which allows to decide whether templates should be cached or not.
+
+* The submit form helper does not generate an id "object_name_id" anymore.
+
+* Allows <tt>FormHelper#form_for</tt> to specify the +:method+ as a direct option instead of through the +:html+ hash. <tt>form_for(==@==post, remote: true, method: :delete)</tt> instead of <tt>form_for(==@==post, remote: true, html: { method: :delete })</tt>.
+
+* Provided <tt>JavaScriptHelper#j()</tt> as an alias for <tt>JavaScriptHelper#escape_javascript()</tt>. This supersedes the <tt>Object#j()</tt> method that the JSON gem adds within templates using the JavaScriptHelper.
+
+* Allows AM/PM format in datetime selectors.
+
+* +auto_link+ has been removed from Rails and extracted into the "rails_autolink gem":https://github.com/tenderlove/rails_autolink
+
+h3. Active Record
+
+* Added a class method <tt>pluralize_table_names</tt> to singularize/pluralize table names of individual models. Previously this could only be set globally for all models through <tt>ActiveRecord::Base.pluralize_table_names</tt>.
+<ruby>
+class User < ActiveRecord::Base
+ self.pluralize_table_names = false
+end
+</ruby>
+
+* Added block setting of attributes to singular associations. The block will get called after the instance is initialized.
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :account
+end
+
+user.build_account{ |a| a.credit_limit => 100.0 }
+</ruby>
+
+* Added <tt>ActiveRecord::Base.attribute_names</tt> to return a list of attribute names. This will return an empty array if the model is abstract or the table does not exist.
+
+* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0.
+
+* <tt>ActiveRecord#new</tt>, <tt>ActiveRecord#create</tt> and <tt>ActiveRecord#update_attributes</tt> all accept a second hash as an option that allows you to specify which role to consider when assigning attributes. This is built on top of Active Model's new mass assignment capabilities:
+
+<ruby>
+class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+end
+
+Post.new(params[:post], :as => :admin)
+</ruby>
+
+* +default_scope+ can now take a block, lambda, or any other object which responds to call for lazy evaluation.
+
+* Default scopes are now evaluated at the latest possible moment, to avoid problems where scopes would be created which would implicitly contain the default scope, which would then be impossible to get rid of via Model.unscoped.
+
+* PostgreSQL adapter only supports PostgreSQL version 8.2 and higher.
+
+* +ConnectionManagement+ middleware is changed to clean up the connection pool after the rack body has been flushed.
+
+* Added an +update_column+ method on Active Record. This new method updates a given attribute on an object, skipping validations and callbacks. It is recommended to use +update_attribute+ unless you are sure you do not want to execute any callback, including the modification of the +updated_at+ column. It should not be called on new records.
+
+* Associations with a +:through+ option can now use any association as the through or source association, including other associations which have a +:through+ option and +has_and_belongs_to_many+ associations.
+
+* The configuration for the current database connection is now accessible via <tt>ActiveRecord::Base.connection_config</tt>.
+
+* limits and offsets are removed from COUNT queries unless both are supplied.
+<ruby>
+People.limit(1).count # => 'SELECT COUNT(*) FROM people'
+People.offset(1).count # => 'SELECT COUNT(*) FROM people'
+People.limit(1).offset(1).count # => 'SELECT COUNT(*) FROM people LIMIT 1 OFFSET 1'
+</ruby>
+
+* <tt>ActiveRecord::Associations::AssociationProxy</tt> has been split. There is now an +Association+ class (and subclasses) which are responsible for operating on associations, and then a separate, thin wrapper called +CollectionProxy+, which proxies collection associations. This prevents namespace pollution, separates concerns, and will allow further refactorings.
+
+* Singular associations (+has_one+, +belongs_to+) no longer have a proxy and simply returns the associated record or +nil+. This means that you should not use undocumented methods such as +bob.mother.create+ - use +bob.create_mother+ instead.
+
+* Support the <tt>:dependent</tt> option on <tt>has_many :through</tt> associations. For historical and practical reasons, +:delete_all+ is the default deletion strategy employed by <tt>association.delete(*records)</tt>, despite the fact that the default strategy is +:nullify+ for regular has_many. Also, this only works at all if the source reflection is a belongs_to. For other situations, you should directly modify the through association.
+
+* The behavior of +association.destroy+ for +has_and_belongs_to_many+ and <tt>has_many :through</tt> is changed. From now on, 'destroy' or 'delete' on an association will be taken to mean 'get rid of the link', not (necessarily) 'get rid of the associated records'.
+
+* Previously, <tt>has_and_belongs_to_many.destroy(*records)</tt> would destroy the records themselves. It would not delete any records in the join table. Now, it deletes the records in the join table.
+
+* Previously, <tt>has_many_through.destroy(*records)</tt> would destroy the records themselves, and the records in the join table. [Note: This has not always been the case; previous version of Rails only deleted the records themselves.] Now, it destroys only the records in the join table.
+
+* Note that this change is backwards-incompatible to an extent, but there is unfortunately no way to 'deprecate' it before changing it. The change is being made in order to have consistency as to the meaning of 'destroy' or 'delete' across the different types of associations. If you wish to destroy the records themselves, you can do <tt>records.association.each(&:destroy)</tt>.
+
+* Add <tt>:bulk => true</tt> option to +change_table+ to make all the schema changes defined in a block using a single ALTER statement.
+
+<ruby>
+change_table(:users, :bulk => true) do |t|
+ t.string :company_name
+ t.change :birthdate, :datetime
+end
+</ruby>
+
+* Removed support for accessing attributes on a +has_and_belongs_to_many+ join table. <tt>has_many :through</tt> needs to be used.
+
+* Added a +create_association!+ method for +has_one+ and +belongs_to+ associations.
+
+* Migrations are now reversible, meaning that Rails will figure out how to reverse your migrations. To use reversible migrations, just define the +change+ method.
+<ruby>
+class MyMigration < ActiveRecord::Migration
+ def change
+ create_table(:horses) do
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+end
+</ruby>
+
+* Some things cannot be automatically reversed for you. If you know how to reverse those things, you should define +up+ and +down+ in your migration. If you define something in change that cannot be reversed, an +IrreversibleMigration+ exception will be raised when going down.
+
+* Migrations now use instance methods rather than class methods:
+<ruby>
+class FooMigration < ActiveRecord::Migration
+ def up # Not self.up
+ ...
+ end
+end
+</ruby>
+
+* Migration files generated from model and constructive migration generators (for example, add_name_to_users) use the reversible migration's +change+ method instead of the ordinary +up+ and +down+ methods.
+
+* Removed support for interpolating string SQL conditions on associations. Instead, a proc should be used.
+
+<ruby>
+has_many :things, :conditions => 'foo = #{bar}' # before
+has_many :things, :conditions => proc { "foo = #{bar}" } # after
+</ruby>
+
+Inside the proc, +self+ is the object which is the owner of the association, unless you are eager loading the association, in which case +self+ is the class which the association is within.
+
+You can have any "normal" conditions inside the proc, so the following will work too:
+<ruby>
+has_many :things, :conditions => proc { ["foo = ?", bar] }
+</ruby>
+
+* Previously +:insert_sql+ and +:delete_sql+ on +has_and_belongs_to_many+ association allowed you to call 'record' to get the record being inserted or deleted. This is now passed as an argument to the proc.
+
+* Added <tt>ActiveRecord::Base#has_secure_password</tt> (via <tt>ActiveModel::SecurePassword</tt>) to encapsulate dead-simple password usage with BCrypt encryption and salting.
+<ruby>
+# Schema: User(name:string, password_digest:string, password_salt:string)
+class User < ActiveRecord::Base
+ has_secure_password
+end
+</ruby>
+
+* When a model is generated +add_index+ is added by default for +belongs_to+ or +references+ columns.
+
+* Setting the id of a +belongs_to+ object will update the reference to the object.
+
+* <tt>ActiveRecord::Base#dup</tt> and <tt>ActiveRecord::Base#clone</tt> semantics have changed to closer match normal Ruby dup and clone semantics.
+
+* Calling <tt>ActiveRecord::Base#clone</tt> will result in a shallow copy of the record, including copying the frozen state. No callbacks will be called.
+
+* Calling <tt>ActiveRecord::Base#dup</tt> will duplicate the record, including calling after initialize hooks. Frozen state will not be copied, and all associations will be cleared. A duped record will return +true+ for <tt>new_record?</tt>, have a +nil+ id field, and is saveable.
+
+* The query cache now works with prepared statements. No changes in the applications are required.
+
+h3. Active Model
+
+* +attr_accessible+ accepts an option +:as+ to specify a role.
+
+* +InclusionValidator+, +ExclusionValidator+, and +FormatValidator+ now accepts an option which can be a proc, a lambda, or anything that respond to +call+. This option will be called with the current record as an argument and returns an object which respond to +include?+ for +InclusionValidator+ and +ExclusionValidator+, and returns a regular expression object for +FormatValidator+.
+
+* Added <tt>ActiveModel::SecurePassword</tt> to encapsulate dead-simple password usage with BCrypt encryption and salting.
+
+* <tt>ActiveModel::AttributeMethods</tt> allows attributes to be defined on demand.
+
+* Added support for selectively enabling and disabling observers.
+
+* Alternate <tt>I18n</tt> namespace lookup is no longer supported.
+
+h3. Active Resource
+
+* The default format has been changed to JSON for all requests. If you want to continue to use XML you will need to set <tt>self.format = :xml</tt> in the class. For example,
+
+<ruby>
+class User < ActiveResource::Base
+ self.format = :xml
+end
+</ruby>
+
+h3. Active Support
+
+* <tt>ActiveSupport::Dependencies</tt> now raises +NameError+ if it finds an existing constant in +load_missing_constant+.
+
+* Added a new reporting method <tt>Kernel#quietly</tt> which silences both +STDOUT+ and +STDERR+.
+
+* Added <tt>String#inquiry</tt> as a convenience method for turning a String into a +StringInquirer+ object.
+
+* Added <tt>Object#in?</tt> to test if an object is included in another object.
+
+* +LocalCache+ strategy is now a real middleware class and no longer an anonymous class.
+
+* <tt>ActiveSupport::Dependencies::ClassCache</tt> class has been introduced for holding references to reloadable classes.
+
+* <tt>ActiveSupport::Dependencies::Reference</tt> has been refactored to take direct advantage of the new +ClassCache+.
+
+* Backports <tt>Range#cover?</tt> as an alias for <tt>Range#include?</tt> in Ruby 1.8.
+
+* Added +weeks_ago+ and +prev_week+ to Date/DateTime/Time.
+
+* Added +before_remove_const+ callback to <tt>ActiveSupport::Dependencies.remove_unloadable_constants!</tt>.
+
+Deprecations:
+
+* <tt>ActiveSupport::SecureRandom</tt> is deprecated in favor of +SecureRandom+ from the Ruby standard library.
+
+h3. Credits
+
+See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
+
+Rails 3.1 Release Notes were compiled by "Vijay Dev":https://github.com/vijaydev.
diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile
index 2eaee158ff..142b9dba7e 100644
--- a/railties/guides/source/action_mailer_basics.textile
+++ b/railties/guides/source/action_mailer_basics.textile
@@ -8,7 +8,7 @@ WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not
h3. Introduction
-Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from +ActionMailer::Base+ and live in +app/mailers+. Those mailers have associated views that appear alongside controller views in +app/views+.
+Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from +ActionMailer::Base+ and live in +app/mailers+. Those mailers have associated views that appear alongside controller views in +app/views+.
h3. Sending Emails
@@ -48,10 +48,8 @@ class UserMailer < ActionMailer::Base
def welcome_email(user)
@user = user
@url = "http://example.com/login"
- mail(:to => user.email,
- :subject => "Welcome to My Awesome Site")
+ mail(:to => user.email, :subject => "Welcome to My Awesome Site")
end
-
end
</ruby>
@@ -142,17 +140,17 @@ end
This provides a much simpler implementation that does not require the registering of observers and the like.
-The method +welcome_email+ returns a Mail::Message object which can then just be told +deliver+ to send itself out.
+The method +welcome_email+ returns a <tt>Mail::Message</tt> object which can then just be told +deliver+ to send itself out.
NOTE: In previous versions of Rails, you would call +deliver_welcome_email+ or +create_welcome_email+. This has been deprecated in Rails 3.0 in favour of just calling the method name itself.
-WARNING: Sending out one email should only take a fraction of a second, if you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like delayed job.
+WARNING: Sending out an email should only take a fraction of a second, but if you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like Delayed Job.
h4. Auto encoding header values
Action Mailer now handles the auto encoding of multibyte characters inside of headers and bodies.
-If you are using UTF-8 as your character set, you do not have to do anything special, just go ahead and send in UTF-8 data to the address fields, subject, keywords, filenames or body of the email and ActionMailer will auto encode it into quoted printable for you in the case of a header field or Base64 encode any body parts that are non US-ASCII.
+If you are using UTF-8 as your character set, you do not have to do anything special, just go ahead and send in UTF-8 data to the address fields, subject, keywords, filenames or body of the email and Action Mailer will auto encode it into quoted printable for you in the case of a header field or Base64 encode any body parts that are non US-ASCII.
For more complex examples such as defining alternate character sets or self encoding text first, please refer to the Mail library.
@@ -213,7 +211,7 @@ NOTE: If you specify an encoding, Mail will assume that your content is already
h5. Making Inline Attachments
-ActionMailer 3.0 makes inline attachments, which involved a lot of hacking in pre 3.0 versions, much simpler and trivial as they should be.
+Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in pre 3.0 versions, much simpler and trivial as they should be.
* Firstly, to tell Mail to turn an attachment into an inline attachment, you just call <tt>#inline</tt> on the attachments method within your Mailer:
@@ -242,32 +240,33 @@ end
h5. Sending Email To Multiple Recipients
-It is possible to send email to one or more recipients in one email (for e.g. informing all admins of a new signup) by setting the list of emails to the <tt>:to</tt> key. The <tt>to:</tt> key however expects a string so you have join the list of recipients using a comma.
+It is possible to send email to one or more recipients in one email (for e.g. informing all admins of a new signup) by setting the list of emails to the <tt>:to</tt> key. The list of emails can be an array of email addresses or a single string with the addresses separated by commas.
<ruby>
- class AdminMailer < ActionMailer::Base
- default :to => Admin.all.map(&:email).join(", "),
- :from => "notification@example.com"
+class AdminMailer < ActionMailer::Base
+ default :to => Admin.all.map(&:email),
+ :from => "notification@example.com"
- def new_registration(user)
- @user = user
- mail(:subject => "New User Signup: #{@user.email}")
- end
+ def new_registration(user)
+ @user = user
+ mail(:subject => "New User Signup: #{@user.email}")
end
+end
</ruby>
+The same format can be used to set carbon copy (Cc:) and blind carbon copy (Bcc:) recipients, by using the <tt>:cc</tt> and <tt>:bcc</tt> keys respectively.
+
h5. Sending Email With Name
Sometimes you wish to show the name of the person instead of just their email address when they receive the email. The trick to doing that is
to format the email address in the format <tt>"Name &lt;email&gt;"</tt>.
<ruby>
- def welcome_email(user)
- @user = user
- email_with_name = "#{@user.name} <#{@user.email}>"
- mail(:to => email_with_name,
- :subject => "Welcome to My Awesome Site")
- end
+def welcome_email(user)
+ @user = user
+ email_with_name = "#{@user.name} <#{@user.email}>"
+ mail(:to => email_with_name, :subject => "Welcome to My Awesome Site")
+end
</ruby>
h4. Mailer Views
@@ -287,9 +286,7 @@ class UserMailer < ActionMailer::Base
:subject => "Welcome to My Awesome Site",
:template_path => 'notifications',
:template_name => 'another')
- end
end
-
end
</ruby>
@@ -404,7 +401,7 @@ Will put the HTML part first, and the plain text part second.
h4. Sending Emails with Attachments
-Attachments can be added by using the +attachment+ method:
+Attachments can be added by using the +attachments+ method:
<ruby>
class UserMailer < ActionMailer::Base
@@ -422,7 +419,7 @@ The above will send a multipart email with an attachment, properly nested with t
h3. Receiving Emails
-Receiving and parsing emails with Action Mailer can be a rather complex endeavour. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need to:
+Receiving and parsing emails with Action Mailer can be a rather complex endeavor. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need to:
* Implement a +receive+ method in your mailer.
@@ -459,14 +456,14 @@ h3. Action Mailer Configuration
The following configuration options are best made in one of the environment files (environment.rb, production.rb, etc...)
-|template_root|Determines the base from which template references will be made.|
-|logger|Generates information on the mailing run if available. Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.|
-|smtp_settings|Allows detailed configuration for :smtp delivery method:<ul><li>:address - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>:port - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>:domain - If you need to specify a HELO domain, you can do it here.</li><li>:user_name - If your mail server requires authentication, set the username in this setting.</li><li>:password - If your mail server requires authentication, set the password in this setting.</li><li>:authentication - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of :plain, :login, :cram_md5.</li></ul>|
-|sendmail_settings|Allows you to override options for the :sendmail delivery method.<ul><li>:location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail.</li><li>:arguments - The command line arguments to be passed to sendmail. Defaults to -i -t.</li></ul>|
-|raise_delivery_errors|Whether or not errors should be raised if the email fails to be delivered.|
-|delivery_method|Defines a delivery method. Possible values are :smtp (default), :sendmail, :file and :test.|
-|perform_deliveries|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
-|deliveries|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
+|+template_root+|Determines the base from which template references will be made.|
+|+logger+|Generates information on the mailing run if available. Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and +Log4r+ loggers.|
+|+smtp_settings+|Allows detailed configuration for <tt>:smtp</tt> delivery method:<ul><li><tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li><tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li><tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.</li><li><tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.</li><li><tt>:password</tt> - If your mail server requires authentication, set the password in this setting.</li><li><tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.</li></ul>|
+|+sendmail_settings+|Allows you to override options for the <tt>:sendmail</tt> delivery method.<ul><li><tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.</li><li><tt>:arguments</tt> - The command line arguments to be passed to sendmail. Defaults to <tt>-i -t</tt>.</li></ul>|
+|+raise_delivery_errors+|Whether or not errors should be raised if the email fails to be delivered.|
+|+delivery_method+|Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:file</tt> and <tt>:test</tt>.|
+|+perform_deliveries+|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
+|+deliveries+|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
h4. Example Action Mailer Configuration
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index 7703d6c720..5a1e8b1247 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -478,7 +478,6 @@ javascript_include_tag :monkey # =>
<script type="text/javascript" src="/javascripts/tail.js"></script>
</ruby>
-
h5. register_stylesheet_expansion
Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+.
@@ -870,7 +869,7 @@ h4. FormHelper
Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller.
-There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don‘t work with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
+There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it:
@@ -914,7 +913,7 @@ check_box("post", "validated")
h5. fields_for
-Creates a scope around a specific model object like form_for, but doesn‘t create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form:
+Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form:
<ruby>
<%= form_for @person, :url => { :action => "update" } do |person_form| %>
diff --git a/railties/guides/source/active_model_basics.textile b/railties/guides/source/active_model_basics.textile
new file mode 100644
index 0000000000..0672669dc5
--- /dev/null
+++ b/railties/guides/source/active_model_basics.textile
@@ -0,0 +1,208 @@
+h2. Active Model Basics
+
+This guide should provide you with all you need to get started using model classes. Active Model allow for Action Pack helpers to interact with non-ActiveRecord models. Active Model also helps building custom ORMs for use outside of the Rails framework.
+
+endprologue.
+
+WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+
+h3. Introduction
+
+Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below -
+
+h4. AttributeMethods
+
+AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes, which methods on the object will use them.
+
+<ruby>
+class Person
+ include ActiveModel::AttributeMethods
+
+ attribute_method_prefix 'reset_'
+ attribute_method_suffix '_highest?'
+ define_attribute_methods ['age']
+
+ attr_accessor :age
+
+private
+ def reset_attribute(attribute)
+ send("#{attribute}=", 0)
+ end
+
+ def attribute_highest?(attribute)
+ send(attribute) > 100 ? true : false
+ end
+
+end
+
+person = Person.new
+person.age = 110
+person.age_highest? # true
+person.reset_age # 0
+person.age_highest? # false
+
+</ruby>
+
+h4. Callbacks
+
+Callbacks gives Active Record style callbacks. This provides the ability to define the callbacks and those will run at appropriate time. After defining a callbacks you can wrap with before, after and around custom methods.
+
+<ruby>
+class Person
+ extend ActiveModel::Callbacks
+
+ define_model_callbacks :update
+
+ before_update :reset_me
+
+ def update
+ _run_update_callbacks do
+ # This will call when we are trying to call update on object.
+ end
+ end
+
+ def reset_me
+ # This method will call when you are calling update on object as a before_update callback as defined.
+ end
+end
+</ruby>
+
+h4. Conversion
+
+If a class defines persisted? and id methods then you can include Conversion module in that class and you can able to call Rails conversion methods to objects of that class.
+
+<ruby>
+class Person
+ include ActiveModel::Conversion
+
+ def persisted?
+ false
+ end
+
+ def id
+ nil
+ end
+end
+
+person = Person.new
+person.to_model == person #=> true
+person.to_key #=> nil
+person.to_param #=> nil
+</ruby>
+
+h4. Dirty
+
+An object becomes dirty when an object is gone through one or more changes to its attributes and not yet saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Lets consider a Person class with attributes first_name and last_name
+
+<ruby>
+require 'rubygems'
+require 'active_model'
+
+class Person
+ include ActiveModel::Dirty
+ define_attribute_methods [:first_name, :last_name]
+
+ def first_name
+ @first_name
+ end
+
+ def first_name=(value)
+ first_name_will_change!
+ @first_name = value
+ end
+
+ def last_name
+ @last_name
+ end
+
+ def last_name=(value)
+ last_name_will_change!
+ @last_name = value
+ end
+
+ def save
+ @previously_changed = changes
+ end
+
+end
+</ruby>
+
+h5. Querying object directly for its list of all changed attributes.
+
+<ruby>
+person = Person.new
+person.first_name = "First Name"
+
+person.first_name #=> "First Name"
+person.first_name = "First Name Changed"
+
+person.changed? #=> true
+
+#returns an list of fields arry which all has been changed before saved.
+person.changed #=> ["first_name"]
+
+#returns a hash of the fields that have changed with their original values.
+person.changed_attributes #=> {"first_name" => "First Name Changed"}
+
+#returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field.
+person.changes #=> {"first_name" => ["First Name","First Name Changed"]}
+</ruby>
+
+h5. Attribute based accessor methods
+
+Track whether the particular attribute has been changed or not.
+
+<ruby>
+#attr_name_changed?
+person.first_name #=> "First Name"
+
+#assign some other value to first_name attribute
+person.first_name = "First Name 1"
+
+person.first_name_changed? #=> true
+</ruby>
+
+Track what was the previous value of the attribute.
+<ruby>
+#attr_name_was accessor
+person.first_name_was #=> "First Name"
+</ruby>
+
+Track both previous and current value of the changed attribute. Returns an array if changed else returns nil
+<ruby>
+#attr_name_change
+person.first_name_change #=> ["First Name", "First Name 1"]
+person.last_name_change #=> nil
+</ruby>
+
+h4. Validations
+
+Validations module adds the ability to class objects to validate them in Active Record style.
+
+<ruby>
+class Person
+ include ActiveModel::Validations
+
+ attr_accessor :name, :email, :token
+
+ validates :name, :presence => true
+ validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/i
+ validates! :token, :presence => true
+
+end
+
+person = Person.new(:token => "2b1f325")
+person.valid? #=> false
+person.name = 'vishnu'
+person.email = 'me'
+person.valid? #=> false
+person.email = 'me@vishnuatrai.com'
+person.valid? #=> true
+person.token = nil
+person.valid? #=> raises ActiveModel::StrictValidationFailed
+</ruby>
+
+h3. Changelog
+
+* August 24, 2011: Add strict validation usage example. "Bogdan Gusiev":http://gusiev.com
+* August 5, 2011: Initial version by "Arun Agrawal":http://github.com/arunagw
diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile
index 3e46e7df9f..cab8c80866 100644
--- a/railties/guides/source/active_record_basics.textile
+++ b/railties/guides/source/active_record_basics.textile
@@ -204,7 +204,6 @@ Likewise, once retrieved an Active Record object can be destroyed which removes
user.destroy
</ruby>
-
h3. Validations
Active Record allows you to validate the state of a model before it gets written into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. You can learn more about validations in the "Active Record Validations and Callbacks guide":active_record_validations_callbacks.html#validations-overview.
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index 082f9eda7d..4e77a6e803 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -560,6 +560,7 @@ Client.where("orders_count > 10").order(:name).reverse_order
</ruby>
The SQL that would be executed:
+
<sql>
SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC
</sql>
@@ -571,6 +572,7 @@ Client.where("orders_count > 10").reverse_order
</ruby>
The SQL that would be executed:
+
<sql>
SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC
</sql>
@@ -621,8 +623,6 @@ You're then responsible for dealing with the conflict by rescuing the exception
NOTE: You must ensure that your database schema defaults the +lock_version+ column to +0+.
-<br />
-
This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
To override the name of the +lock_version+ column, +ActiveRecord::Base+ provides a class method called +set_locking_column+:
@@ -1016,6 +1016,7 @@ You can specify an exclamation point (<tt>!</tt>) on the end of the dynamic find
If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields. For example, +Client.find_by_first_name_and_locked("Ryan", true)+.
+WARNING: Up to and including Rails 3.1, when the number of arguments passed to a dynamic finder method is lesser than the number of fields, say <tt>Client.find_by_name_and_locked("Ryan")</tt>, the behavior is to pass +nil+ as the missing argument. This is *unintentional* and this behavior will be changed in Rails 3.2 to throw an +ArgumentError+.
There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 50ff1c9ff7..aba3224ba7 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -569,11 +569,50 @@ end
All validations inside of +with_options+ block will have automatically passed the condition +:if => :is_admin?+
-h3. Creating Custom Validation Methods
+h3. Performing Custom Validations
-When the built-in validation helpers are not enough for your needs, you can write your own validation methods.
+When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.
-Simply create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names.
+h4. Custom Validators
+
+Custom validators are classes that extend <tt>ActiveModel::Validator</tt>. These classes must implement a +validate+ method which takes a record as an argument and performs the validation on it. The custom validator is called using the +validates_with+ method.
+
+<ruby>
+class MyValidator < ActiveModel::Validator
+ def validate(record)
+ if record.name.starts_with? 'X'
+ record.errors[:name] << 'Need a name starting with X please!'
+ end
+ end
+end
+
+class Person
+ include ActiveModel::Validations
+ validates_with MyValidator
+end
+</ruby>
+
+The easiest way to add custom validators for validating individual attributes is with the convenient <tt>ActiveModel::EachValidator</tt>. In this case, the custom validator class must implement a +validate_each+ method which takes three arguments: record, attribute and value which correspond to the instance, the attribute to be validated and the value of the attribute in the passed instance.
+
+<ruby>
+class EmailValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
+ record.errors[attribute] << (options[:message] || "is not an email")
+ end
+ end
+end
+
+class Person < ActiveRecord::Base
+ validates :email, :presence => true, :email => true
+end
+</ruby>
+
+As shown in the example, you can also combine standard validations with your own custom validators.
+
+h4. Custom Methods
+
+You can also create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names.
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
@@ -583,13 +622,15 @@ class Invoice < ActiveRecord::Base
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
- errors.add(:expiration_date, "can't be in the past") if
- !expiration_date.blank? and expiration_date < Date.today
+ if !expiration_date.blank? and expiration_date < Date.today
+ errors.add(:expiration_date, "can't be in the past")
+ end
end
def discount_cannot_be_greater_than_total_value
- errors.add(:discount, "can't be greater than total value") if
- discount > total_value
+ if discount > total_value
+ errors.add(:discount, "can't be greater than total value")
+ end
end
end
</ruby>
@@ -710,8 +751,6 @@ class Person < ActiveRecord::Base
end
</ruby>
-
-
h4. +errors.clear+
The +clear+ method is used when you intentionally want to clear all the messages in the +errors+ collection. Of course, calling +errors.clear+ upon an invalid object won't actually make it valid: the +errors+ collection will now be empty, but the next time you call +valid?+ or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the +errors+ collection will be filled again.
@@ -758,6 +797,7 @@ h3. Displaying Validation Errors in the View
Rails maintains an official plugin that provides helpers to display the error messages of your models in your view templates. You can install it as a plugin or as a Gem.
h4. Installing as a plugin
+
<shell>
$ rails plugin install git://github.com/joelmoss/dynamic_form.git
</shell>
@@ -765,6 +805,7 @@ $ rails plugin install git://github.com/joelmoss/dynamic_form.git
h4. Installing as a Gem
Add this line in your Gemfile:
+
<ruby>
gem "dynamic_form"
</ruby>
@@ -848,7 +889,7 @@ The way form fields with errors are treated is defined by +ActionView::Base.fiel
* A string with the HTML tag
* An instance of +ActionView::Helpers::InstanceTag+.
-Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want.
+Here is a simple example where we change the Rails behavior to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want.
<ruby>
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
@@ -941,7 +982,7 @@ The +after_initialize+ callback will be called whenever an Active Record object
The +after_find+ callback will be called whenever Active Record loads a record from the database. +after_find+ is called before +after_initialize+ if both are defined.
-The +after_initialize+ and +after_find+ callbacks are a bit different from the others. They have no +before_*+ counterparts, and the only way to register them is by defining them as regular methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries.
+The +after_initialize+ and +after_find+ callbacks are a bit different from the others. They have no +before_*+ counterparts, and the only way to register them is by defining them as regular methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behavior is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries.
<ruby>
class User < ActiveRecord::Base
@@ -1102,8 +1143,9 @@ Here's an example where we create a class with an +after_destroy+ callback for a
<ruby>
class PictureFileCallbacks
def after_destroy(picture_file)
- File.delete(picture_file.filepath)
- if File.exists?(picture_file.filepath)
+ if File.exists?(picture_file.filepath)
+ File.delete(picture_file.filepath)
+ end
end
end
</ruby>
@@ -1121,8 +1163,9 @@ Note that we needed to instantiate a new +PictureFileCallbacks+ object, since we
<ruby>
class PictureFileCallbacks
def self.after_destroy(picture_file)
- File.delete(picture_file.filepath)
- if File.exists?(picture_file.filepath)
+ if File.exists?(picture_file.filepath)
+ File.delete(picture_file.filepath)
+ end
end
end
</ruby>
@@ -1172,7 +1215,7 @@ As usual, settings in +config/environments+ take precedence over those in +confi
h4. Sharing Observers
-By default, Rails will simply strip "Observer" from an observer's name to find the model it should observe. However, observers can also be used to add behaviour to more than one model, and so it's possible to manually specify the models that our observer should observe.
+By default, Rails will simply strip "Observer" from an observer's name to find the model it should observe. However, observers can also be used to add behavior to more than one model, and so it's possible to manually specify the models that our observer should observe.
<ruby>
class MailerObserver < ActiveRecord::Observer
diff --git a/railties/guides/source/active_resource_basics.textile b/railties/guides/source/active_resource_basics.textile
new file mode 100644
index 0000000000..3294227f7b
--- /dev/null
+++ b/railties/guides/source/active_resource_basics.textile
@@ -0,0 +1,124 @@
+h2. Active Resource Basics
+
+This guide should provide you with all you need to get started managing the connection between business objects and RESTful web services. It implements a way to map web-based resources to local objects with CRUD semantics.
+
+endprologue.
+
+WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+
+h3. Introduction
+
+Active Resource allows you to connect with RESTful web services. So, in Rails, Resource classes inherited from +ActiveResource::Base+ and live in +app/models+.
+
+h3. Configuration and Usage
+
+Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
+that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
+
+<ruby>
+class Person < ActiveResource::Base
+ self.site = "http://api.people.com:3000/"
+end
+</ruby>
+
+Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
+life cycle methods that operate against a persistent store.
+
+h3. Reading and Writing Data
+
+Active Resource make request over HTTP using a standard JSON format. It mirrors the RESTful routing built into Action Controller but will also work with any other REST service that properly implements the protocol.
+
+h4. Read
+
+Read requests use the GET method and expect the JSON form of whatever resource/resources is/are being requested.
+
+<ruby>
+# Find a person with id = 1
+person = Person.find(1)
+# Check if a person exists with id = 1
+Person.exists?(1) # => true
+# Get all resources of Person class
+Person.all
+</ruby>
+
+h4. Create
+
+Creating a new resource submits the JSON form of the resource as the body of the request with HTTP POST method and parse the response into Active Resource object.
+
+<ruby>
+person = Person.create(:name => 'Vishnu')
+person.id # => 1
+</ruby>
+
+h4. Update
+
+To update an existing resource, 'save' method is used. This method make a HTTP PUT request in JSON format.
+
+<ruby>
+person = Person.find(1)
+person.name = 'Atrai'
+person.save
+</ruby>
+
+h4. Delete
+
+'destroy' method makes a HTTP DELETE request for an existing resource in JSON format to delete that resource.
+
+<ruby>
+person = Person.find(1)
+person.destroy
+</ruby>
+
+h3. Validations
+
+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.
+
+h4. Validating client side resources by overriding validation methods in base class
+
+<ruby>
+class Person < ActiveResource::Base
+ self.site = "http://api.people.com:3000/"
+
+ protected
+
+ def validate
+ errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
+ end
+end
+</ruby>
+
+h4. Validating client side resources
+
+Consider a Person resource on the server requiring both a first_name and a last_name with a validates_presence_of :first_name, :last_name declaration in the model:
+
+<ruby>
+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[:last_name] # => ["can't be empty"]
+person.last_name = "Halpert"
+person.save # => true (and person is now saved to the remote service)
+</ruby>
+
+h4. Public instance methods
+
+ActiveResource::Validations have three public instance methods
+
+h5. errors()
+
+This will return errors object that holds all information about attribute error messages
+
+h5. save_with_validation(options=nil)
+
+This validates the resource with any local validations written in base class and then it will try to POST if there are no errors.
+
+h5. valid?
+
+Runs all the local validations and will return true if no errors.
+
+h3. Changelog
+
+* July 30, 2011: Initial version by "Vishnu Atrai":http://github.com/vatrai \ No newline at end of file
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index a0ed85cf01..b2436a2e68 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -78,12 +78,14 @@ The following values are considered to be blank in a Rails application:
* +nil+ and +false+,
-* strings composed only of whitespace, i.e. matching +/\A\s*\z/+,
+* strings composed only of whitespace (see note below),
* empty arrays and hashes, and
* any other object that responds to +empty?+ and it is empty.
+INFO: In Ruby 1.9 the predicate for strings uses the Unicode-aware character class <tt>[:space:]</tt>, so for example U+2029 (paragraph separator) is considered to be whitespace. In Ruby 1.8 whitespace is considered to be <tt>\s</tt> together with the ideographic space U+3000.
+
WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are *not* blank.
For example, this method from +ActionDispatch::Session::AbstractStore+ uses +blank?+ for checking whether a session key is present:
@@ -498,7 +500,7 @@ ActionController::TestCase.class_eval do
end
</ruby>
-Rails uses +alias_method_chain+ all over the code base. For example validations are added to +ActiveRecord::Base#save+ by wrapping the method that way in a separate module specialised in validations.
+Rails uses +alias_method_chain+ all over the code base. For example validations are added to +ActiveRecord::Base#save+ by wrapping the method that way in a separate module specialized in validations.
NOTE: Defined in +active_support/core_ext/module/aliasing.rb+.
@@ -862,7 +864,9 @@ end
It is shorter, and the intention more obvious.
-The macro accepts several methods:
+The method must be public in the target.
+
+The +delegate+ macro accepts several methods:
<ruby>
delegate :name, :age, :address, :twitter, :to => :profile
@@ -2067,6 +2071,30 @@ shape_types = [Circle, Square, Triangle].sample(2)
NOTE: Defined in +active_support/core_ext/array/random_access.rb+.
+h4. Adding Elements
+
+h5. +prepend+
+
+This method is an alias of <tt>Array#unshift</tt>.
+
+<ruby>
+%w(a b c d).prepend('e') # => %w(e a b c d)
+[].prepend(10) # => [10]
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/array/prepend_and_append.rb+.
+
+h5. +append+
+
+This method is an alias of <tt>Array#<<</tt>.
+
+<ruby>
+%w(a b c d).append('e') # => %w(a b c d e)
+[].append([1,2]) # => [[1,2]]
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/array/prepend_and_append.rb+.
+
h4. Options Extraction
When the last argument in a method call is a hash, except perhaps for a +&block+ argument, Ruby allows you to omit the brackets:
@@ -2694,6 +2722,18 @@ hash # => {:a => 1}
NOTE: Defined in +active_support/core_ext/hash/slice.rb+.
+h4. Extracting
+
+The method +extract!+ removes and returns the key/value pairs matching the given keys.
+
+<ruby>
+hash = {:a => 1, :b => 2}
+rest = hash.extract!(:a) # => {:a => 1}
+hash # => {:b => 2}
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/hash/slice.rb+.
+
h4. Indifferent Access
The method +with_indifferent_access+ returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver:
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
index 8b72e20c33..77f7661deb 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/railties/guides/source/ajax_on_rails.textile
@@ -24,16 +24,12 @@ h4. Standard HTML communication vs AJAX
How do 'standard' and AJAX requests differ, why does this matter for understanding AJAX on Rails (tie in for *_remote helpers, the next section)
-
-
-
-
-
h3. Built-in Rails Helpers
Rails' JavaScript framework of choice is "Prototype":http://www.prototypejs.org. Prototype is a generic-purpose JavaScript framework that aims to ease the development of dynamic web applications by offering DOM manipulation, AJAX and other JavaScript functionality ranging from utility functions to object oriented constructs. It is not specifically written for any language, so Rails provides a set of helpers to enable seamless integration of Prototype with your Rails views.
To get access to these helpers, all you have to do is to include the prototype framework in your pages - typically in your master layout, application.html.erb - like so:
+
<ruby>
javascript_include_tag 'prototype'
</ruby>
@@ -59,7 +55,6 @@ link_to_remote "Add to cart",
</ruby>
* The very first parameter, a string, is the text of the link which appears on the page.
-
* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff:
** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too.
** *:update* Specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
@@ -193,7 +188,6 @@ end
What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior).
-
h3. Testing JavaScript
JavaScript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with JavaScript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it:
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
index 50e86e05a8..3ebf0e10f1 100644
--- a/railties/guides/source/api_documentation_guidelines.textile
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -35,7 +35,7 @@ Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite databa
h3. English
-Please use American English (_color_, _center_, _modularize_, etc.). See "a list of American and British English spelling differences here":http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences.
+Please use American English (<em>color</em>, <em>center</em>, <em>modularize</em>, etc.). See "a list of American and British English spelling differences here":http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences.
h3. Example Code
@@ -78,14 +78,14 @@ The result of expressions follow them and are introduced by "# => ", vertically
If a line is too long, the comment may be placed on the next line:
<ruby>
- # label(:post, :title)
- # # => <label for="post_title">Title</label>
- #
- # label(:post, :title, "A short title")
- # # => <label for="post_title">A short title</label>
- #
- # label(:post, :title, "A short title", :class => "title_label")
- # # => <label for="post_title" class="title_label">A short title</label>
+# label(:post, :title)
+# # => <label for="post_title">Title</label>
+#
+# label(:post, :title, "A short title")
+# # => <label for="post_title">A short title</label>
+#
+# label(:post, :title, "A short title", :class => "title_label")
+# # => <label for="post_title" class="title_label">A short title</label>
</ruby>
Avoid using any printing methods like +puts+ or +p+ for that purpose.
@@ -106,7 +106,6 @@ routes.rb # NO
RAILS_ROOT/config/routes.rb # NO
</plain>
-
h3. Fonts
h4. Fixed-width Font
@@ -188,4 +187,3 @@ self.class_eval %{
h3. Changelog
* July 17, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn
-
diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile
index 563c1c79ae..4fbdda4c07 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/railties/guides/source/asset_pipeline.textile
@@ -1,6 +1,6 @@
h2. Asset Pipeline
-This guide will cover the ideology of the asset pipeline introduced in Rails 3.1.
+This guide covers the ideology of the asset pipeline introduced in Rails 3.1.
By referring to this guide you will be able to:
* Understand what the asset pipeline is and what it does
@@ -11,40 +11,37 @@ By referring to this guide you will be able to:
endprologue.
-h3. What Is The Asset Pipeline?
+h3. What is the Asset Pipeline?
-The asset pipeline provides a framework to concatenate and minify or compress Javascript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, SCSS and ERB.
+The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, SCSS and ERB.
Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 includes the +sprockets-rails+ gem, which depends on the +sprockets+ gem, by default.
By having this as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "Fast by default" strategy as outlined by DHH in his 2011 keynote at Railsconf.
-In new Rails 3.1 application the asset pipeline is enable by default. It can be disabled in +application.rb+ by putting this line inside the +Application+ class definition:
+In new Rails 3.1 application the asset pipeline is enabled by default. It can be disabled in +application.rb+ by putting this line inside the +Application+ class definition:
<plain>
- config.assets.enabled = false
+config.assets.enabled = false
</plain>
It is recommended that you use the defaults for all new apps.
-
h4. Main Features
-The first is to concatenate of assets. This is important in a production environment to reduce the number of requests that a client browser has to make to render a web page. While Rails already has a feature to concatenate these types of asset--by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+--, many people do not use it.
-
-The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS, however you can separate files or groups of files if required (see below). In production an MD5 fingerprint is inserted into each filename.
+The first feature of the pipeline is to concatenate assets. This is important in a production environment, as it reduces the number of requests that a browser must make to render a web page. While Rails already has a feature to concatenate these types of assetsi -- by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+ -- many people do not use it.
-The second feature of the pipeline is to minify or compress. For CSS this usually involves removing whitespace and comments. For Javascript more complex processes can be applied.
+The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS. However, you can separate files or groups of files if required (see below). In production, an MD5 fingerprint is inserted into each filename so that the file is cached by the web browser but can be invalidated if the fingerprint is altered.
-You can choose from a set of built in options or specify your own.
+The second feature is to minify or compress assets. For CSS, this usually involves removing whitespace and comments. For JavaScript, more complex processes can be applied. You can choose from a set of built in options or specify your own.
-The third feature is the ability to code these assets using another language, or language extension. These include SCSS or Sass for CSS, CoffeeScript for Javascript, and ERB for both.
+The third feature is the ability to code these assets using another language, or language extension. These include SCSS or Sass for CSS, CoffeeScript for JavaScript, and ERB for both.
-h4. What is fingerprinting and why should I care?
+h4. What is Fingerprinting and Why Should I Care?
-Fingerprinting is a technique where the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file.
+Fingerprinting is a technique whereby the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file.
-When a filename is unique and based on its content, http headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep there own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cachebusting_.
+When a filename is unique and based on its content, HTTP headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep their own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cachebusting_.
The most effective technique is to insert a hash of the content into the name, usually at the end. For example a CSS file +global.css+ is hashed and the filename is updated to incorporate the hash.
@@ -54,7 +51,7 @@ global.css => global-908e25f4bf641868d8683022a5b62f54.css
This is the strategy adopted by the Rails asset pipeline.
-Rails old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this:
+Rails' old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this:
<plain>
/stylesheets/global.css?1309495796
@@ -62,31 +59,33 @@ Rails old strategy was to append a query string to every asset linked with a bui
This has several disadvantages:
-1. Not all caches will cache content with a query string
-
-"Steve Souders recommends":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/, "...avoiding a querystring for cacheable resources". He found that in these case 5-20% of requests will not be cached.
-
-2. The filename can change between nodes in multi-server environments.
-
-The query string in Rails is based on the files mtime (mtime is the file modification time). When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request.
+<ol>
+ <li>
+ <strong>Not all caches will cache content with a query string</strong><br>
+ "Steve Souders recommends":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/, "...avoiding a querystring for cacheable resources". He found that in these case 5-20% of requests will not be cached.
+ </li>
+ <li>
+ <strong>The file name can change between nodes in multi-server environments.</strong><br>
+ The query string in Rails is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request.
+ </li>
+</ol>
-The other problems is that when static assets are deployed with each new release of code, the mtime of *all* these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
+The other problem is that when static assets are deployed with each new release of code, the mtime of *all* these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
-Fingerprinting avoids all these problems be ensuring filenames are consistent based on the content.
+Fingerprinting avoids all these problems by ensuring filenames are consistent based on their content.
More reading:
* "Optimize caching":http://code.google.com/speed/page-speed/docs/caching.html
* "Revving Filenames: don’t use querystring":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
-
h3. How to Use the Asset Pipeline
-In previous versions of Rails, all assets were located in subdirectories of +public+ such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem.
+In previous versions of Rails, all assets were located in subdirectories of +public+ such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory are served by the Sprockets middleware included in the sprockets gem.
-This is not to say that assets can (or should) no longer be placed in +public+. They still can be and will be served as static files by the application or web server. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served.
+This is not to say that assets can (or should) no longer be placed in +public+; they still can be and will be served as static files by the application or web server. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served.
-When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller.
+When a scaffold or controller is generated for the application, Rails also generates a JavaScript file (or CoffeeScript file if the +coffee-script+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS file if +sass-rails+ is in the +Gemfile+) for that controller.
For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as +<%= javascript_include_tag params[:controller] %>+ or +<%= stylesheet_link_tag params[:controller] %>+.
@@ -100,32 +99,72 @@ Assets can be placed inside an application in one of three locations: +app/asset
+vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins.
-All subdirectories that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and served.
+All subdirectories that exist within these three locations are added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths are looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and served.
+
+h4. Coding Links to Assets
-h4. Coding links to Assets
+To access assets, you use the same tags that you are generally familiar with:
-To access assets, we can use the same tags that we are generally familiar with:
+Sprockets does not add any new methods to require your assets, you still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+.
<erb>
- <%= image_tag "rails.png" %>
+<%= stylesheet_link_tag "application" %>
+<%= javascript_include_tag "application" %>
</erb>
-Providing that assets are enabled within our application (+config.assets.enabled+ in the current environment's file is not set to +false+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Alternatively, a file with an MD5 hash after its name such as +public/assets/rails-af27b6a414e6da00003503148be9b409.png+ will also be picked up by Sprockets. How these hashes are generated is covered in the "Production Assets":#production_assets section later on in this guide.
+In regular views you can access images in the +assets/images+ directory like this:
-Otherwise, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application.
+<erb>
+<%= image_tag "rails.png" %>
+</erb>
-Sprockets does not add any new methods to require your assets, we still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+.
+Images can be organized into directories if required, and they can be accessed by specifying the directory's name in the tag:
<erb>
- <%= stylesheet_link_tag "application" %>
- <%= javascript_include_tag "application" %>
+<%= image_tag "icons/rails.png" %>
</erb>
-These helpers (when the pipeline is on) are providing links to the compiled manifest with the specified name (or names).
+Providing that assets are enabled within your application (+config.assets.enabled+ in the current environment's file is not set to +false+), this file is served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file is served.
+
+Alternatively, a file with an MD5 hash after its name such as +public/assets/rails-af27b6a414e6da00003503148be9b409.png+ is also picked up by Sprockets. How these hashes are generated is covered in the "Production Assets":#production_assets section later on in this guide.
+
+Otherwise, Sprockets looks through the available paths until it finds a file that matches the name and then serves it, first looking in the application's assets directories and then falling back to the various engines of the application.
+
+If you want to use a "css data URI":http://en.wikipedia.org/wiki/Data_URI_scheme -- a method of embedding the image data directly into the CSS file -- you can use the +asset_data_uri+ helper.
+
+<plain>
+#logo { background: url(<%= asset_data_uri 'logo.png' %>)
+</plain>
+
+This inserts a correctly-formatted data URI into the CSS source.
+
+h5. CSS and ERB
+
+If you add an +erb+ extension to a CSS asset, making it something such as +application.css.erb+, then you can use the +asset_path+ helper in your CSS rules:
+
+<plain>
+.class{background-image:<%= asset_path 'image.png' %>}
+</plain>
+
+This writes the path to the particular asset being referenced. In this example, it would make sense to have an image in one of the asset load paths, such as +app/assets/images/image.png+, which would be referenced here. If this image is already available in +public/assets+ as a fingerprinted file, then that path is referenced.
+
+Note that the closing tag cannot be of the style +-%>+.
+
+h5. CSS and SCSS
+
+When using the asset pipeline, paths to assets must be re-written and +sass-rails+ provides +_url+ and +_path+ helpers for the following asset classes: image, font, video, audio, javascript, stylesheet.
+
+* +image_url("rails.png")+ becomes +url(/assets/rails.png)+
+* +image_path("rails.png")+ becomes +"/assets/rails.png"+.
+
+The more generic form can also be used but the asset path and class must both be specified:
+
+* +asset_url("rails.png", "image")+ becomes +url(/assets/rails.png)+
+* +asset_path("rails.png", "image")+ becomes +"/assets/rails.png"+
h4. Manifest Files and Directives
-Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ - instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time is greatly reduced.
+Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ -- instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, the load time of pages are greatly reduced as there are fewer requests to make.
For example, in the default Rails application there's a +app/assets/javascripts/application.js+ file which contains the following lines:
@@ -135,9 +174,11 @@ For example, in the default Rails application there's a +app/assets/javascripts/
//= require_tree .
</plain>
-In JavaScript files, directives begin with +//=+. In this case, the following file is using the +require+ directive and the +require_tree+ directive. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem. An identical event takes place for the +jquery_ujs+ require
+In JavaScript files, the directives begin with +//=+. In this case, the file is using the +require+ and the +require_tree+ directives. The +require+ directive is used to tell Sprockets the files that you wish to require. Here, you are requiring the files +jquery.js+ and +jquery_ujs.js+ that are available somewhere in the search path for Sprockets. You need not supply the extensions explicitly. Sprockets assumes you are requiring a +.js+ file when done from within a +.js+ file.
+
+NOTE. In Rails 3.1, the +jquery.js+ and +jquery_ujs.js+ files are located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem.
-The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. Only a path relative to the file can be specified.
+The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. Only a path relative to the file can be specified. There is also a +require_directory+ directive which includes all JavaScript files only in the directory specified (no nesting).
There's also a default +app/assets/stylesheets/application.css+ file which contains these lines:
@@ -148,13 +189,13 @@ There's also a default +app/assets/stylesheets/application.css+ file which conta
*/
</plain>
-The directives that work in the JavaScript files will also work in stylesheets, obviously including stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory.
+The directives that work in the JavaScript files also work in stylesheets, obviously including stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory.
-In this example +require_self+ is used. This will put the CSS contained within the file (if any) at the top of any other CSS in this file unless +require_self+ is specified after another +require+ directive.
+In this example +require_self+ is used. This puts the CSS contained within the file (if any) at the top of any other CSS in this file unless +require_self+ is specified after another +require+ directive.
You can have as many manifest files as you need. For example the +admin.css+ and +admin.js+ manifest could contain the JS and CSS files that are used for the admin section of an application.
-For some assets (like CSS) the compiled order is important. You can specify individual files and they will be compiled in the order specified:
+For some assets (like CSS) the compiled order is important. You can specify individual files and they are compiled in the order specified:
<plain>
/* ...
@@ -167,132 +208,161 @@ For some assets (like CSS) the compiled order is important. You can specify indi
h4. Preprocessing
-The file extensions used on an asset will determine what preprocssing will be applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file.
+The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file.
-When these files are requested, they will be processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively.
+When these files are requested, they are processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively.
-Additional layers of pre-processing can be requested by adding other extensions. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ would first be processed as ERB, then SCSS and finally served as CSS. The same applies to a JavaScript file - +app/assets/javascripts/projects.js.coffee.erb+ would be process as ERB, CoffeeScript and served as JavaScript.
+Additional layers of pre-processing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ is first processed as ERB, then SCSS and finally served as CSS. The same applies to a JavaScript file -- +app/assets/javascripts/projects.js.coffee.erb+ is processed as ERB, CoffeeScript and served as JavaScript.
-Keep in mind that the order of these pre-processors is important. For example, if we called our JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore we would run into problems.
+Keep in mind that the order of these pre-processors is important. For example, if you called your JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it is processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems.
h3. In Development
-TODO: Talk about: Rack::Cache's caching (used in dev and production. The only difference is hashing and headers).
+In the development environment assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-validate+ Cache-Control HTTP header to reduce request overhead on subsequent requests - on these the browser gets a 304 (not-modified) response.
-In the development environment assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-validate+ cache-control http header to reduce request overhead on subsequent requests - on these the browser gets a 304 (not-modified) response.
-
-If any of the files in the manifest have changed between requests, the server will respond with a new compiled file.
+If any of the files in the manifest have changed between requests, the server responds with a new compiled file.
h4. Debugging Assets
-You can put +?debug_assets=true+ or +?debug_assets=1+ at the end of a URL and Sprockets will expand the lines which load the assets. For example, if we had an +app/assets/javascripts/application.js+ file containing these lines:
+You can put +?debug_assets=true+ or +?debug_assets=1+ at the end of a URL or set +config.assets.debug+ and Sprockets expands the lines which load the assets. For example, if you had an +app/assets/javascripts/application.js+ file containing these lines:
<plain>
//= require "projects"
//= require "tickets"
</plain>
-By default, this would only render this line when used with +<%= javascript_include_tag "application" %>+ in a view or layout:
+By default, this only renders this line when used with +<%= javascript_include_tag "application" %>+ in a view or layout:
<html>
- <script src='/assets/application.js'></script>
+<script src='/assets/application.js'></script>
</html>
-When the +debug_assets+ parameter is set, this line will be expanded out into three separate lines, separating out the combined file into their parts.
+When the +debug_assets+ parameter is set, this line is expanded out into three separate lines, separating out the combined file into their parts.
<html>
- <script src='/assets/application.js'></script>
- <script src='/assets/projects.js'></script>
- <script src='/assets/tickets.js'></script>
+<script src='/assets/application.js'></script>
+<script src='/assets/projects.js'></script>
+<script src='/assets/tickets.js'></script>
</html>
This allows the individual parts of an asset to be rendered and debugged separately.
+Additionally if the +config.assets.debug+ is set to true you can debug your assets passing the +:debug+ option to the assets tags:
+
+<erb>
+<%= javascript_include_tag :application, :debug => true %>
+</erb>
+
+
+NOTE. Assets debugging is turned on by default in development and test environments. You can set +config.assets.allow_debugging+ to false to turn it off.
+
h3. In Production
In the production environment, assets are served slightly differently.
-On the first request the assets are compiled and cached as described above, however the manifest names are altered to include an MD5 hash. Files names typically will look like these:
+On the first request the assets are compiled and cached as described above, however the manifest names are altered to include an MD5 hash. Files names typically look like these:
<plain>
/assets/application-908e25f4bf641868d8683022a5b62f54.js
/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css
</plain>
-The MD5 is generated from the contents of the compiled files, and is included in the http +Content-MD5+ header.
+The MD5 is generated from the contents of the compiled files, and is included in the HTTP +Content-MD5+ header.
-Sprockets also sets the +Cache-Control+ http header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache.
+Sprockets also sets the +Cache-Control+ HTTP header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache.
This behavior is controlled by the setting of +config.action_controller.perform_caching+ setting in Rails (which is +true+ for production, +false+ for everything else). This value is propagated to Sprockets during initialization for use when action_controller is not available.
-TODO:
-describe each and the differences between:
- * Sass-rails's handy +image_url+ helpers
- * ERB pre-processing and +asset_path+
-
-h4. Precompiling assets
+h4. Precompiling Assets
Even though assets are served by Rack::Cache with far-future headers, in high traffic sites this may not be fast enough.
-Rails comes bundled with a rake task to compile the manifests to files on disc. These are located in the +public/assets+ directory where they will be served by your web server instead of the Rails application.
-
-TODO: Add section about image assets
+Rails comes bundled with a rake task to compile the manifests to files on disc. These are located in the +public/assets+ directory where they are served by your web server instead of the Rails application.
The rake task is:
-<erb>
+<plain>
rake assets:precompile
+</plain>
+
+Capistrano (v2.8.0+) has a recipe to handle this in deployment. Add the following line to +Capfile+:
+
+<erb>
+load 'deploy/assets'
</erb>
-TODO: explain where to use this with Capistrano
+This links the folder specified in +config.assets.prefix+ to +shared/assets+. If you already use this folder you'll need to write your own deployment task.
+
+It is important that this folder is shared between deployments so that remotely cached pages that reference the old compiled assets still work for the life of the cached page.
+
+The default matcher for compiling files includes +application.js+, +application.css+ and all files that do not end in +js+ or +css+:
-TODO: talk about the +config.assets.precompile+ option and the default matcher for files:
+<ruby>
+[ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
+</ruby>
+
+If you have other manifests or individual stylesheets and JavaScript files to include, you can append them to the +precompile+ array:
<erb>
-[ /\w+\.(?!js|css).+/, "application.js", "application.css" ]
+config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
</erb>
+Precompiled assets exist on the filesystem and are served directly by your webserver. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them.
+
+For Apache:
-Sprockets also creates a "gzip":http://en.wikipedia.org/wiki/Gzip (.gz) of your assets. This prevents your server from contently compressing your assets for each request. You must configure your server to use gzip compression and serve the compressed assets that will be stored in the public/assets folder. The following are some configuration blocks that you can use for common servers.
-NGINX & Apache examples?
+<plain>
+<LocationMatch "^/assets/.*$">
+ # Some browsers still send conditional-GET requests if there's a
+ # Last-Modified header or an ETag header even if they haven't
+ # reached the expiry date sent in the Expires header.
+ Header unset Last-Modified
+ Header unset ETag
+ FileETag None
+ # RFC says only cache for 1 year
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
+</LocationMatch>
+</plain>
+TODO: NGINX instructions
+When files are precompiled, Sprockets also creates a "Gzip":http://en.wikipedia.org/wiki/Gzip (.gz) version of your assets. This avoids the server having to do this for any requests; it can simply read the compressed files from disc. You must configure your server to use gzip compression and serve the compressed assets that will be stored in the public/assets folder. The following configuration options can be used:
-h3. Customizing The Pipeline
+TODO: Apache instructions
-h4. CSS
+h3. Customizing the Pipeline
-There is currently one option for processing CSS - SCSS. This Gem extends the CSS syntax and offers minification.
-The following line will enable SCSS in you project.
+h4. CSS Compression
+
+There is currently one option for compressing CSS - YUI. This Gem extends the CSS syntax and offers minification.
+
+The following line enables YUI compression, and requires the +yui-compressor+ gem.
<erb>
-config.assets.css_compressor = :scss
+config.assets.css_compressor = :yui
</erb>
-This option is for compression only and does not relate to the SCSS language extensions that apply when using the +.scss+ file extension on CSS assets.
+The +config.assets.compress+ must be set to +true+ to enable CSS compression
-h4. Javascript
+h4. JavaScript
-There are three options available to process javascript - uglifier, closure and yui.
+Possible options for JavaScript compression are +:closure+, +:uglifier+ and +:yui+. These require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems respectively.
-The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compress your code by removing white spaces and other magical things like changing your if and else statements to ternary operators when possible.
+The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compresses your code by removing white space and other magical things like changing your +if+ and +else+ statements to ternary operators where possible.
-TODO: Add detail about the other two
-
-The following line will invoke uglifier for Javascript compression.
+The following line invokes +uglifier+ for JavaScript compression.
<erb>
-config.assets.js_compressor = :uglifier
+config.assets.js_compressor = :uglifier
</erb>
+The +config.assets.compress+ must be set to +true+ to enable JavaScript compression
+h4. Using Your Own Compressor
-h4. Using your own compressor
-
-The compressor config settings for CSS and Javascript will also take an Object.
-
-This object must have a +compress+ method that takes a string as the sole argument and it must return a string.
+The compressor config settings for CSS and JavaScript also take any Object. This object must have a +compress+ method that takes a string as the sole argument and it must return a string.
<erb>
class Transformer
@@ -302,14 +372,13 @@ class Transformer
end
</erb>
-To enable this pass a +new+ Object to the config option in +application.rb+:
+To enable this, pass a +new+ Object to the config option in +application.rb+:
<erb>
config.assets.css_compressor = Transformer.new
</erb>
-
-h4. Changing the _assets_ path
+h4. Changing the _assets_ Path
The public path that Sprockets uses by default is +/assets+.
@@ -319,8 +388,26 @@ This can be changed to something else:
config.assets.prefix = "/some_other_path"
</erb>
-This is a handy option if you have any existing project (pre Rails 3.1) that already uses this path.
+This is a handy option if you have any existing project (pre Rails 3.1) that already uses this path or you wish to use this path for a new resource.
+
+h4. X-Sendfile Headers
+
+The X-Sendfile header is a directive to the server to ignore the response from the application, and instead serve the file specified in the headers. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is faster.
+
+Apache and nginx support this option which is enabled in <tt>config/environments/production.rb</tt>.
+
+<erb>
+# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+</erb>
+
+WARNING: If you are upgrading an existing application and intend to use this option, take care to paste this configuration option only into +production.rb+ (and not +application.rb+) and any other environment you define with production behavior.
+
+h3. How Caching Works
+Sprockets uses the default rails cache store to cache assets in dev and production. The only difference is file names are fingerprinted and get far-future headers in production.
+
+TODO: Add more about changing the default store.
h3. Adding Assets to Your Gems
@@ -330,6 +417,4 @@ A good example of this is the +jquery-rails+ gem which comes with Rails as the s
h3. Making Your Library or Gem a Pre-Processor
-"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh
-Tilt: https://github.com/rtomayko/tilt
-
+TODO: Registering gems on "Tilt":https://github.com/rtomayko/tilt enabling Sprockets to find them.
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index 3c2497e83a..ce4ff0389d 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -443,7 +443,7 @@ class CreateAssemblyPartJoinTable < ActiveRecord::Migration
end
</ruby>
-We pass +:id => false+ to +create_table+ because that table does not represent a model. That's required for the association to work properly. If you observe any strange behaviour in a +has_and_belongs_to_many+ association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit.
+We pass +:id => false+ to +create_table+ because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a +has_and_belongs_to_many+ association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit.
h4. Controlling Association Scope
diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile
index 252003edd0..693303950d 100644
--- a/railties/guides/source/caching_with_rails.textile
+++ b/railties/guides/source/caching_with_rails.textile
@@ -15,7 +15,7 @@ h3. Basic Caching
This is an introduction to the three types of caching techniques that Rails provides by default without the use of any third party plugins.
-To start playing with testing you'll want to ensure that +config.action_controller.perform_caching+ is set to +true+ if you're running in development mode. This flag is normally set in the corresponding +config/environments/*.rb+ and caching is disabled by default for development and test, and enabled for production.
+To start playing with caching you'll want to ensure that +config.action_controller.perform_caching+ is set to +true+, if you're running in development mode. This flag is normally set in the corresponding +config/environments/*.rb+ and caching is disabled by default for development and test, and enabled for production.
<ruby>
config.action_controller.perform_caching = true
@@ -23,9 +23,9 @@ config.action_controller.perform_caching = true
h4. Page Caching
-Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
+Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
-So, how do you enable this super-fast cache behavior? Simple, let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products
+To enable page caching, you need to use the +caches_page+ method.
<ruby>
class ProductsController < ActionController
@@ -35,11 +35,10 @@ class ProductsController < ActionController
def index
@products = Products.all
end
-
end
</ruby>
-The first time anyone requests +/products+, Rails will generate a file called +products.html+ and the webserver will then look for that file before it passes the next request for +/products+ to your Rails application.
+Let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products. The first time anyone requests +/products+, Rails will generate a file called +products.html+ and the webserver will then look for that file before it passes the next request for +/products+ to your Rails application.
By default, the page cache directory is set to +Rails.public_path+ (which is usually set to the +public+ folder) and this can be configured by changing the configuration setting +config.action_controller.page_cache_directory+. Changing the default from +public+ helps avoid naming conflicts, since you may want to put other static html in +public+, but changing this will require web server reconfiguration to let the web server know where to serve the cached files from.
@@ -104,7 +103,7 @@ INFO: Action caching runs in an after filter. Thus, invalid requests won't gener
h4. Fragment Caching
-Life would be perfect if we could get away with caching the entire contents of a page or action and serving it out to the world. Unfortunately, dynamic web applications usually build pages with a variety of components not all of which have the same caching characteristics. In order to address such a dynamically created page where different parts of the page need to be cached and expired differently Rails provides a mechanism called Fragment Caching.
+Life would be perfect if we could get away with caching the entire contents of a page or action and serving it out to the world. Unfortunately, dynamic web applications usually build pages with a variety of components not all of which have the same caching characteristics. In order to address such a dynamically created page where different parts of the page need to be cached and expired differently, Rails provides a mechanism called Fragment Caching.
Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in.
@@ -405,7 +404,6 @@ h3. Further reading
* "Scaling Rails Screencasts":http://railslab.newrelic.com/scaling-rails
-
h3. Changelog
* Feb 17, 2011: Document 3.0.0 changes to ActiveSupport::Cache
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 9e3b25d794..f6b33d283c 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -85,6 +85,8 @@ h4. +rails generate+
The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators:
+You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>.
+
<shell>
$ rails generate
Usage: rails generate GENERATOR [args] [options]
@@ -311,6 +313,8 @@ h4. +rails runner+
$ rails runner "Model.long_running_method"
</shell>
+You can also use the alias "r" to invoke the runner: <tt>rails r</tt>.
+
You can specify the environment in which the +runner+ command should operate using the +-e+ switch.
<shell>
@@ -321,6 +325,8 @@ h4. +rails destroy+
Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it.
+You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>.
+
<shell>
$ rails generate model Oops
exists app/models/
@@ -377,19 +383,22 @@ Ruby version 1.8.7 (x86_64-linux)
RubyGems version 1.3.6
Rack version 1.1
Rails version 3.1.0
+JavaScript Runtime Node.js (V8)
Active Record version 3.1.0
Action Pack version 3.1.0
Active Resource version 3.1.0
Action Mailer version 3.1.0
Active Support version 3.1.0
-Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, Rack::Sendfile, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head
+Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head
Application root /home/foobar/commandsapp
Environment development
+Database adapter sqlite3
+Database schema version 20110805173523
</shell>
h4. +assets+
-You can precompile the assets in <tt>app/assets</tt> using <tt>rake assets:precompile</tt> and remove compiled assets using <tt>rake assets:clean</tt>.
+You can precompile the assets in <tt>app/assets</tt> using <tt>rake assets:precompile</tt> and remove those compiled assets using <tt>rake assets:clean</tt>.
h4. +db+
@@ -454,13 +463,18 @@ h4. +test+
INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html
-Rails comes with a test suite called Test::Unit. It is through the use of tests that Rails itself is so stable, and the slew of people working on Rails can prove that everything works as it should.
-
-The +test:+ namespace helps in running the different tests you will (hopefully!) write.
+Rails comes with a test suite called <tt>Test::Unit</tt>. Rails owes its stability to the use of tests. The tasks available in the +test:+ namespace helps in running the different tests you will hopefully write.
h4. +tmp+
-The <tt>Rails.root/tmp</tt> directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry.
+The <tt>Rails.root/tmp</tt> directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions.
+
+The +tmp:+ namespaced tasks will help you clear the <tt>Rails.root/tmp</tt> directory:
+
+* +rake tmp:cache:clear+ clears <tt>tmp/cache</tt>.
+* +rake tmp:sessions:clear+ clears <tt>tmp/sessions</tt>.
+* +rake tmp:sockets:clear+ clears <tt>tmp/sockets</tt>.
+* +rake tmp:clear+ clears all the three: cache, sessions and sockets.
h4. Miscellaneous
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 8e6010ff79..110c04f66e 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -120,10 +120,12 @@ h4. Configuring Assets
Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful.
-* +config.assets.css_compressor+ defines the CSS compressor to use. Only supported value at the moment is +:yui+, which uses the +yui-compressor+ gem.
-
* +config.assets.enabled+ a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in +config/application.rb+.
+* +config.assets.compress+ a flag that enables the compression of compiled assets. It is explicitly set to true in +config/production.rb+.
+
+* +config.assets.css_compressor+ defines the CSS compressor to use. Only supported value at the moment is +:yui+, which uses the +yui-compressor+ gem.
+
* +config.assets.js_compressor+ defines the JavaScript compressor to use. Possible values are +:closure+, +:uglifier+ and +:yui+ which require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems respectively.
* +config.assets.paths+ contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets.
@@ -521,7 +523,7 @@ The error occurred while evaluating nil.each
*+action_view.cache_asset_ids+* Sets +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ to +false+ when Active Support loads, but only if +config.cache_classes+ is too.
-*+action_view.javascript_expansions+* Registers the expansions set up by +config.action_view.javascript_expansions+ and +config.action_view.stylesheet_expansions+ to be recognised by Action View and therefore usable in the views.
+*+action_view.javascript_expansions+* Registers the expansions set up by +config.action_view.javascript_expansions+ and +config.action_view.stylesheet_expansions+ to be recognized by Action View and therefore usable in the views.
*+action_view.set_configs+* Sets up Action View by using the settings in +config.action_view+ by +send+'ing the method names as setters to +ActionView::Base+ and passing the values through.
@@ -555,7 +557,7 @@ The error occurred while evaluating nil.each
*+set_load_path+* This initializer runs before +bootstrap_hook+. Adds the +vendor+, +lib+, all directories of +app+ and any paths specified by +config.load_paths+ to +$LOAD_PATH+.
-*+set_autoload_path+* This initializer runs before +bootstrap_hook+. Adds all sub-directories of +app+ and paths specified by +config.autoload_paths+ to +ActiveSupport::Dependencies.autoload_paths+.
+*+set_autoload_paths+* This initializer runs before +bootstrap_hook+. Adds all sub-directories of +app+ and paths specified by +config.autoload_paths+ to +ActiveSupport::Dependencies.autoload_paths+.
*+add_routing_paths+* Loads (by default) all +config/routes.rb+ files (in the application and railties, including engines) and sets up the routes for the application.
diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile
index e6ec061c9a..4706725bb6 100644
--- a/railties/guides/source/contributing_to_ruby_on_rails.textile
+++ b/railties/guides/source/contributing_to_ruby_on_rails.textile
@@ -104,7 +104,6 @@ $ cd railties
$ TEST_DIR=generators rake test
</shell>
-
h4. Warnings
The test suite runs with warnings enabled. Ideally Ruby on Rails should issue no warning, but there may be a few, and also some from third-party libraries. Please ignore (or fix!) them if any, and submit patches that do not issue new warnings.
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 9051ede9dd..c277f5723a 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -27,9 +27,7 @@ The most basic form helper is +form_tag+.
<% end %>
</erb>
-When called without arguments like this, it creates a form element that has the current page as its action and "post" as its method (some line breaks added for readability):
-
-Sample output from +form_tag+:
+When called without arguments like this, it creates a +&lt;form&gt;+ tag which, when submitted, will POST to the current page. For instance, assuming the current page is +/home/index+, the generated HTML will look like this (some line breaks added for readability):
<html>
<form accept-charset="UTF-8" action="/home/index" method="post">
@@ -41,36 +39,30 @@ Sample output from +form_tag+:
</form>
</html>
-If you carefully observe this output, you can see that the helper generated something you didn't specify: a +div+ element with two hidden input elements inside. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether action is "get" or "post". Second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form whose action is not "get" (provided that this security feature is enabled). You can read more about this in the "Ruby On Rails Security Guide":./security.html#_cross_site_reference_forgery_csrf.
+Now, you'll notice that the HTML contains something extra: a +div+ element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection*, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the "Security Guide":./security.html#_cross_site_reference_forgery_csrf.
-NOTE: Throughout this guide, this +div+ with the hidden input elements will be stripped away to have clearer code samples.
+NOTE: Throughout this guide, the +div+ with the hidden input elements will be excluded from code samples for brevity.
h4. A 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:
+One of the most basic forms you see on the web is a search form. This form contains:
# a form element with "GET" method,
# a label for the input,
# a text input element, and
# a submit element.
-IMPORTANT: Always use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action.
-
-To create this form you will use +form_tag+, +label_tag+, +text_field_tag+, and +submit_tag+, respectively.
-
-A basic search form
+To create this form you will use +form_tag+, +label_tag+, +text_field_tag+, and +submit_tag+, respectively. Like this:
<erb>
-<%= form_tag(search_path, :method => "get") do %>
+<%= form_tag("/search", :method => "get") do %>
<%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %>
</erb>
-TIP: +search_path+ can be a named route specified in "routes.rb" as: <br /><code>match "search" => "search"</code> This declares that path "/search" will be handled by action "search" belonging to controller "search".
-
-The above view code will result in the following markup:
+This will generate the following HTML:
<html>
<form accept-charset="UTF-8" action="/search" method="get">
@@ -80,47 +72,35 @@ The above view code will result in the following markup:
</form>
</html>
+TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
+
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 the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
+IMPORTANT: Always use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action.
h4. Multiple Hashes in Form Helper Calls
-By now you've seen that the +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class.
+The +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class.
-As with the +link_to+ helper, the path argument doesn't have to be given a string. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. However, this is a bad way to pass multiple hashes as method arguments:
+As with the +link_to+ helper, the path argument doesn't have to be given a string; it can be a hash of URL parameters recognizable by Rails' routing mechanism, which will turn the hash into a valid URL. However, since both arguments to +form_tag+ are hashes, you can easily run into a problem if you would like to specify both. For instance, let's say you write this:
<ruby>
form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form")
-# => <form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">
+# => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'
</ruby>
-Here you wanted to pass two hashes, but the Ruby interpreter sees only one hash, so Rails will construct a URL with extraneous parameters. The correct way of passing multiple hashes as arguments is to delimit the first hash (or both hashes) with curly brackets:
+Here, +method+ and +class+ are appended to the query string of the generated URL because you even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
<ruby>
form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form")
-# => <form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">
+# => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">'
</ruby>
-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 +expecting tASSOC+ syntax error.
-
h4. Helpers for Generating Form Elements
-Rails provides a series of helpers for generating form elements such as checkboxes, text fields and radio buttons. These basic helpers, with names ending in <notextile>_tag</notextile> such as +text_field_tag+ and +check_box_tag+ generate just a single +&lt;input&gt;+ element. The first parameter to these is always the name of the input. In the controller this name will be the key in the +params+ hash used to get the value entered by the user. For example, if the form contains
+Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as +text_field_tag+ and +check_box_tag+), generate just a single +&lt;input&gt;+ element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the +params+ hash in the controller with the value entered by the user for that field. For example, if the form contains +<%= text_field_tag(:query) %>+, then you would be able to get the value of this field in the controller with +params[:query]+.
-<erb>
-<%= text_field_tag(:query) %>
-</erb>
-
-then the controller code should use
-
-<ruby>
-params[:query]
-</ruby>
-
-to retrieve the value entered by the user. When naming inputs, be aware that Rails uses certain conventions that control whether values are at the top level of the +params+ hash, inside an array or a nested hash and so on. You can read more about them in the parameter_names section. For details on the precise usage of these helpers, please refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html.
+When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in +params+. You can read more about them in "chapter 7 of this guide":#understanding-parameter-naming-conventions. For details on the precise usage of these helpers, please refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html.
h5. Checkboxes
@@ -133,7 +113,7 @@ Checkboxes are form controls that give the user a set of options they can enable
<%= label_tag(:pet_cat, "I own a cat") %>
</erb>
-output:
+This generates the following:
<html>
<input id="pet_dog" name="pet_dog" type="checkbox" value="1" />
@@ -142,11 +122,11 @@ output:
<label for="pet_cat">I own a cat</label>
</html>
-The second parameter to +check_box_tag+ is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the +params+ hash). With the above form you would check the value of +params[:pet_dog]+ and +params[:pet_cat]+ to see which pets the user owns.
+The first parameter to +check_box_tag+, of course, is the name of the input. The second parameter, naturally, is the value of the input. This value will be included in the form data (and be present in +params+) when the checkbox is checked.
h5. Radio Buttons
-Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e. the user can only pick one):
+Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e., the user can only pick one):
<erb>
<%= radio_button_tag(:age, "child") %>
@@ -155,7 +135,7 @@ Radio buttons, while similar to checkboxes, are controls that specify a set of o
<%= label_tag(:age_adult, "I'm over 21") %>
</erb>
-output:
+Output:
<html>
<input id="age_child" name="age" type="radio" value="child" />
@@ -164,32 +144,41 @@ output:
<label for="age_adult">I'm over 21</label>
</html>
-As with +check_box_tag+ the second parameter to +radio_button_tag+ is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and +params[:age]+ will contain either "child" or "adult".
+As with +check_box_tag+, the second parameter to +radio_button_tag+ is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and +params[:age]+ will contain either "child" or "adult".
-IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region.
+NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and make it easier for users to click the inputs by expanding the clickable region.
h4. Other Helpers of Interest
-Other form controls worth mentioning are the text area, password input and hidden input:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
<%= password_field_tag(:password) %>
<%= hidden_field_tag(:parent_id, "5") %>
+<%= search_field(:user, :name) %>
+<%= telephone_field(:user, :phone) %>
+<%= url_field(:user, :homepage) %>
+<%= email_field(:user, :address) %>
</erb>
-output:
+Output:
<html>
<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" />
+<input id="user_name" name="user[name]" size="30" type="search" />
+<input id="user_phone" name="user[phone]" size="30" type="tel" />
+<input id="user_homepage" size="30" name="user[homepage]" type="url" />
+<input id="user_address" size="30" name="user[address]" type="email" />
</html>
-Hidden inputs are not shown to the user, but they hold data like any textual input. Values inside them can be changed with JavaScript.
+Hidden inputs are not shown to the user but instead hold data like 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 configure your application to prevent those parameters from being logged.
+IMPORTANT: The search, telephone, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
h3. Dealing with Model Objects
@@ -353,7 +342,6 @@ output:
When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
-
h3. Making Select Boxes with Ease
Select boxes in HTML require a significant amount of markup (one +OPTION+ element for each option to choose from), therefore it makes the most sense for them to be dynamically generated.
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 6aca5d3420..d2bfcfdbb4 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -1,6 +1,7 @@
h2. Getting Started with Rails
-This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with:
+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
* The general layout of a Rails application
@@ -9,20 +10,32 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y
endprologue.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This Guide is based on Rails 3.1. Some of the code shown here will not
+work in earlier versions of Rails.
h3. Guide Assumptions
-This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed:
+This guide is designed for beginners who want to get started with a Rails
+application from scratch. It does not assume that you have any prior experience
+with Rails. However, to get the most out of it, you need to have some
+prerequisites installed:
* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher
-TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails 3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth sailing.
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails
+3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02
+though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults
+on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth
+sailing.
* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
+ ** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1
* A working installation of the "SQLite3 Database":http://www.sqlite.org
-Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including:
+Rails is a web application framework running on the Ruby programming language.
+If you have no prior experience with Ruby, you will find a very steep learning
+curve diving straight into Rails. There are some good free resources on the
+internet for learning Ruby, including:
* "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com
* "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/
@@ -30,19 +43,32 @@ Rails is a web application framework running on the Ruby programming language. I
h3. What is Rails?
-Rails is a web application development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun.
+Rails is a web application development framework written in the Ruby language.
+It is designed to make programming web applications easier by making assumptions
+about what every developer needs to get started. It allows you to write less
+code while accomplishing more than many other languages and frameworks.
+Experienced Rails developers also report that it makes web application
+development more fun.
-Rails is opinionated software. It makes the assumption that there is a "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience.
+Rails is opinionated software. It makes the assumption that there is a "best"
+way to do things, and it's designed to encourage that way - and in some cases to
+discourage alternatives. If you learn "The Rails Way" you'll probably discover a
+tremendous increase in productivity. If you persist in bringing old habits from
+other languages to your Rails development, and trying to use patterns you
+learned elsewhere, you may have a less happy experience.
The Rails philosophy includes several guiding principles:
* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing.
-* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to do it, rather than requiring you to specify every little thing through endless configuration files.
-* REST is the best pattern for web applications - organizing your application around resources and standard HTTP verbs is the fastest way to go.
+* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to
+d o it, rather than requiring you to specify every little thing through endless configuration files.
+* REST is the best pattern for web applications - organizing your application around resources and standard HTTP verbs
+i s the fastest way to go.
h4. The MVC Architecture
-At the core of Rails is the Model, View, Controller architecture, usually just called MVC. MVC benefits include:
+At the core of Rails is the Model, View, Controller architecture, usually just
+called MVC. MVC benefits include:
* Isolation of business logic from the user interface
* Ease of keeping code DRY
@@ -50,19 +76,34 @@ At the core of Rails is the Model, View, Controller architecture, usually just c
h5. Models
-A 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. In most cases, one table in your database will correspond to one model in your application. The bulk of your application's business logic will be concentrated in the models.
+A 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. In most
+cases, each table in your database will correspond to one model in your
+application. The bulk of your application's business logic will be concentrated
+in the models.
h5. Views
-Views represent the user interface of your application. In Rails, views are often HTML files with embedded Ruby code that perform tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application.
+Views represent the user interface of your application. In Rails, views are
+often HTML files with embedded Ruby code that perform tasks related solely to
+the presentation of the data. Views handle the job of providing data to the web
+browser or other tool that is used to make requests from your application.
h5. Controllers
-Controllers provide the "glue" between models and views. In Rails, controllers are responsible for processing the incoming requests from the web browser, interrogating the models for data, and passing that data on to the views for presentation.
+Controllers provide the "glue" between models and views. In Rails, controllers
+are responsible for processing the incoming requests from the web browser,
+interrogating the models for data, and passing that data on to the views for
+presentation.
h4. The Components of Rails
-Rails ships as many individual components.
+Rails ships as many individual components. Each of these components are briefly
+explained below. If you are new to Rails, as you read this section, don't get
+hung up on the details of each component, as they will be explained in further
+detail later. For instance, we will bring up Rack applications, but you don't
+need to know anything about them to continue with this guide.
* Action Pack
** Action Controller
@@ -75,70 +116,107 @@ Rails ships as many individual components.
* Active Support
* Railties
-
h5. Action Pack
-Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC".
+Action Pack is a single gem that contains Action Controller, Action View and
+Action Dispatch. The "VC" part of "MVC".
-h5. Action Controller
+h6. Action Controller
-Action Controller is the component that manages the controllers in a Rails application. The Action Controller framework processes incoming requests to a Rails application, extracts parameters, and dispatches them to the intended action. Services provided by Action Controller include session management, template rendering, and redirect management.
+Action Controller is the component that manages the controllers in a Rails
+application. The Action Controller framework processes incoming requests to a
+Rails application, extracts parameters, and dispatches them to the intended
+action. Services provided by Action Controller include session management,
+template rendering, and redirect management.
-h5. Action View
+h6. Action View
-Action View manages the views of your Rails application. It can create both HTML and XML output by default. Action View manages rendering templates, including nested and partial templates, and includes built-in AJAX support.
+Action View manages the views of your Rails application. It can create both HTML
+and XML output by default. Action View manages rendering templates, including
+nested and partial templates, and includes built-in AJAX support. View
+templates are covered in more detail in another guide called "Layouts and
+Rendering":layouts_and_rendering.html.
-h5. Action Dispatch
+h6. Action Dispatch
-Action Dispatch handles routing of web requests and dispatches them as you want, either to your application or any other Rack application.
+Action Dispatch handles routing of web requests and dispatches them as you want,
+either to your application or any other Rack application. Rack applications are
+a more advanced topic and are covered in a separate guide called "Rails on
+Rack":rails_on_rack.html.
h5. Action Mailer
-Action Mailer is a framework for building e-mail services. You can use Action Mailer to receive and process incoming email and send simple plain text or complex multipart emails based on flexible templates.
+Action Mailer is a framework for building e-mail services. You can use Action
+Mailer to receive and process incoming email and send simple plain text or
+complex multipart emails based on flexible templates.
h5. Active Model
-Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this.
+Active Model provides a defined interface between the Action Pack gem services
+and Object Relationship Mapping gems such as Active Record. Active Model allows
+Rails to utilize other ORM frameworks in place of Active Record if your
+application needs this.
h5. Active Record
-Active Record is the base for the models in a Rails application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other services.
+Active Record is the base for the models in a Rails application. It provides
+database independence, basic CRUD functionality, advanced finding capabilities,
+and the ability to relate models to one another, among other services.
h5. Active Resource
-Active Resource provides a framework for managing the connection between business objects and RESTful web services. It implements a way to map web-based resources to local objects with CRUD semantics.
+Active Resource provides a framework for managing the connection between
+business objects and RESTful web services. It implements a way to map web-based
+resources to local objects with CRUD semantics.
h5. Active Support
-Active Support is an extensive collection of utility classes and standard Ruby library extensions that are used in Rails, both by the core code and by your applications.
+Active Support is an extensive collection of utility classes and standard Ruby
+library extensions that are used in Rails, both by the core code and by your
+applications.
h5. Railties
-Railties is the core Rails code that builds new Rails applications and glues the various frameworks and plugins together in any Rails application.
+Railties is the core Rails code that builds new Rails applications and glues the
+various frameworks and plugins together in any Rails application.
h4. REST
-Rest stands for Representational State Transfer and is the foundation of the RESTful architecture. This is generally considered to be Roy Fielding's doctoral thesis, "Architectural Styles and the Design of Network-based Software Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. While you can read through the thesis, REST in terms of Rails boils down to two main principles:
+Rest stands for Representational State Transfer and is the foundation of the
+RESTful architecture. This is generally considered to be Roy Fielding's doctoral
+thesis, "Architectural Styles and the Design of Network-based Software
+Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. While
+you can read through the thesis, REST in terms of Rails boils down to two main
+principles:
* Using resource identifiers such 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:
+For example, the following HTTP request:
<tt>DELETE /photos/17</tt>
-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 hooks into this shielding you from many of the RESTful complexities and browser quirks.
+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 hooks into this shielding you
+from many of the RESTful complexities and browser quirks.
-If you'd like more details on REST as an architectural style, these resources are more approachable than Fielding's thesis:
+If you'd like more details on REST as an architectural style, these resources
+are more approachable than Fielding's thesis:
* "A Brief Introduction to REST":http://www.infoq.com/articles/rest-introduction by Stefan Tilkov
* "An Introduction to REST":http://bitworking.org/news/373/An-Introduction-to-REST (video tutorial) by Joe Gregorio
* "Representational State Transfer":http://en.wikipedia.org/wiki/Representational_State_Transfer article in Wikipedia
-* "How to GET a Cup of Coffee":http://www.infoq.com/articles/webber-rest-workflow by Jim Webber, Savas Parastatidis & Ian Robinson
+* "How to GET a Cup of Coffee":http://www.infoq.com/articles/webber-rest-workflow by Jim Webber, Savas Parastatidis &
+Ian Robinson
h3. Creating a New Rails Project
-If you follow this guide, you'll create a Rails project called <tt>blog</tt>, a (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed.
+If you follow this guide, you'll create a Rails project called <tt>blog</tt>, a
+(very) simple weblog. Before you can start building the application, you need to
+make sure that you have Rails itself installed.
+
+TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code>
h4. Installing Rails
@@ -149,13 +227,19 @@ Usually run this as the root user:
# gem install rails
</shell>
-TIP. If you're working on Windows, you can quickly install Ruby and Rails with "Rails Installer":http://railsinstaller.org.
+TIP. If you're working on Windows, you can quickly install Ruby and Rails with
+"Rails Installer":http://railsinstaller.org.
h4. Creating the Blog Application
-The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":https://github.com/mikel/getting-started-code.
+The best way to use this guide is to follow each step as it happens, no code or
+step needed to make this example application has been left out, so you can
+literally follow along step by step. If you need to see the completed code, you
+can download it from "Getting Started
+Code":https://github.com/mikel/getting-started-code.
-To begin, open a terminal, navigate to a folder where you have rights to create files, and type:
+To begin, open a terminal, navigate to a folder where you have rights to create
+files, and type:
<shell>
$ rails new blog
@@ -163,20 +247,27 @@ $ rails new blog
This will create a Rails application called Blog in a directory called blog.
-TIP: You can see all of the switches that the Rails application builder accepts by running <tt>rails new -h</tt>.
+TIP: You can see all of the switches that the Rails application builder accepts
+by running
+<tt>rails new -h</tt>.
-After you create the blog application, switch to its folder to continue work directly in that application:
+After you create the blog application, switch to its folder to continue work
+directly in that application:
<shell>
$ cd blog
</shell>
-In any case, Rails will create a folder in your working directory called <tt>blog</tt>. Open up that folder and explore its contents. Most of the work in this tutorial will happen in the <tt>app/</tt> folder, but here's a basic rundown on the function of each folder that Rails creates in a new application by default:
+In any case, Rails will create a folder in your working directory called
+<tt>blog</tt>. Open up that folder and explore its contents. Most of the work in
+this tutorial will happen in the <tt>app/</tt> folder, but here's a basic
+rundown on the function of each folder that Rails creates in a new application
+by default:
|_.File/Folder|_.Purpose|
-|Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application.|
-|README|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.|
-|Rakefile|This file contains batch jobs that can be run from the terminal.|
+|Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application. See section on Bundler, below.|
+|README|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
+|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
|app/|Contains the controllers, models, views and assets for your application. You'll focus on this folder for the remainder of this guide.|
|config/|Configure your application's runtime rules, routes, database, and more.|
|config.ru|Rack configuration for Rack based servers used to start the application.|
@@ -190,20 +281,13 @@ In any case, Rails will create a folder in your working directory called <tt>blo
|tmp/|Temporary files|
|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.|
-h4. Installing the Required Gems
-
-Rails applications manage gem dependencies with "Bundler":http://gembundler.com/v1.0/index.html by default. As we don't need any other gems beyond the ones in the generated +Gemfile+ we can directly run
-
-<shell>
-$ bundle install
-</shell>
-
-to have them ready.
-
h4. Configuring a Database
-Just about every Rails application will interact with a database. The database to use is specified in a configuration file, +config/database.yml+.
-If you open this file in a new Rails application, you'll see a default database configuration using SQLite3. The file contains sections for three different environments in which Rails can run by default:
+Just about every Rails application will interact with a database. The database
+to use is specified in a configuration file, +config/database.yml+. If you open
+this file in a new Rails application, you'll see a default database
+configuration using SQLite3. The file contains sections for three different
+environments in which Rails can run by default:
* The +development+ environment is used on your development computer as you interact manually with the application.
* The +test+ environment is used to run automated tests.
@@ -211,9 +295,15 @@ If you open this file in a new Rails application, you'll see a default database
h5. Configuring an SQLite3 Database
-Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
+Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is
+a lightweight serverless database application. While a busy production
+environment may overload SQLite, it works well for development and testing.
+Rails defaults to using an SQLite database when creating a new project, but you
+can always change it later.
-Here's the section of the default configuration file (<tt>config/database.yml</tt>) with connection information for the development environment:
+Here's the section of the default configuration file
+(<tt>config/database.yml</tt>) with connection information for the development
+environment:
<yaml>
development:
@@ -223,11 +313,17 @@ development:
timeout: 5000
</yaml>
-NOTE: In this guide we are using an SQLite3 database for data storage, because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
+NOTE: In this guide we are using an SQLite3 database for data storage, because
+it is a zero configuration database that just works. Rails also supports MySQL
+and PostgreSQL "out of the box", and has plugins for many database systems. If
+you are using a database in a production environment Rails most likely has an
+adapter for it.
h5. Configuring a MySQL Database
-If you choose to use MySQL instead of the shipped SQLite3 database, your +config/database.yml+ will look a little different. Here's the development section:
+If you choose to use MySQL instead of the shipped SQLite3 database, your
++config/database.yml+ will look a little different. Here's the development
+section:
<yaml>
development:
@@ -240,11 +336,14 @@ development:
socket: /tmp/mysql.sock
</yaml>
-If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the +development+ section as appropriate.
+If your development computer's MySQL installation includes a root user with an
+empty password, this configuration should work for you. Otherwise, change the
+username and password in the +development+ section as appropriate.
h5. Configuring a PostgreSQL Database
-If you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases:
+If you choose to use PostgreSQL, your +config/database.yml+ will be customized
+to use PostgreSQL databases:
<yaml>
development:
@@ -258,7 +357,8 @@ development:
h5. Configuring an SQLite3 Database for JRuby Platform
-If you choose to use SQLite3 and using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+If you choose to use SQLite3 and using JRuby, your +config/database.yml+ will
+look a little different. Here's the development section:
<yaml>
development:
@@ -268,7 +368,8 @@ development:
h5. Configuring a MySQL Database for JRuby Platform
-If you choose to use MySQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+If you choose to use MySQL and using JRuby, your +config/database.yml+ will look
+a little different. Here's the development section:
<yaml>
development:
@@ -280,7 +381,9 @@ development:
h5. Configuring a PostgreSQL Database for JRuby Platform
-Finally if you choose to use PostgreSQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+Finally if you choose to use PostgreSQL and using JRuby, your
++config/database.yml+ will look a little different. Here's the development
+section:
<yaml>
development:
@@ -293,51 +396,79 @@ development:
Change the username and password in the +development+ section as appropriate.
-TIP: You don't have to update the database configurations manually. If you had a look at the options of application generator, you have seen that one of them is named <tt>--database</tt>. It lets you choose an adapter for couple of most used relational databases. You can even run the generator repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting of the +config/database.yml+ file, your application will be configured for MySQL instead of SQLite.
+TIP: You don't have to update the database configurations manually. If you look at the
+options of the application generator, you will see that one of the options
+is named <tt>--database</tt>. This option allows you to choose an adapter from a
+list of the most used relational databases. You can even run the generator
+repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting
+ of the +config/database.yml+ file, your application will be configured for MySQL
+instead of SQLite.
h4. Creating the Database
-Now that you have your database configured, it's time to have Rails create an empty database for you. You can do this by running a rake command:
+Now that you have your database configured, it's time to have Rails create an
+empty database for you. You can do this by running a rake command:
<shell>
$ rake db:create
</shell>
-This will create your development and test SQLite3 databases inside the <tt>db/</tt> folder.
+This will create your development and test SQLite3 databases inside the
+<tt>db/</tt> folder.
-TIP: Rake is a general-purpose command-runner that Rails uses for many things. You can see the list of available rake commands in your application by running +rake -T+.
+TIP: Rake is a general-purpose command-runner that Rails uses for many things.
+You can see the list of available rake commands in your application by running
++rake -T+.
h3. Hello, Rails!
-One of the traditional places to start with a new language is by getting some text up on screen quickly. To do this, you need to get your Rails application server running.
+One of the traditional places to start with a new language is by getting some
+text up on screen quickly. To do this, you need to get your Rails application
+server running.
h4. Starting up the Web Server
-You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running:
+You actually have a functional Rails application already. To see it, you need to
+start a web server on your development machine. You can do this by running:
<shell>
$ rails server
</shell>
-This will fire up an instance of the WEBrick web server by default (Rails can also use several other web servers). To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see Rails' default information page:
+This will fire up an instance of the WEBrick web server by default (Rails can
+also use several other web servers). To see your application in action, open a
+browser window and navigate to "http://localhost:3000":http://localhost:3000.
+You should see Rails' default information page:
!images/rails_welcome.png(Welcome Aboard screenshot)!
-TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server.
+TIP: To stop the web server, hit Ctrl+C in the terminal window where it's
+running. In development mode, Rails does not generally require you to stop the
+server; changes you make in files will be automatically picked up by the server.
-The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment.
+The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it
+makes sure that you have your software configured correctly enough to serve a
+page. You can also click on the _About your application’s environment_ link to
+see a summary of your application's environment.
h4. Say "Hello", Rails
-To get Rails saying "Hello", you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal:
+To get Rails saying "Hello", you need to create at minimum a controller and a
+view. Fortunately, you can do that in a single command. Enter this command in
+your terminal:
<shell>
$ rails generate controller home index
</shell>
-TIP: If you're on Windows, or your Ruby is set up in some non-standard fashion, you may need to explicitly pass Rails +rails+ commands to Ruby: <tt>ruby \path\to\your\application\script\rails generate controller home index</tt>.
+TIP: If you get a command not found error when running this command, you
+need to explicitly pass Rails +rails+ commands to Ruby: <tt>ruby
+\path\to\your\application\script\rails generate controller home index</tt>.
-Rails will create several files for you, including +app/views/home/index.html.erb+. This is the template that will be used to display the results of the +index+ action (method) in the +home+ controller. Open this file in your text editor and edit it to contain a single line of code:
+Rails will create several files for you, including
++app/views/home/index.html.erb+. This is the template that will be used to
+display the results of the +index+ action (method) in the +home+ controller.
+Open this file in your text editor and edit it to contain a single line of code:
<code class="html">
<h1>Hello, Rails!</h1>
@@ -345,17 +476,30 @@ Rails will create several files for you, including +app/views/home/index.html.er
h4. Setting the Application Home Page
-Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test.
+Now that we have made the controller and view, we need to tell Rails when we
+want "Hello Rails" to show up. In our case, we want it to show up when we
+navigate to the root URL of our site,
+"http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard"
+smoke test.
-The first step to doing this is to delete the default page from your application:
+The first step to doing this is to delete the default page from your
+application:
<shell>
$ rm public/index.html
</shell>
-We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic content we generate from the controllers.
+We need to do this as Rails will deliver any static file in the +public+
+directory in preference to any dynamic content we generate from the controllers.
-Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+, uncomment it and change it like the following:
+Now, you have to tell Rails where your actual home page is located. Open the
+file +config/routes.rb+ in your editor. This is your application's _routing
+file_ which holds entries in a special DSL (domain-specific language) that tells
+Rails how to connect incoming requests to controllers and actions. This file
+contains many sample routes on commented lines, and one of them actually shows
+you how to connect the root of your site to a specific controller and action.
+Find the line beginning with +root :to+, uncomment it and change it like the
+following:
<ruby>
Blog::Application.routes.draw do
@@ -366,27 +510,33 @@ Blog::Application.routes.draw do
root :to => "home#index"
</ruby>
-The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action.
+The +root :to => "home#index"+ tells Rails to map the root action to the home
+controller's index action.
-Now if you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see +Hello, Rails!+.
+Now if you navigate to "http://localhost:3000":http://localhost:3000 in your
+browser, you'll see +Hello, Rails!+.
-NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html.
+NOTE. For more information about routing, refer to "Rails Routing from the
+Outside In":routing.html.
h3. Getting Up and Running Quickly with Scaffolding
-Rails _scaffolding_ is a quick way to generate some of the major pieces of an application. If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job.
+Rails _scaffolding_ is a quick way to generate some of the major pieces of an
+application. If you want to create the models, views, and controllers for a new
+resource in a single operation, scaffolding is the tool for the job.
h3. Creating a Resource
-In the case of the blog application, you can start by generating a scaffolded Post resource: this will represent a single blog posting. To do this, enter this command in your terminal:
+In the case of the blog application, you can start by generating a scaffolded
+Post resource: this will represent a single blog posting. To do this, enter this
+command in your terminal:
<shell>
$ rails generate scaffold Post name:string title:string content:text
</shell>
-NOTE. While scaffolding will get you up and running quickly, the code it generates is unlikely to be a perfect fit for your application. You'll most probably want to customize the generated code. Many experienced Rails developers avoid scaffolding entirely, preferring to write all or most of their source code from scratch. Rails, however, makes it really simple to customize templates for generated models, controllers, views and other source files. You'll find more information in the "Creating and Customizing Rails Generators & Templates":generators.html guide.
-
-The scaffold generator will build 15 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates:
+The scaffold generator will build several files in your application, along with some
+folders, and edit <tt>config/routes.rb</tt>. Here's a quick overview of what it creates:
|_.File |_.Purpose|
|db/migrate/20100207214725_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp)|
@@ -400,16 +550,33 @@ The scaffold generator will build 15 files in your application, along with some
|app/views/posts/_form.html.erb |A partial to control the overall look and feel of the form used in edit and new views|
|app/helpers/posts_helper.rb |Helper functions to be used from the post views|
|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better|
+|app/assets/stylesheets/post.css.scss |Cascading style sheet for the posts controller|
+|app/assets/javascripts/post.js.coffee |CoffeeScript for the posts controller|
|test/unit/post_test.rb |Unit testing harness for the posts model|
|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller|
|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper|
|config/routes.rb |Edited to include routing information for posts|
+NOTE. While scaffolding will get you up and running quickly, the code it
+generates is unlikely to be a perfect fit for your application. You'll most
+probably want to customize the generated code. Many experienced Rails developers
+avoid scaffolding entirely, preferring to write all or most of their source code
+from scratch. Rails, however, makes it really simple to customize templates for
+generated models, controllers, views and other source files. You'll find more
+information in the "Creating and Customizing Rails Generators &
+Templates":generators.html guide.
+
h4. Running a Migration
-One of the products of the +rails generate scaffold+ command is a _database migration_. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created.
+One of the products of the +rails generate scaffold+ command is a _database
+migration_. Migrations are Ruby classes that are designed to make it simple to
+create and modify database tables. Rails uses rake commands to run migrations,
+and it's possible to undo a migration after it's been applied to your database.
+Migration filenames include a timestamp to ensure that they're processed in the
+order that they were created.
-If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find:
+If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember,
+yours will have a slightly different name), here's what you'll find:
<ruby>
class CreatePosts < ActiveRecord::Migration
@@ -425,7 +592,14 @@ class CreatePosts < ActiveRecord::Migration
end
</ruby>
-The above migration creates a method name +change+ which will be called when you run this migration. The action defined in that method is also reversible, which means Rails knows how to reverse the change made by this migration, in case you want to reverse it at later date. By default, when you run this migration it will creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide.
+The above migration creates a method name +change+ which will be called when you
+run this migration. The action defined in that method is also reversible, which
+means Rails knows how to reverse the change made by this migration, in case you
+want to reverse it at later date. By default, when you run this migration it
+will creates a +posts+ table with two string columns and a text column. It also
+creates two timestamp fields to track record creation and updating. More
+information about Rails migrations can be found in the "Rails Database
+Migrations":migrations.html guide.
At this point, you can use a rake command to run the migration:
@@ -433,7 +607,8 @@ At this point, you can use a rake command to run the migration:
$ rake db:migrate
</shell>
-Rails will execute this migration command and tell you it created the Posts table.
+Rails will execute this migration command and tell you it created the Posts
+table.
<shell>
== CreatePosts: migrating ====================================================
@@ -442,28 +617,43 @@ Rails will execute this migration command and tell you it created the Posts tabl
== CreatePosts: migrated (0.0020s) ===========================================
</shell>
-NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. If you would like to execute migrations in other environment, for instance in production, you must explicitly pass it when invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>.
+NOTE. Because you're working in the development environment by default, this
+command will apply to the database defined in the +development+ section of your
++config/database.yml+ file. If you would like to execute migrations in other
+environment, for instance in production, you must explicitly pass it when
+invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>.
h4. Adding a Link
-To hook the posts up to the home page you've already created, you can add a link to the home page. Open +app/views/home/index.html.erb+ and modify it as follows:
+To hook the posts up to the home page you've already created, you can add a link
+to the home page. Open +app/views/home/index.html.erb+ and modify it as follows:
<ruby>
<h1>Hello, Rails!</h1>
<%= link_to "My Blog", posts_path %>
</ruby>
-The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts.
+The +link_to+ method is one of Rails' built-in view helpers. It creates a
+hyperlink based on text to display and where to go - in this case, to the path
+for posts.
h4. Working with Posts in the Browser
-Now you're ready to start working with posts. To do that, navigate to "http://localhost:3000":http://localhost:3000/ and then click the "My Blog" link:
+Now you're ready to start working with posts. To do that, navigate to
+"http://localhost:3000":http://localhost:3000/ and then click the "My Blog"
+link:
!images/posts_index.png(Posts Index screenshot)!
-This is the result of Rails rendering the +index+ view of your posts. There aren't currently any posts in the database, but if you click the +New Post+ link you can create one. After that, you'll find that you can edit posts, look at their details, or destroy them. All of the logic and HTML to handle this was built by the single +rails generate scaffold+ command.
+This is the result of Rails rendering the +index+ view of your posts. There
+aren't currently any posts in the database, but if you click the +New Post+ link
+you can create one. After that, you'll find that you can edit posts, look at
+their details, or destroy them. All of the logic and HTML to handle this was
+built by the single +rails generate scaffold+ command.
-TIP: In development mode (which is what you're working in by default), Rails reloads your application with every browser request, so there's no need to stop and restart the web server.
+TIP: In development mode (which is what you're working in by default), Rails
+reloads your application with every browser request, so there's no need to stop
+and restart the web server.
Congratulations, you're riding the rails! Now it's time to see how it all works.
@@ -476,11 +666,16 @@ class Post < ActiveRecord::Base
end
</ruby>
-There isn't much to this file - but note that the +Post+ class inherits from +ActiveRecord::Base+. Active Record supplies a great deal of functionality to your Rails models for free, including basic database CRUD (Create, Read, Update, Destroy) operations, data validation, as well as sophisticated search support and the ability to relate multiple models to one another.
+There isn't much to this file - but note that the +Post+ class inherits from
++ActiveRecord::Base+. Active Record supplies a great deal of functionality to
+your Rails models for free, including basic database CRUD (Create, Read, Update,
+Destroy) operations, data validation, as well as sophisticated search support
+and the ability to relate multiple models to one another.
h4. Adding Some Validation
-Rails includes methods to help you validate the data that you send to models. Open the +app/models/post.rb+ file and edit it:
+Rails includes methods to help you validate the data that you send to models.
+Open the +app/models/post.rb+ file and edit it:
<ruby>
class Post < ActiveRecord::Base
@@ -490,17 +685,24 @@ class Post < ActiveRecord::Base
end
</ruby>
-These changes will ensure that all posts have a name and a title, and that the title is at least five characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects.
+These changes will ensure that all posts have a name and a title, and that the
+title is at least five characters long. Rails can validate a variety of
+conditions in a model, including the presence or uniqueness of columns, their
+format, and the existence of associated objects.
h4. Using the Console
-To see your validations in action, you can use the console. The console is a command-line tool that lets you execute Ruby code in the context of your application:
+To see your validations in action, you can use the console. The console is a
+command-line tool that lets you execute Ruby code in the context of your
+application:
<shell>
$ rails console
</shell>
-TIP: The default console will make changes to your database. You can instead open a console that will roll back any changes you make by using +rails console --sandbox+.
+TIP: The default console will make changes to your database. You can instead
+open a console that will roll back any changes you make by using +rails console
+--sandbox+.
After the console loads, you can use it to work with your application's models:
@@ -517,15 +719,21 @@ After the console loads, you can use it to work with your application's models:
:name=>["can't be blank"] }>
</shell>
-This code shows creating a new +Post+ instance, attempting to save it and getting +false+ for a return value (indicating that the save failed), and inspecting the +errors+ of the post.
+This code shows creating a new +Post+ instance, attempting to save it and
+getting +false+ for a return value (indicating that the save failed), and
+inspecting the +errors+ of the post.
When you're finished, type +exit+ and hit +return+ to exit the console.
-TIP: Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes to your models while the console is open, type +reload!+ at the console prompt to load them.
+TIP: Unlike the development web server, the console does not automatically load
+your code afresh for each line. If you make changes to your models while the
+console is open, type +reload!+ at the console prompt to load them.
h4. Listing All Posts
-The easiest place to start looking at functionality is with the code that lists all posts. Open the file +app/controllers/posts_controller.rb+ and look at the +index+ action:
+The easiest place to start looking at functionality is with the code that lists
+all posts. Open the file +app/controllers/posts_controller.rb+ and look at the
++index+ action:
<ruby>
def index
@@ -538,11 +746,19 @@ def index
end
</ruby>
-+Post.all+ calls the +Post+ model to return all of the posts currently in the database. The result of this call is an array of posts that we store in an instance variable called +@posts+.
++Post.all+ calls the +Post+ model to return all of the posts currently in the
+database. The result of this call is an array of posts that we store in an
+instance variable called +@posts+.
-TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html.
+TIP: For more information on finding records with Active Record, see "Active
+Record Query Interface":active_record_querying.html.
-The +respond_to+ block handles both HTML and JSON calls to this action. If you browse to "http://localhost:3000/posts.json":http://localhost:3000/posts.json, you'll see a JSON containing all of the posts. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:
+The +respond_to+ block handles both HTML and JSON calls to this action. If you
+browse to "http://localhost:3000/posts.json":http://localhost:3000/posts.json,
+you'll see a JSON containing all of the posts. The HTML format looks for a view
+in +app/views/posts/+ with a name that corresponds to the action name. Rails
+makes all of the instance variables from the action available to the view.
+Here's +app/views/posts/index.html.erb+:
<erb>
<h1>Listing posts</h1>
@@ -574,18 +790,31 @@ The +respond_to+ block handles both HTML and JSON calls to this action. If you b
<%= link_to 'New post', new_post_path %>
</erb>
-This view iterates over the contents of the +@posts+ array to display content and links. A few things to note in the view:
+This view iterates over the contents of the +@posts+ array to display content
+and links. A few things to note in the view:
* +link_to+ builds a hyperlink to a particular destination
* +edit_post_path+ and +new_post_path+ are helpers that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes.
-NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +&lt;%= raw post.name %&gt;+.
+NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so
+that any HTML would be escaped before being inserted into the page. In Rails
+3.0, this is now the default. To get unescaped HTML, you now use +&lt;%= raw
+post.name %&gt;+.
-TIP: For more details on the rendering process, see "Layouts and Rendering in Rails":layouts_and_rendering.html.
+TIP: For more details on the rendering process, see "Layouts and Rendering in
+Rails":layouts_and_rendering.html.
h4. Customizing the Layout
-The view is only part of the story of how HTML is displayed in your web browser. Rails also has the concept of +layouts+, which are containers for views. When Rails renders a view to the browser, it does so by putting the view's HTML into a layout's HTML. In previous versions of Rails, the +rails generate scaffold+ command would automatically create a controller specific layout, like +app/views/layouts/posts.html.erb+, for the posts controller. However this has been changed in Rails 3.0. An application specific +layout+ is used for all the controllers and can be found in +app/views/layouts/application.html.erb+. Open this layout in your editor and modify the +body+ tag:
+The view is only part of the story of how HTML is displayed in your web browser.
+Rails also has the concept of +layouts+, which are containers for views. When
+Rails renders a view to the browser, it does so by putting the view's HTML into
+a layout's HTML. In previous versions of Rails, the +rails generate scaffold+
+command would automatically create a controller specific layout, like
++app/views/layouts/posts.html.erb+, for the posts controller. However this has
+been changed in Rails 3.0. An application specific +layout+ is used for all the
+controllers and can be found in +app/views/layouts/application.html.erb+. Open
+this layout in your editor and modify the +body+ tag:
<erb>
<!DOCTYPE html>
@@ -604,11 +833,13 @@ The view is only part of the story of how HTML is displayed in your web browser.
</html>
</erb>
-Now when you refresh the +/posts+ page, you'll see a gray background to the page. This same gray background will be used throughout all the views for posts.
+Now when you refresh the +/posts+ page, you'll see a gray background to the
+page. This same gray background will be used throughout all the views for posts.
h4. Creating New Posts
-Creating a new post involves two actions. The first is the +new+ action, which instantiates an empty +Post+ object:
+Creating a new post involves two actions. The first is the +new+ action, which
+instantiates an empty +Post+ object:
<ruby>
def new
@@ -631,9 +862,15 @@ The +new.html.erb+ view displays this empty Post to the user:
<%= link_to 'Back', posts_path %>
</erb>
-The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post.
+The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in
+Rails. A partial is a snippet of HTML and Ruby code that can be reused in
+multiple locations. In this case, the form used to make a new post, is basically
+identical to a form used to edit a post, both have text fields for the name and
+title and a text area for the content with a button to make a new post or update
+the existing post.
-If you take a look at +views/posts/_form.html.erb+ file, you will see the following:
+If you take a look at +views/posts/_form.html.erb+ file, you will see the
+following:
<erb>
<%= form_for(@post) do |f| %>
@@ -666,17 +903,34 @@ If you take a look at +views/posts/_form.html.erb+ file, you will see the follow
<% end %>
</erb>
-This partial receives all the instance variables defined in the calling view file, so in this case, the controller assigned the new Post object to +@post+ and so, this is available in both the view and partial as +@post+.
+This partial receives all the instance variables defined in the calling view
+file, so in this case, the controller assigned the new Post object to +@post+
+and so, this is available in both the view and partial as +@post+.
-For more information on partials, refer to the "Layouts and Rendering in Rails":layouts_and_rendering.html#using-partials guide.
+For more information on partials, refer to the "Layouts and Rendering in
+Rails":layouts_and_rendering.html#using-partials guide.
-The +form_for+ block is used to create an HTML form. Within this block, you have access to methods to build various controls on the form. For example, +f.text_field :name+ tells Rails to create a text input on the form, and to hook it up to the +name+ attribute of the instance being displayed. You can only use these methods with attributes of the model that the form is based on (in this case +name+, +title+, and +content+). Rails uses +form_for+ in preference to having you write raw HTML because the code is more succinct, and because it explicitly ties the form to a particular model instance.
+The +form_for+ block is used to create an HTML form. Within this block, you have
+access to methods to build various controls on the form. For example,
++f.text_field :name+ tells Rails to create a text input on the form, and to hook
+it up to the +name+ attribute of the instance being displayed. You can only use
+these methods with attributes of the model that the form is based on (in this
+case +name+, +title+, and +content+). Rails uses +form_for+ in preference to
+having you write raw HTML because the code is more succinct, and because it
+explicitly ties the form to a particular model instance.
-The +form_for+ block is also smart enough to work out if you are doing a _New Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit button names appropriately in the HTML output.
+The +form_for+ block is also smart enough to work out if you are doing a _New
+Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit
+button names appropriately in the HTML output.
-TIP: If you need to create an HTML form that displays arbitrary fields, not tied to a model, you should use the +form_tag+ method, which provides shortcuts for building forms that are not necessarily tied to a model instance.
+TIP: If you need to create an HTML form that displays arbitrary fields, not tied
+to a model, you should use the +form_tag+ method, which provides shortcuts for
+building forms that are not necessarily tied to a model instance.
-When the user clicks the +Create Post+ button on this form, the browser will send information back to the +create+ method of the controller (Rails knows to call the +create+ method because the form is sent with an HTTP POST request; that's one of the conventions that I mentioned earlier):
+When the user clicks the +Create Post+ button on this form, the browser will
+send information back to the +create+ method of the controller (Rails knows to
+call the +create+ method because the form is sent with an HTTP POST request;
+that's one of the conventions that I mentioned earlier):
<ruby>
def create
@@ -697,15 +951,32 @@ def create
end
</ruby>
-The +create+ action instantiates a new Post object from the data supplied by the user on the form, which Rails makes available in the +params+ hash. After successfully saving the new post, +create+ returns the appropriate format that the user has requested (HTML in our case). It then redirects the user to the resulting post +show+ action and sets a notice to the user that the Post was successfully created.
-
-If the post was not successfully saved, due to a validation error, then the controller returns the user back to the +new+ action with any error messages so that the user has the chance to fix the error and try again.
-
-The "Post was successfully created." message is stored inside of the Rails +flash+ hash, (usually just called _the flash_) so that messages can be carried over to another action, providing the user with useful information on the status of their request. In the case of +create+, the user never actually sees any page rendered during the Post creation process, because it immediately redirects to the new Post as soon Rails saves the record. The Flash carries over a message to the next action, so that when the user is redirected back to the +show+ action, they are presented with a message saying "Post was successfully created."
+The +create+ action instantiates a new Post object from the data supplied by the
+user on the form, which Rails makes available in the +params+ hash. After
+successfully saving the new post, +create+ returns the appropriate format that
+the user has requested (HTML in our case). It then redirects the user to the
+resulting post +show+ action and sets a notice to the user that the Post was
+successfully created.
+
+If the post was not successfully saved, due to a validation error, then the
+controller returns the user back to the +new+ action with any error messages so
+that the user has the chance to fix the error and try again.
+
+The "Post was successfully created." message is stored inside of the Rails
++flash+ hash, (usually just called _the flash_) so that messages can be carried
+over to another action, providing the user with useful information on the status
+of their request. In the case of +create+, the user never actually sees any page
+rendered during the Post creation process, because it immediately redirects to
+the new Post as soon Rails saves the record. The Flash carries over a message to
+the next action, so that when the user is redirected back to the +show+ action,
+they are presented with a message saying "Post was successfully created."
h4. Showing an Individual Post
-When you click the +show+ link for a post on the index page, it will bring you to a URL like +http://localhost:3000/posts/1+. Rails interprets this as a call to the +show+ action for the resource, and passes in +1+ as the +:id+ parameter. Here's the +show+ action:
+When you click the +show+ link for a post on the index page, it will bring you
+to a URL like +http://localhost:3000/posts/1+. Rails interprets this as a call
+to the +show+ action for the resource, and passes in +1+ as the +:id+ parameter.
+Here's the +show+ action:
<ruby>
def show
@@ -718,7 +989,9 @@ def show
end
</ruby>
-The +show+ action uses +Post.find+ to search for a single record in the database by its id value. After finding the record, Rails displays it by using +show.html.erb+:
+The +show+ action uses +Post.find+ to search for a single record in the database
+by its id value. After finding the record, Rails displays it by using
++show.html.erb+:
<erb>
<p class="notice"><%= notice %></p>
@@ -745,7 +1018,9 @@ The +show+ action uses +Post.find+ to search for a single record in the database
h4. Editing Posts
-Like creating a new post, editing a post is a two-part process. The first step is a request to +edit_post_path(@post)+ with a particular post. This calls the +edit+ action in the controller:
+Like creating a new post, editing a post is a two-part process. The first step
+is a request to +edit_post_path(@post)+ with a particular post. This calls the
++edit+ action in the controller:
<ruby>
def edit
@@ -753,7 +1028,8 @@ def edit
end
</ruby>
-After finding the requested post, Rails uses the +edit.html.erb+ view to display it:
+After finding the requested post, Rails uses the +edit.html.erb+ view to display
+it:
<erb>
<h1>Editing post</h1>
@@ -764,9 +1040,12 @@ After finding the requested post, Rails uses the +edit.html.erb+ view to display
<%= link_to 'Back', posts_path %>
</erb>
-Again, as with the +new+ action, the +edit+ action is using the +form+ partial, this time however, the form will do a PUT action to the PostsController and the submit button will display "Update Post"
+Again, as with the +new+ action, the +edit+ action is using the +form+ partial,
+this time however, the form will do a PUT action to the PostsController and the
+submit button will display "Update Post"
-Submitting the form created by this view will invoke the +update+ action within the controller:
+Submitting the form created by this view will invoke the +update+ action within
+the controller:
<ruby>
def update
@@ -786,11 +1065,17 @@ def update
end
</ruby>
-In the +update+ action, Rails first uses the +:id+ parameter passed back from the edit view to locate the database record that's being edited. The +update_attributes+ call then takes the rest of the parameters from the request and applies them to this record. If all goes well, the user is redirected to the post's +show+ view. If there are any problems, it's back to the +edit+ view to correct them.
+In the +update+ action, Rails first uses the +:id+ parameter passed back from
+the edit view to locate the database record that's being edited. The
++update_attributes+ call then takes the rest of the parameters from the request
+and applies them to this record. If all goes well, the user is redirected to the
+post's +show+ view. If there are any problems, it's back to the +edit+ view to
+correct them.
h4. Destroying a Post
-Finally, clicking one of the +destroy+ links sends the associated id to the +destroy+ action:
+Finally, clicking one of the +destroy+ links sends the associated id to the
++destroy+ action:
<ruby>
def destroy
@@ -804,15 +1089,25 @@ def destroy
end
</ruby>
-The +destroy+ method of an Active Record model instance removes the corresponding record from the database. After that's done, there isn't any record to display, so Rails redirects the user's browser to the index view for the model.
+The +destroy+ method of an Active Record model instance removes the
+corresponding record from the database. After that's done, there isn't any
+record to display, so Rails redirects the user's browser to the index view for
+the model.
h3. Adding a Second Model
-Now that you've seen how a model built with scaffolding looks like, it's time to add a second model to the application. The second model will handle comments on blog posts.
+Now that you've seen how a model built with scaffolding looks like, it's time to
+add a second model to the application. The second model will handle comments on
+blog posts.
h4. Generating a Model
-Models in Rails use a singular name, and their corresponding database tables use a plural name. For the model to hold comments, the convention is to use the name Comment. Even if you don't want to use the entire apparatus set up by scaffolding, most Rails developers still use generators to make things like models and controllers. To create the new model, run this command in your terminal:
+Models in Rails use a singular name, and their corresponding database tables use
+a plural name. For the model to hold comments, the convention is to use the name
+Comment. Even if you don't want to use the entire apparatus set up by
+scaffolding, most Rails developers still use generators to make things like
+models and controllers. To create the new model, run this command in your
+terminal:
<shell>
$ rails generate model Comment commenter:string body:text post:references
@@ -832,9 +1127,12 @@ class Comment < ActiveRecord::Base
end
</ruby>
-This is very similar to the +post.rb+ model that you saw earlier. The difference is the line +belongs_to :post+, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide.
+This is very similar to the +post.rb+ model that you saw earlier. The difference
+is the line +belongs_to :post+, which sets up an Active Record _association_.
+You'll learn a little about associations in the next section of this guide.
-In addition to the model, Rails has also made a migration to create the corresponding database table:
+In addition to the model, Rails has also made a migration to create the
+corresponding database table:
<ruby>
class CreateComments < ActiveRecord::Migration
@@ -852,13 +1150,16 @@ class CreateComments < ActiveRecord::Migration
end
</ruby>
-The +t.references+ line sets up a foreign key column for the association between the two models. And the +add_index+ line sets up an index for this association column. Go ahead and run the migration:
+The +t.references+ line sets up a foreign key column for the association between
+the two models. And the +add_index+ line sets up an index for this association
+column. Go ahead and run the migration:
<shell>
$ rake db:migrate
</shell>
-Rails is smart enough to only execute the migrations that have not already been run against the current database, so in this case you will just see:
+Rails is smart enough to only execute the migrations that have not already been
+run against the current database, so in this case you will just see:
<shell>
== CreateComments: migrating =================================================
@@ -869,12 +1170,16 @@ Rails is smart enough to only execute the migrations that have not already been
h4. Associating Models
-Active Record associations let you easily declare the relationship between two models. In the case of comments and posts, you could write out the relationships this way:
+Active Record associations let you easily declare the relationship between two
+models. In the case of comments and posts, you could write out the relationships
+this way:
* Each comment belongs to one post
* One post can have many comments
-In fact, this is very close to the syntax that Rails uses to declare this association. You've already seen the line of code inside the Comment model that makes each comment belong to a Post:
+In fact, this is very close to the syntax that Rails uses to declare this
+association. You've already seen the line of code inside the Comment model that
+makes each comment belong to a Post:
<ruby>
class Comment < ActiveRecord::Base
@@ -894,13 +1199,20 @@ class Post < ActiveRecord::Base
end
</ruby>
-These two declarations enable a good bit of automatic behavior. For example, if you have an instance variable +@post+ containing a post, you can retrieve all the comments belonging to that post as the array +@post.comments+.
+These two declarations enable a good bit of automatic behavior. For example, if
+you have an instance variable +@post+ containing a post, you can retrieve all
+the comments belonging to that post as the array +@post.comments+.
-TIP: For more information on Active Record associations, see the "Active Record Associations":association_basics.html guide.
+TIP: For more information on Active Record associations, see the "Active Record
+Associations":association_basics.html guide.
h4. Adding a Route for Comments
-As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows:
+As with the +home+ controller, we will need to add a route so that Rails knows
+where we would like to navigate to see +comments+. Open up the
++config/routes.rb+ file again, you will see an entry that was added
+automatically for +posts+ near the top by the scaffold generator, +resources
+:posts+, edit it as follows:
<ruby>
resources :posts do
@@ -908,29 +1220,40 @@ resources :posts do
end
</ruby>
-This creates +comments+ as a _nested resource_ within +posts+. This is another part of capturing the hierarchical relationship that exists between posts and comments.
+This creates +comments+ as a _nested resource_ within +posts+. This is another
+part of capturing the hierarchical relationship that exists between posts and
+comments.
-TIP: For more information on routing, see the "Rails Routing from the Outside In":routing.html guide.
+TIP: For more information on routing, see the "Rails Routing from the Outside
+In":routing.html guide.
h4. Generating a Controller
-With the model in hand, you can turn your attention to creating a matching controller. Again, there's a generator for this:
+With the model in hand, you can turn your attention to creating a matching
+controller. Again, there's a generator for this:
<shell>
$ rails generate controller Comments
</shell>
-This creates four files and one empty directory:
+This creates six files and one empty directory:
* +app/controllers/comments_controller.rb+ - The controller
* +app/helpers/comments_helper.rb+ - A view helper file
* +test/functional/comments_controller_test.rb+ - The functional tests for the controller
* +test/unit/helpers/comments_helper_test.rb+ - The unit tests for the helper
* +app/views/comments/+ - Views of the controller are stored here
+* +app/assets/stylesheets/comment.css.scss+ - Cascading style sheet for the controller
+* +app/assets/javascripts/comment.js.coffee+ - CoffeeScript for the controller
-Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive.
+Like with any blog, our readers will create their comments directly after
+reading the post, and once they have added their comment, will be sent back to
+the post show page to see their comment now listed. Due to this, our
++CommentsController+ is there to provide a method to create comments and delete
+SPAM comments when they arrive.
-So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+) to let us make a new comment:
+So first, we'll wire up the Post show template
+(+/app/views/posts/show.html.erb+) to let us make a new comment:
<erb>
<p class="notice"><%= notice %></p>
@@ -969,7 +1292,8 @@ So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-This adds a form on the Post show page that creates a new comment, which will call the +CommentsController+ +create+ action, so let's wire that up:
+This adds a form on the Post show page that creates a new comment, which will
+call the +CommentsController+ +create+ action, so let's wire that up:
<ruby>
class CommentsController < ApplicationController
@@ -981,11 +1305,21 @@ class CommentsController < ApplicationController
end
</ruby>
-You'll see a bit more complexity here than you did in the controller for posts. That's a side-effect of the nesting that you've set up; each request for a comment has to keep track of the post to which the comment is attached, thus the initial find action to the Post model to get the post in question.
+You'll see a bit more complexity here than you did in the controller for posts.
+That's a side-effect of the nesting that you've set up; each request for a
+comment has to keep track of the post to which the comment is attached, thus the
+initial find action to the Post model to get the post in question.
-In addition, the code takes advantage of some of the methods available for an association. We use the +create+ method on +@post.comments+ to create and save the comment. This will automatically link the comment so that it belongs to that particular post.
+In addition, the code takes advantage of some of the methods available for an
+association. We use the +create+ method on +@post.comments+ to create and save
+the comment. This will automatically link the comment so that it belongs to that
+particular post.
-Once we have made the new comment, we send the user back to the original post using the +post_path(@post)+ helper. As we have already seen, this calls the +show+ action of the +PostsController+ which in turn renders the +show.html.erb+ template. This is where we want the comment to show, so let's add that to the +app/views/posts/show.html.erb+.
+Once we have made the new comment, we send the user back to the original post
+using the +post_path(@post)+ helper. As we have already seen, this calls the
++show+ action of the +PostsController+ which in turn renders the +show.html.erb+
+template. This is where we want the comment to show, so let's add that to the
++app/views/posts/show.html.erb+.
<erb>
<p class="notice"><%= notice %></p>
@@ -1039,15 +1373,20 @@ Once we have made the new comment, we send the user back to the original post us
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-Now you can add posts and comments to your blog and have them show up in the right places.
+Now you can add posts and comments to your blog and have them show up in the
+right places.
h3. Refactoring
-Now that we have Posts and Comments working, if we take a look at the +app/views/posts/show.html.erb+ template, it's getting long and awkward. We can use partials to clean this up.
+Now that we have Posts and Comments working, if we take a look at the
++app/views/posts/show.html.erb+ template, it's getting long and awkward. We can
+use partials to clean this up.
h4. Rendering Partial Collections
-First we will make a comment partial to extract showing all the comments for the post. Create the file +app/views/comments/_comment.html.erb+ and put the following into it:
+First we will make a comment partial to extract showing all the comments for the
+post. Create the file +app/views/comments/_comment.html.erb+ and put the
+following into it:
<erb>
<p>
@@ -1061,7 +1400,8 @@ First we will make a comment partial to extract showing all the comments for the
</p>
</erb>
-Then in the +app/views/posts/show.html.erb+ you can change it to look like the following:
+Then in the +app/views/posts/show.html.erb+ you can change it to look like the
+following:
<erb>
<p class="notice"><%= notice %></p>
@@ -1105,11 +1445,16 @@ Then in the +app/views/posts/show.html.erb+ you can change it to look like the f
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the <tt>@post.comments</tt> collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show.
+This will now render the partial in +app/views/comments/_comment.html.erb+ once
+for each comment that is in the +@post.comments+ collection. As the +render+
+method iterates over the <tt>@post.comments</tt> collection, it assigns each
+comment to a local variable named the same as the partial, in this case
++comment+ which is then available in the partial for us to show.
h4. Rendering a Partial Form
-Lets also move that new comment section out to it's own partial, again, you create a file +app/views/comments/_form.html.erb+ and in it you put:
+Lets also move that new comment section out to it's own partial, again, you
+create a file +app/views/comments/_form.html.erb+ and in it you put:
<erb>
<%= form_for([@post, @post.comments.build]) do |f| %>
@@ -1159,15 +1504,22 @@ Then you make the +app/views/posts/show.html.erb+ look like the following:
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-The second render just defines the partial template we want to render, <tt>comments/form</tt>, Rails is smart enough to spot the forward slash in that string and realize that you want to render the <tt>_form.html.erb</tt> file in the <tt>app/views/comments</tt> directory.
+The second render just defines the partial template we want to render,
+<tt>comments/form</tt>, Rails is smart enough to spot the forward slash in that
+string and realize that you want to render the <tt>_form.html.erb</tt> file in
+the <tt>app/views/comments</tt> directory.
-The +@post+ object is available to any partials rendered in the view because we defined it as an instance variable.
+The +@post+ object is available to any partials rendered in the view because we
+defined it as an instance variable.
h3. Deleting Comments
-Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+.
+Another important feature on a blog is being able to delete SPAM comments. To do
+this, we need to implement a link of some sort in the view and a +DELETE+ action
+in the +CommentsController+.
-So first, let's add the delete link in the +app/views/comments/_comment.html.erb+ partial:
+So first, let's add the delete link in the
++app/views/comments/_comment.html.erb+ partial:
<erb>
<p>
@@ -1187,7 +1539,10 @@ So first, let's add the delete link in the +app/views/comments/_comment.html.erb
</p>
</erb>
-Clicking this new "Destroy Comment" link will fire off a <tt>DELETE /posts/:id/comments/:id</tt> to our +CommentsController+, which can then use this to find the comment we want to delete, so let's add a destroy action to our controller:
+Clicking this new "Destroy Comment" link will fire off a <tt>DELETE
+/posts/:id/comments/:id</tt> to our +CommentsController+, which can then use
+this to find the comment we want to delete, so let's add a destroy action to our
+controller:
<ruby>
class CommentsController < ApplicationController
@@ -1208,12 +1563,17 @@ class CommentsController < ApplicationController
end
</ruby>
-The +destroy+ action will find the post we are looking at, locate the comment within the <tt>@post.comments</tt> collection, and then remove it from the database and send us back to the show action for the post.
+The +destroy+ action will find the post we are looking at, locate the comment
+within the <tt>@post.comments</tt> collection, and then remove it from the
+database and send us back to the show action for the post.
h4. Deleting Associated Objects
-If you delete a post then its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows:
+If you delete a post then its associated comments will also need to be deleted.
+Otherwise they would simply occupy space in the database. Rails allows you to
+use the +dependent+ option of an association to achieve this. Modify the Post
+model, +app/models/post.rb+, as follows:
<ruby>
class Post < ActiveRecord::Base
@@ -1226,13 +1586,20 @@ end
h3. Security
-If you were to publish your blog online, anybody would be able to add, edit and delete posts or delete comments.
+If you were to publish your blog online, anybody would be able to add, edit and
+delete posts or delete comments.
-Rails provides a very simple HTTP authentication system that will work nicely in this situation.
+Rails provides a very simple HTTP authentication system that will work nicely in
+this situation.
-In the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails <tt>http_basic_authenticate_with</tt> method, allowing access to the requested action if that method allows it.
+In the +PostsController+ we need to have a way to block access to the various
+actions if the person is not authenticated, here we can use the Rails
+<tt>http_basic_authenticate_with</tt> method, allowing access to the requested
+action if that method allows it.
-To use the authentication system, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that:
+To use the authentication system, we specify it at the top of our
++PostsController+, in this case, we want the user to be authenticated on every
+action, except for +index+ and +show+, so we write that:
<ruby>
class PostsController < ApplicationController
@@ -1247,7 +1614,8 @@ class PostsController < ApplicationController
# snipped for brevity
</ruby>
-We also only want to allow authenticated users to delete comments, so in the +CommentsController+ we write:
+We also only want to allow authenticated users to delete comments, so in the
++CommentsController+ we write:
<ruby>
class CommentsController < ApplicationController
@@ -1259,16 +1627,20 @@ class CommentsController < ApplicationController
# snipped for brevity
</ruby>
-Now if you try to create a new post, you will be greeted with a basic HTTP Authentication challenge
+Now if you try to create a new post, you will be greeted with a basic HTTP
+Authentication challenge
!images/challenge.png(Basic HTTP Authentication Challenge)!
-
h3. Building a Multi-Model Form
-Another feature of your average blog is the ability to tag posts. To implement this feature your application needs to interact with more than one model on a single form. Rails offers support for nested forms.
+Another feature of your average blog is the ability to tag posts. To implement
+this feature your application needs to interact with more than one model on a
+single form. Rails offers support for nested forms.
-To demonstrate this, we will add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags:
+To demonstrate this, we will add support for giving each post multiple tags,
+right in the form where you create the post. First, create a new model to hold
+the tags:
<shell>
$ rails generate model tag name:string post:references
@@ -1280,7 +1652,9 @@ Again, run the migration to create the database table:
$ rake db:migrate
</shell>
-Next, edit the +post.rb+ file to create the other side of the association, and to tell Rails (via the +accepts_nested_attributes_for+ macro) that you intend to edit tags via posts:
+Next, edit the +post.rb+ file to create the other side of the association, and
+to tell Rails (via the +accepts_nested_attributes_for+ macro) that you intend to
+edit tags via posts:
<ruby>
class Post < ActiveRecord::Base
@@ -1296,7 +1670,10 @@ class Post < ActiveRecord::Base
end
</ruby>
-The +:allow_destroy+ option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you'll build shortly. The +:reject_if+ option prevents saving new tags that do not have any attributes filled in.
+The +:allow_destroy+ option on the nested attribute declaration tells Rails to
+display a "remove" checkbox on the view that you'll build shortly. The
++:reject_if+ option prevents saving new tags that do not have any attributes
+filled in.
We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
@@ -1335,13 +1712,20 @@ We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
<% end %>
</erb>
-Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to make it easier to understand what is going on.
+Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to
+make it easier to understand what is going on.
-This example shows another option of the render helper, being able to pass in local variables, in this case, we want the local variable +form+ in the partial to refer to the +post_form+ object.
+This example shows another option of the render helper, being able to pass in
+local variables, in this case, we want the local variable +form+ in the partial
+to refer to the +post_form+ object.
-We also add a <tt>@post.tags.build</tt> at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create.
+We also add a <tt>@post.tags.build</tt> at the top of this form, this is to make
+sure there is a new tag ready to have it's name filled in by the user. If you do
+not build the new tag, then the form will not appear as there is no new Tag
+object ready to create.
-Now create the folder <tt>app/views/tags</tt> and make a file in there called <tt>_form.html.erb</tt> which contains the form for the tag:
+Now create the folder <tt>app/views/tags</tt> and make a file in there called
+<tt>_form.html.erb</tt> which contains the form for the tag:
<erb>
<%= form.fields_for :tags do |tag_form| %>
@@ -1358,7 +1742,8 @@ Now create the folder <tt>app/views/tags</tt> and make a file in there called <t
<% end %>
</erb>
-Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to show our tags.
+Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to
+show our tags.
<erb>
<p class="notice"><%= notice %></p>
@@ -1394,13 +1779,18 @@ Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to sho
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-With these changes in place, you'll find that you can edit a post and its tags directly on the same view.
+With these changes in place, you'll find that you can edit a post and its tags
+directly on the same view.
-However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is awkward, we could handle this by making a helper method.
+However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is
+awkward, we could handle this by making a helper method.
h3. View Helpers
-View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper.
+View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable
+code for views. In our case, we want a method that strings a bunch of objects
+together using their name attribute and joining them with a comma. As this is
+for the Post show template, we put it in the PostsHelper.
Open up <tt>app/helpers/posts_helper.rb</tt> and add the following:
@@ -1412,7 +1802,8 @@ module PostsHelper
end
</erb>
-Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like this:
+Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
+this:
<erb>
<p class="notice"><%= notice %></p>
@@ -1450,7 +1841,10 @@ Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
h3. What's Next?
-Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources:
+Now that you've seen your first Rails application, you should feel free to
+update it and experiment on your own. But you don't have to do everything
+without help. As you need assistance getting up and running with Rails, feel
+free to consult these support resources:
* The "Ruby on Rails guides":index.html
* The "Ruby on Rails Tutorial":http://railstutorial.org/book
@@ -1465,9 +1859,18 @@ Rails also comes with built-in help that you can generate using the rake command
h3. Configuration Gotchas
-The easiest way to work with Rails is to store all external data as UTF-8. If you don't, Ruby libraries and Rails will often be able to convert your native data into UTF-8, but this doesn't always work reliably, so you're better off ensuring that all external data is UTF-8.
+The easiest way to work with Rails is to store all external data as UTF-8. If
+you don't, Ruby libraries and Rails will often be able to convert your native
+data into UTF-8, but this doesn't always work reliably, so you're better off
+ensuring that all external data is UTF-8.
-If you have made a mistake in this area, the most common symptom is a black diamond with a question mark inside appearing in the browser. Another common symptom is characters like "ü" appearing instead of "ü". Rails takes a number of internal steps to mitigate common causes of these problems that can be automatically detected and corrected. However, if you have external data that is not stored as UTF-8, it can occasionally result in these kinds of issues that cannot be automatically detected by Rails and corrected.
+If you have made a mistake in this area, the most common symptom is a black
+diamond with a question mark inside appearing in the browser. Another common
+symptom is characters like "ü" appearing instead of "ü". Rails takes a number
+of internal steps to mitigate common causes of these problems that can be
+automatically detected and corrected. However, if you have external data that is
+not stored as UTF-8, it can occasionally result in these kinds of issues that
+cannot be automatically detected by Rails and corrected.
Two very common sources of data that are not UTF-8:
* Your text editor: Most text editors (such as Textmate), default to saving files as
@@ -1487,7 +1890,7 @@ h3. Changelog
* April 26, 2011: Change migration code from +up+, +down+ pair to +change+ method by "Prem Sichanugrist":http://sikachu.com
* April 11, 2011: Change scaffold_controller generator to create format block for JSON instead of XML by "Sebastian Martinez":http://www.wyeworks.com
-* August 30, 2010: Minor editing after Rails 3 release by "Joost Baaij":http://www.spacebabies.nl
+* August 30, 2010: Minor editing after Rails 3 release by Joost Baaij
* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
* May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com
* April 30, 2010: Fixes, editing and updating of code samples by "Rohit Arondekar":http://rohitarondekar.com
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index 0c8e4e974d..4b6b08bcec 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -1,4 +1,4 @@
-lh2. Rails Internationalization (I18n) API
+h2. Rails Internationalization (I18n) API
The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application.
@@ -796,7 +796,6 @@ h5. Active Support Methods
* +Array#to_sentence+ uses format settings as given in the "support.array":https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30 scope.
-
h3. Customize your I18n Setup
h4. Using Different Backends
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index b48488d8a2..684251962c 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -124,7 +124,7 @@ Ruby on Rails Guides
<p>This guide covers the basic configuration settings for a Rails application.</p>
<% end %>
-<%= guide("Rails Command Line Tools and Rake tasks", 'command_line.html', :work_in_progress => true) do %>
+<%= guide("Rails Command Line Tools and Rake tasks", 'command_line.html') do %>
<p>This guide covers the command line tools and rake tasks provided by Rails.</p>
<% end %>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 340699419b..9cc4dd5f04 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -1,6 +1,6 @@
h2. The Rails Initialization Process
-This guide explains the internals of the initialization process in Rails works as of Rails 3.1. It is an extremely in-depth guide and recommended for advanced Rails developers.
+This guide explains the internals of the initialization process in Rails as of Rails 3.1. It is an extremely in-depth guide and recommended for advanced Rails developers.
* Using +rails server+
* Using Passenger
@@ -33,7 +33,7 @@ end
This file will attempt to load +rails/cli+ and if it cannot find it then add the +railties/lib+ path to the load path (+$:+) and will then try to require it again.
-h4. +railites/lib/rails/cli.rb+
+h4. +railties/lib/rails/cli.rb+
This file looks like this:
@@ -71,7 +71,7 @@ module Rails
end
</ruby>
-The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +bin_dir+ and +ruby_install_name+ values for the configuration which will result in a path such as +/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby+, which is the default path on Mac OS X. If you're running Windows the path may be something such as +C:/Ruby192/bin/ruby+. Anyway, the path on your system may be different, but the point of this is that it will point at the known ruby executable location for your install. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ console, we'll see that when we get to the +in_rails_application?+ method.
+The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +bin_dir+ and +ruby_install_name+ values for the configuration which will result in a path such as +/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby+, which is the default path on Mac OS X. If you're running Windows the path may be something such as +C:/Ruby192/bin/ruby+. Anyway, the path on your system may be different, but the point of this is that it will point at the known ruby executable location for your install. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ constant, we'll see that when we get to the +in_rails_application?+ method.
Back in +rails/cli+, the next line is this:
@@ -190,7 +190,8 @@ aliases = {
"g" => "generate",
"c" => "console",
"s" => "server",
- "db" => "dbconsole"
+ "db" => "dbconsole",
+ "r" => "runner"
}
command = ARGV.shift
@@ -348,10 +349,10 @@ The class *is* defined in +Rack::Server+, but is overwritten in +Rails::Server+
def parse!(args)
args, options = args.dup, {}
- opt_parser = OptionParser.new do |opts|
- opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
- opts.on("-p", "--port=port", Integer,
- "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
+ opt_parser = OptionParser.new do |opts|
+ opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
+ opts.on("-p", "--port=port", Integer,
+ "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
...
</ruby>
@@ -512,7 +513,7 @@ h4. +railties/lib/rails/ruby_version_check.rb+
This file simply checks if the Ruby version is less than 1.8.7 or is 1.9.1 and raises an error if that is the case. Rails 3 simply will not run on earlier versions of Ruby than 1.8.7 or 1.9.1.
-NOTE: You should always endeavour to run the latest version of Ruby with your Rails applications. The benefits are many, including security fixes and the like, and very often there is a speed increase associated with it. The caveat is that you could have code that potentially breaks on the latest version, which should be fixed to work on the latest version rather than kept around as an excuse not to upgrade.
+NOTE: You should always endeavor to run the latest version of Ruby with your Rails applications. The benefits are many, including security fixes and the like, and very often there is a speed increase associated with it. The caveat is that you could have code that potentially breaks on the latest version, which should be fixed to work on the latest version rather than kept around as an excuse not to upgrade.
h4. +active_support/core_ext/kernel/reporting.rb+
@@ -760,7 +761,6 @@ def subclasses
end
</ruby>
-
The +config+ method used at the top of +I18n::Railtie+ is defined on +Rails::Railtie+ and is defined like this:
<ruby>
@@ -847,7 +847,7 @@ The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +A
The +initializers_chain+ method referenced in the +initializers_for+ method is defined like this:
-<rub>
+<ruby>
def initializers_chain
initializers = Collection.new
ancestors.reverse_each do | klass |
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index ba45b84242..87ba8ab82d 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -94,7 +94,7 @@ NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandler
h4. Using +render+
-In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customise the behaviour of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well.
+In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behaviour of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well.
TIP: If you want to see the exact results of a call to +render+ without needing to inspect it in a browser, you can call +render_to_string+. This method takes exactly the same options as +render+, but it returns a string instead of sending a response back to the browser.
@@ -1179,14 +1179,14 @@ On pages generated by +NewsController+, you want to hide the top menu and add a
<% end %>
<% content_for :content do %>
<div id="right_menu">Right menu items here</div>
- <%= yield(:news_content) or yield %>
+ <%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
<%= render :template => 'layouts/application' %>
</erb>
That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.
-There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :template => 'layouts/news'+ to base a new layout on the News layout. If you are sure you will not subtemplate the +News+ layout, you can replace the +yield(:news_content) or yield+ with simply +yield+.
+There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :template => 'layouts/news'+ to base a new layout on the News layout. If you are sure you will not subtemplate the +News+ layout, you can replace the +content_for?(:news_content) ? yield(:news_content) : yield+ with simply +yield+.
h3. Changelog
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index dbbf8f3b51..6fcc3cf4a2 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -17,7 +17,7 @@ endprologue.
h3. 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:
+Before we dive into the details of a migration, here are a few examples of the sorts of things you can do:
<ruby>
class CreateProducts < ActiveRecord::Migration
@@ -117,6 +117,33 @@ Occasionally you will make a mistake when writing a migration. If you have alrea
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.
+h4. Supported Types
+
+Active Record supports the following types:
+
+* +: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
+
+<ruby>
+create_table :products do |t|
+ t.column :name, 'polygon', :null => false
+end
+</ruby>
+
+This may however hinder portability to other databases.
+
h3. Creating a Migration
h4. Creating a Model
@@ -261,18 +288,6 @@ end
will append +ENGINE=BLACKHOLE+ to the SQL statement used to create the table (when using MySQL the default is +ENGINE=InnoDB+).
-The types supported by Active Record 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
-
-<ruby>
-create_table :products do |t|
- t.column :name, 'polygon', :null => false
-end
-</ruby>
-
-This may however hinder portability to other databases.
-
h4. Changing Tables
A close cousin of +create_table+ 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
@@ -285,6 +300,7 @@ change_table :products do |t|
t.rename :upccode, :upc_code
end
</ruby>
+
removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing
<ruby>
@@ -456,8 +472,8 @@ By default migrations tell you exactly what they're doing and how long it took.
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)
+* +suppress_messages+ takes a block as an argument and suppresses any output generated by the block.
+* +say+ takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent 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
@@ -495,41 +511,108 @@ generates the following output
20080906170109 CreateProducts: migrated (10.0097s)
</shell>
-If you just want Active Record to shut up then running +rake db:migrate VERBOSE=false+ will suppress any output.
+If you just want Active Record to shut up then running +rake db:migrate VERBOSE=false+ will suppress all output.
h3. 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.
+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.
+For example, problems occur when the model uses database columns which are (1) not currently in the database and (2) will be created by this or a subsequent migration.
-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:
+Consider this example, where Alice and Bob are working on the same code base which contains a +Product+ model:
+
+Bob goes on vacation.
+
+Alice creates a migration for the +products+ table which adds a new column and initializes it.
+She also adds a validation to the Product model for the new column.
<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
- class Product < ActiveRecord::Base
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
+ def change
+ add_column :products, :flag, :int
+ Product.all.each { |f| f.update_attributes!(:flag => 'false') }
end
+end
+</ruby>
+<ruby>
+# app/model/product.rb
+
+class Product < ActiveRecord::Base
+ validates :flag, :presence => true
+end
+</ruby>
+
+Alice adds a second migration which adds and initializes another column to the +products+ table and also adds a validation to the Product model for the new column.
+
+<ruby>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
def change
- ...
+ add_column :products, :fuzz, :string
+ Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
</ruby>
-The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application.
-h4. Dealing with Changing Models
+<ruby>
+# app/model/product.rb
+
+class Product < ActiveRecord::Base
+ validates :flag, :fuzz, :presence => true
+end
+</ruby>
+
+Both migrations work for Alice.
+
+Bob comes back from vacation and:
+
+# updates the source - which contains both migrations and the latests version of the Product model.
+# runs outstanding migrations with +rake db:migrate+, which includes the one that updates the +Product+ model.
+
+The migration crashes because when the model attempts to save, it tries to validate the second added column, which is not in the database when the _first_ migration runs.
-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 to use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example
+<plain>
+rake aborted!
+An error has occurred, this and all later migrations canceled:
+
+undefined method `fuzz' for #<Product:0x000001049b14a0>
+</plain>
+
+A fix for this is to create a local model within the migration. This keeps rails from running the validations, so that the migrations run to completion.
+
+When using a faux model, it's a good idea to call +Product.reset_column_information+ to refresh the ActiveRecord cache for the Product model prior to updating data in the database.
+
+If Alice had done this instead, there would have been no problem:
<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
- add_column :product, :part_number, :string
+ add_column :products, :flag, :int
+ Product.reset_column_information
+ Product.all.each { |f| f.update_attributes!(:flag => false) }
+ end
+end
+</ruby>
+
+<ruby>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
+ class Product < ActiveRecord::Base
+ end
+ def change
+ add_column :products, :fuzz, :string
Product.reset_column_information
- ...
+ Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
</ruby>
diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile
index dbe6f97f5c..75f81cf13d 100644
--- a/railties/guides/source/performance_testing.textile
+++ b/railties/guides/source/performance_testing.textile
@@ -438,9 +438,9 @@ alias gcrails='~/rubygc/bin/rails'
Don't forget to use your aliases from now on.
-h6. Install Rubygems (1.8 only!)
+h6. Install RubyGems (1.8 only!)
-Download "Rubygems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions. Please note that this step isn't necessary if you've installed Ruby 1.9 and above.
+Download "RubyGems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions. Please note that this step isn't necessary if you've installed Ruby 1.9 and above.
h4. Using Ruby-Prof on MRI and REE
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index 79bbe495bd..d3f9783fa6 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -86,7 +86,7 @@ class CoreExtTest < Test::Unit::TestCase
end
</ruby>
-Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method:
+Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squawk+ method:
<shell>
1) Error:
@@ -218,8 +218,8 @@ test/dummy directory:
<shell>
$ cd test/dummy
-$ rails generate model Hickwall last_squak:string
-$ rails generate model Wickwall last_squak:string last_tweet:string
+$ rails generate model Hickwall last_squawk:string
+$ rails generate model Wickwall last_squawk:string last_tweet:string
</shell>
Now you can create the necessary database tables in your testing database by navigating to your dummy app
@@ -386,6 +386,7 @@ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
</ruby>
Run +rake+ one final time and you should see:
+
<shell>
7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
</shell>
@@ -426,6 +427,7 @@ require 'yaffle'
You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the
console we can check to see if the String has an instance method of to_squawk.
+
<shell>
$ cd my_app
$ rails console
diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile
index 3db47a70e8..566f8a0bdd 100644
--- a/railties/guides/source/rails_application_templates.textile
+++ b/railties/guides/source/rails_application_templates.textile
@@ -11,22 +11,18 @@ endprologue.
h3. Usage
-To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option:
+To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option. This can either be path to a file or a URL.
<shell>
$ rails new blog -m ~/template.rb
+$ rails new blog -m http://example.com/template.rb
</shell>
-It's also possible to apply a template using a URL:
-
-<shell>
-$ rails new blog -m https://gist.github.com/755496.txt
-</shell>
-
-Alternatively, you can use the rake task +rails:template+ to apply a template to an existing Rails application:
+You can use the rake task +rails:template+ to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
<shell>
$ rake rails:template LOCATION=~/template.rb
+$ rake rails:template LOCATION=http://example.com/template.rb
</shell>
h3. Template API
@@ -152,7 +148,7 @@ The above creates +lib/tasks/bootstrap.rake+ with a +boot:strap+ rake task.
h4. generate(what, args)
-Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails:
+Runs the supplied rails generator with given arguments.
<ruby>
generate(:scaffold, "person", "name:string", "address:text", "age:number")
@@ -180,12 +176,6 @@ You can also run rake tasks with a different Rails environment:
rake "db:migrate", :env => 'production'
</ruby>
-Or even use sudo:
-
-<ruby>
-rake "gems:install", :sudo => true
-</ruby>
-
h4. route(routing_code)
This adds a routing entry to the +config/routes.rb+ file. In above steps, we generated a person scaffold and also removed +public/index.html+. Now to make +PeopleController#index+ as the default page for the application:
diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile
index 8d5985dba8..818df0ffaf 100644
--- a/railties/guides/source/rails_on_rack.textile
+++ b/railties/guides/source/rails_on_rack.textile
@@ -89,23 +89,32 @@ $ rake middleware
For a freshly generated Rails application, this might produce something like:
<ruby>
+use ActionDispatch::Static
use Rack::Lock
-use ActionController::Failsafe
-use ActionController::Session::CookieStore, , {:secret=>"<secret>", :session_key=>"_<app>_session"}
-use Rails::Rack::Metal
-use ActionDispatch::RewindableInput
-use ActionController::ParamsParser
-use Rack::MethodOverride
-use Rack::Head
+use ActiveSupport::Cache::Strategy::LocalCache
+use Rack::Runtime
+use Rails::Rack::Logger
+use ActionDispatch::ShowExceptions
+use ActionDispatch::RemoteIp
+use Rack::Sendfile
+use ActionDispatch::Callbacks
+use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
-run ActionController::Dispatcher.new
+use ActionDispatch::Cookies
+use ActionDispatch::Session::CookieStore
+use ActionDispatch::Flash
+use ActionDispatch::ParamsParser
+use Rack::MethodOverride
+use ActionDispatch::Head
+use ActionDispatch::BestStandardsSupport
+run Blog::Application.routes
</ruby>
Purpose of each of this middlewares is explained in the "Internal Middlewares":#internal-middleware-stack section.
h4. Configuring Middleware Stack
-Rails provides a simple configuration interface +config.middleware+ for adding, removing and modifying the middlewares in the middleware stack via +environment.rb+ or the environment specific configuration file <tt>environments/&lt;environment&gt;.rb</tt>.
+Rails provides a simple configuration interface +config.middleware+ for adding, removing and modifying the middlewares in the middleware stack via +application.rb+ or the environment specific configuration file <tt>environments/&lt;environment&gt;.rb</tt>.
h5. Adding a Middleware
@@ -118,7 +127,7 @@ You can add a new middleware to the middleware stack using any of the following
* <tt>config.middleware.insert_after(existing_middleware, new_middleware, args)</tt> - Adds the new middleware after the specified existing middleware in the middleware stack.
<ruby>
-# config/environment.rb
+# config/application.rb
# Push Rack::BounceFavicon at the bottom
config.middleware.use Rack::BounceFavicon
@@ -133,7 +142,7 @@ h5. Swapping a Middleware
You can swap an existing middleware in the middleware stack using +config.middleware.swap+.
<ruby>
-# config/environment.rb
+# config/application.rb
# Replace ActionController::Failsafe with Lifo::Failsafe
config.middleware.swap ActionController::Failsafe, Lifo::Failsafe
@@ -198,7 +207,7 @@ The following shows how to replace use +Rack::Builder+ instead of the Rails supp
<strong>Clear the existing Rails middleware stack</strong>
<ruby>
-# environment.rb
+# config/application.rb
config.middleware.clear
</ruby>
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 1cbc5c8f6e..99dd9a1cd2 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -68,7 +68,7 @@ Rails would dispatch that request to the +destroy+ method on the +photos+ contro
h4. CRUD, Verbs, and Actions
-In Rails, a resourceful route provides a mapping between HTTP verbs and URLs and controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as
+In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as
<ruby>
resources :photos
@@ -94,8 +94,8 @@ Creating a resourceful route will also expose a number of helpers to the control
* +photos_path+ returns +/photos+
* +new_photo_path+ returns +/photos/new+
-* +edit_photo_path(id)+ returns +/photos/:id/edit+ (for instance, +edit_photo_path(10)+ returns +/photos/10/edit+)
-* +photo_path(id)+ returns +/photos/:id+ (for instance, +photo_path(10)+ returns +/photos/10+)
+* +edit_photo_path(:id)+ returns +/photos/:id/edit+ (for instance, +edit_photo_path(10)+ returns +/photos/10/edit+)
+* +photo_path(:id)+ returns +/photos/:id+ (for instance, +photo_path(10)+ returns +/photos/10+)
Each of these helpers has a corresponding +_url+ helper (such as +photos_url+) which returns the same path prefixed with the current host, port and path prefix.
@@ -163,14 +163,14 @@ end
This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
-|_.HTTP Verb |_.Path |_.action |_.named helper |
-|GET |/admin/posts |index | admin_posts_path |
-|GET |/admin/posts/new |new | new_admin_posts_path |
-|POST |/admin/posts |create | admin_posts_path |
-|GET |/admin/posts/1 |show | admin_post_path(id) |
-|GET |/admin/posts/1/edit |edit | edit_admin_post_path(id) |
-|PUT |/admin/posts/1 |update | admin_post_path(id) |
-|DELETE |/admin/posts/1 |destroy | admin_post_path(id) |
+|_.HTTP Verb |_.Path |_.action |_.named helper |
+|GET |/admin/posts |index | admin_posts_path |
+|GET |/admin/posts/new |new | new_admin_post_path |
+|POST |/admin/posts |create | admin_posts_path |
+|GET |/admin/posts/:id |show | admin_post_path(:id) |
+|GET |/admin/posts/:id/edit |edit | edit_admin_post_path(:id) |
+|PUT |/admin/posts/:id |update | admin_post_path(:id) |
+|DELETE |/admin/posts/:id |destroy | admin_post_path(:id) |
If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
@@ -204,12 +204,12 @@ In each of these cases, the named routes remain the same as if you did not use +
|_.HTTP Verb |_.Path |_.action |_.named helper |
|GET |/admin/posts |index | posts_path |
-|GET |/admin/posts/new |new | posts_path |
+|GET |/admin/posts/new |new | new_post_path |
|POST |/admin/posts |create | posts_path |
-|GET |/admin/posts/1 |show | post_path(id) |
-|GET |/admin/posts/1/edit |edit | edit_post_path(id) |
-|PUT |/admin/posts/1 |update | post_path(id) |
-|DELETE |/admin/posts/1 |destroy | post_path(id) |
+|GET |/admin/posts/:id |show | post_path(:id) |
+|GET |/admin/posts/:id/edit|edit | edit_post_path(:id)|
+|PUT |/admin/posts/:id |update | post_path(:id) |
+|DELETE |/admin/posts/:id |destroy | post_path(:id) |
h4. Nested Resources
@@ -236,13 +236,13 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
|_.HTTP Verb |_.Path |_.action |_.used for |
-|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine |
-|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
-|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine |
-|GET |/magazines/1/ads/1 |show |display a specific ad belonging to a specific magazine |
-|GET |/magazines/1/ads/1/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
-|PUT |/magazines/1/ads/1 |update |update a specific ad belonging to a specific magazine |
-|DELETE |/magazines/1/ads/1 |destroy |delete a specific ad belonging to a specific magazine |
+|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
+|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
+|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
+|GET |/magazines/:id/ads/:id |show |display a specific ad belonging to a specific magazine |
+|GET |/magazines/:id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
+|PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
+|DELETE |/magazines/:id/ads/:id |destroy |delete a specific ad belonging to a specific magazine |
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
@@ -560,13 +560,19 @@ would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +par
NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route:
<ruby>
-map '*pages' => 'pages#show'
+match '*pages' => 'pages#show'
</ruby>
NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this:
<ruby>
-map '*pages' => 'pages#show', :format => false
+match '*pages' => 'pages#show', :format => false
+</ruby>
+
+NOTE: If you want to make the format segment mandatory, so it cannot be omitted, you can supply +:format => true+ like this:
+
+<ruby>
+match '*pages' => 'pages#show', :format => true
</ruby>
h4. Redirection
@@ -628,16 +634,16 @@ resources :photos, :controller => "images"
will recognize incoming paths beginning with +/photos+ but route to the +Images+ controller:
-|_.HTTP Verb |_.Path |_.action |_.named helper |
-|GET |/photos |index | photos_path |
-|GET |/photos/new |new | new_photo_path |
-|POST |/photos |create | photos_path |
-|GET |/photos/1 |show | photo_path(id) |
-|GET |/photos/1/edit |edit | edit_photo_path(id) |
-|PUT |/photos/1 |update | photo_path(id) |
-|DELETE |/photos/1 |destroy | photo_path(id) |
+|_.HTTP Verb |_.Path |_.action |_.named helper |
+|GET |/photos |index | photos_path |
+|GET |/photos/new |new | new_photo_path |
+|POST |/photos |create | photos_path |
+|GET |/photos/:id |show | photo_path(:id) |
+|GET |/photos/:id/edit |edit | edit_photo_path(:id) |
+|PUT |/photos/:id |update | photo_path(:id) |
+|DELETE |/photos/:id |destroy | photo_path(:id) |
-NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
+NOTE: Use +photos_path+, +new_photo_path+, etc. to generate paths for this resource.
h4. Specifying Constraints
@@ -672,14 +678,14 @@ resources :photos, :as => "images"
will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+, but use the value of the :as option to name the helpers.
-|_.HTTP verb|_.Path |_.action |_.named helper |
-|GET |/photos |index | images_path |
-|GET |/photos/new |new | new_image_path |
-|POST |/photos |create | images_path |
-|GET |/photos/1 |show | image_path(id) |
-|GET |/photos/1/edit |edit | edit_image_path(id) |
-|PUT |/photos/1 |update | image_path(id) |
-|DELETE |/photos/1 |destroy | image_path(id) |
+|_.HTTP verb|_.Path |_.action |_.named helper |
+|GET |/photos |index | images_path |
+|GET |/photos/new |new | new_image_path |
+|POST |/photos |create | images_path |
+|GET |/photos/:id |show | image_path(:id) |
+|GET |/photos/:id/edit |edit | edit_image_path(:id) |
+|PUT |/photos/:id |update | image_path(:id) |
+|DELETE |/photos/:id |destroy | image_path(:id) |
h4. Overriding the +new+ and +edit+ Segments
@@ -776,14 +782,14 @@ end
Rails now creates routes to the +CategoriesController+.
-|_.HTTP verb|_.Path |_.action |_.named helper |
-|GET |/kategorien |index | categories_path |
-|GET |/kategorien/neu |new | new_category_path |
-|POST |/kategorien |create | categories_path |
-|GET |/kategorien/1 |show | category_path(id) |
-|GET |/kategorien/1/bearbeiten |edit | edit_category_path(id) |
-|PUT |/kategorien/1 |update | category_path(id) |
-|DELETE |/kategorien/1 |destroy | category_path(id) |
+|_.HTTP verb|_.Path |_.action |_.named helper |
+|GET |/kategorien |index | categories_path |
+|GET |/kategorien/neu |new | new_category_path |
+|POST |/kategorien |create | categories_path |
+|GET |/kategorien/:id |show | category_path(:id) |
+|GET |/kategorien/:id/bearbeiten |edit | edit_category_path(:id) |
+|PUT |/kategorien/:id |update | category_path(:id) |
+|DELETE |/kategorien/:id |destroy | category_path(:id) |
h4. Overriding the Singular Form
@@ -823,10 +829,10 @@ If you want a complete list of all of the available routes in your application,
For example, here's a small section of the +rake routes+ output for a RESTful route:
<pre>
- 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"}
+ users GET /users(.:format) users#index
+ POST /users(.:format) users#create
+ new_user GET /users/new(.:format) users#new
+edit_user GET /users/:id/edit(.:format) users#edit
</pre>
You may restrict the listing to the routes that map to a particular controller setting the +CONTROLLER+ environment variable:
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 908f3f125a..04d1d0bda8 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -15,7 +15,7 @@ endprologue.
h3. Introduction
-Web application frameworks are made to help developers building web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem. It‘s nice to see that all of the Rails applications I audited had a good level of security.
+Web application frameworks are made to help developers building web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem. It's nice to see that all of the Rails applications I audited had a good level of security.
In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications).
@@ -23,7 +23,7 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati
The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at.
-In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that‘s how you find the nasty logical security problems.
+In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that's how you find the nasty logical security problems.
h3. Sessions
@@ -80,7 +80,6 @@ This will also be a good idea, if you modify the structure of an object and old
* _(highlight)Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
-
h4. Session Storage
-- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecordStore and CookieStore._
@@ -209,7 +208,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST (
* The interaction _(highlight)changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or
* The user is _(highlight)held accountable for the results_ of the interaction.
-If your web application is RESTful, you might be used to additional HTTP verbs, such as PUT or DELETE. Most of today‘s web browsers, however do not support them - only GET and POST. Rails uses a hidden +_method+ field to handle this barrier.
+If your web application is RESTful, you might be used to additional HTTP verbs, such as PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden +_method+ field to handle this barrier.
_(highlight)POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.
@@ -617,7 +616,7 @@ This is alright for some web applications, but certainly not if the user is not
Depending on your web application, there will be many more parameters the user can tamper with. As a rule of thumb, _(highlight)no user input data is secure, until proven otherwise, and every parameter from the user is potentially manipulated_.
-Don‘t be fooled by security by obfuscation and JavaScript security. The Web Developer Toolbar for Mozilla Firefox lets you review and change every form's hidden fields. _(highlight)JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Live Http Headers plugin for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet.
+Don't be fooled by security by obfuscation and JavaScript security. The Web Developer Toolbar for Mozilla Firefox lets you review and change every form's hidden fields. _(highlight)JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Live Http Headers plugin for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet.
h3. Injection
@@ -825,7 +824,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte
&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#39;&amp;#88;&amp;#83;&amp;#83;&amp;#39;&amp;#41;>
</html>
-This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails‘ sanitize() method does a good job to fend off encoding attacks.
+This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails' sanitize() method does a good job to fend off encoding attacks.
h5. Examples from the Underground
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index df92934457..73bdd0b552 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -4,6 +4,7 @@ require 'pathname'
require 'active_support'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/logger'
require 'rails/application'
@@ -14,15 +15,17 @@ require 'action_dispatch/railtie'
# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
# multibyte safe operations. Plugin authors supporting other encodings
-# should override this behaviour and set the relevant +default_charset+
+# should override this behavior and set the relevant +default_charset+
# on ActionController::Base.
#
# For Ruby 1.9, UTF-8 is the default internal and external encoding.
if RUBY_VERSION < '1.9'
$KCODE='u'
else
- Encoding.default_external = Encoding::UTF_8
- Encoding.default_internal = Encoding::UTF_8
+ silence_warnings do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_8
+ end
end
module Rails
@@ -91,7 +94,7 @@ module Rails
#
# * The Rails environment;
# * The environment variable RAILS_GROUPS;
- # * The optional hash given as argument with group dependencies;
+ # * The optional envs given as argument and the hash with group dependencies;
#
# == Examples
#
@@ -101,12 +104,14 @@ module Rails
# # => [:default, :development, :assets] for Rails.env == "development"
# # => [:default, :production] for Rails.env == "production"
#
- def groups(hash={})
+ def groups(*groups)
+ hash = groups.extract_options!
env = Rails.env
- groups = [:default, env]
+ groups.unshift(:default, env)
groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) }
groups.compact!
+ groups.uniq!
groups
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index fe29668c72..528c96ef3e 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -11,7 +11,7 @@ module Rails
# == Initialization
#
# Rails::Application is responsible for executing all railties, engines and plugin
- # initializers. Besides, it also executed some bootstrap initializers (check
+ # initializers. It also executes some bootstrap initializers (check
# Rails::Application::Bootstrap) and finishing initializers, after all the others
# are executed (check Rails::Application::Finisher).
#
@@ -55,6 +55,11 @@ module Rails
delegate :default_url_options, :default_url_options=, :to => :routes
+ def initialize
+ super
+ @initialized = false
+ end
+
# This method is called just after an application inherits from Rails::Application,
# allowing the developer to load classes in lib and use them during application
# configuration.
@@ -106,6 +111,15 @@ module Rails
self
end
+ # Rails.application.env_config stores some of the Rails initial environment parameters.
+ # Currently stores:
+ #
+ # * action_dispatch.parameter_filter" => config.filter_parameters,
+ # * action_dispatch.secret_token" => config.secret_token,
+ # * action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
+ #
+ # These parameters will be used by middlewares and engines to configure themselves.
+ #
def env_config
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
@@ -154,7 +168,9 @@ module Rails
middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
- middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
+ if config.action_dispatch.x_sendfile_header.present?
+ middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
+ end
middleware.use ::ActionDispatch::Reloader unless config.cache_classes
middleware.use ::ActionDispatch::Callbacks
middleware.use ::ActionDispatch::Cookies
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9f21d273e6..c9b147d075 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -28,7 +28,7 @@ module Rails
logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
logger.auto_flushing = false if Rails.env.production?
logger
- rescue StandardError => e
+ rescue StandardError
logger = ActiveSupport::BufferedLogger.new(STDERR)
logger.level = ActiveSupport::BufferedLogger::WARN
logger.warn(
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 1a29483a73..5d7bd3282d 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/string/encoding'
+require 'active_support/core_ext/kernel/reporting'
require 'rails/engine/configuration'
module Rails
@@ -6,12 +7,13 @@ module Rails
class Configuration < ::Rails::Engine::Configuration
attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
:cache_classes, :cache_store, :consider_all_requests_local,
- :dependency_loading, :encoding, :filter_parameters,
+ :dependency_loading, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :preload_frameworks,
:reload_plugins, :secret_token, :serve_static_assets,
:static_cache_control, :session_options, :time_zone, :whiny_nils
attr_writer :log_level
+ attr_reader :encoding
def initialize(*)
super
@@ -30,13 +32,18 @@ module Rails
@log_level = nil
@middleware = app_middleware
@generators = app_generators
+ @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
@assets = ActiveSupport::OrderedOptions.new
- @assets.enabled = false
- @assets.paths = []
- @assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
- @assets.prefix = "/assets"
+ @assets.enabled = false
+ @assets.paths = []
+ @assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
+ @assets.prefix = "/assets"
+ @assets.version = ''
+ @assets.debug = false
+ @assets.allow_debugging = false
+ @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/" ]
@assets.js_compressor = nil
@assets.css_compressor = nil
end
@@ -48,8 +55,10 @@ module Rails
def encoding=(value)
@encoding = value
if "ruby".encoding_aware?
- Encoding.default_external = value
- Encoding.default_internal = value
+ silence_warnings do
+ Encoding.default_external = value
+ Encoding.default_internal = value
+ end
else
$KCODE = value
if $KCODE == "NONE"
@@ -70,7 +79,6 @@ module Rails
paths.add "public/javascripts"
paths.add "public/stylesheets"
paths.add "tmp"
- paths.add "tmp/cache"
paths
end
end
@@ -95,16 +103,6 @@ module Rails
YAML::load(ERB.new(IO.read(paths["config/database"].first)).result)
end
- def cache_store
- @cache_store ||= begin
- if File.exist?("#{root}/tmp/cache/")
- [ :file_store, "#{root}/tmp/cache/" ]
- else
- :memory_store
- end
- end
- end
-
def log_level
@log_level ||= Rails.env.production? ? :info : :debug
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 770c23ae41..e6822b75b7 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -30,7 +30,7 @@ class CodeStatistics #:nodoc:
stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
Dir.foreach(directory) do |file_name|
- if File.stat(directory + "/" + file_name).directory? and (/^\./ !~ file_name)
+ if File.directory?(directory + "/" + file_name) and (/^\./ !~ file_name)
newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
stats.each { |k, v| stats[k] += newstats[k] }
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 5e55aeada9..ada150ceec 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -4,6 +4,7 @@ ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
+ "d" => "destroy",
"c" => "console",
"s" => "server",
"db" => "dbconsole",
@@ -87,13 +88,13 @@ The most common rails commands are:
In addition to those, there are:
application Generate the Rails application code
- destroy Undo code generated with "generate"
+ destroy Undo code generated with "generate" (short-cut alias: "d")
benchmarker See how fast a piece of code runs
profiler Get profile information from a piece of code
plugin Install a plugin
runner Run a piece of code in the application environment (short-cut alias: "r")
-All commands can be run with -h for more information.
+All commands can be run with -h (or --help) for more information.
EOT
exit(1)
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index eb6fcd5dd7..89b151beb6 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -5,13 +5,14 @@ require 'rbconfig'
require 'rails/engine/railties'
module Rails
- # Rails::Engine allows you to wrap a specific Rails application and share it across
- # different applications. Since Rails 3.0, every <tt>Rails::Application</tt> is nothing
- # more than an engine, allowing you to share it very easily.
+ # <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
+ # functionality and share it with other applications. Since Rails 3.0, every
+ # <tt>Rails::Application</tt> is just an engine, which allows for simple
+ # feature and application sharing.
#
- # Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same methods
- # (like <tt>rake_tasks</tt> and +generators+) and configuration available in the
- # latter can also be used in the former.
+ # Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same
+ # methods (like <tt>rake_tasks</tt> and +generators+) and configuration
+ # options that are available in railties can also be used in engines.
#
# == Creating an Engine
#
@@ -71,12 +72,13 @@ module Rails
#
# == Paths
#
- # Since Rails 3.0, both your application and engines do not have hardcoded paths.
- # This means that you are not required to place your controllers at <tt>app/controllers</tt>,
- # but in any place which you find convenient.
+ # Since Rails 3.0, applications and engines have more flexible path configuration (as
+ # opposed to the previous hardcoded path configuration). This means that you are not
+ # required to place your controllers at <tt>app/controllers</tt>, but in any place
+ # which you find convenient.
#
# For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>.
- # All you would need to do is:
+ # You can set that as an option:
#
# class MyEngine < Rails::Engine
# paths["app/controllers"] = "lib/controllers"
@@ -105,9 +107,9 @@ module Rails
# paths["config/routes"] # => ["config/routes.rb"]
# end
#
- # Your <tt>Application</tt> class adds a couple more paths to this set. And as in your
- # <tt>Application</tt>,all folders under +app+ are automatically added to the load path.
- # So if you have <tt>app/observers</tt>, it's added by default.
+ # The <tt>Application</tt> class adds a couple more paths to this set. And as in your
+ # <tt>Application</tt>, all folders under +app+ are automatically added to the load path.
+ # If you have an <tt>app/observers</tt> folder for example, it will be added by default.
#
# == Endpoint
#
@@ -130,8 +132,8 @@ module Rails
#
# == Middleware stack
#
- # As an engine can now be rack endpoint, it can also have a middleware stack. The usage is exactly
- # the same as in <tt>Application</tt>:
+ # As an engine can now be a rack endpoint, it can also have a middleware
+ # stack. The usage is exactly the same as in <tt>Application</tt>:
#
# module MyEngine
# class Engine < Rails::Engine
@@ -141,8 +143,8 @@ module Rails
#
# == Routes
#
- # If you don't specify an endpoint, routes will be used as the default endpoint. You can use them
- # just like you use an application's routes:
+ # If you don't specify an endpoint, routes will be used as the default
+ # endpoint. You can use them just like you use an application's routes:
#
# # ENGINE/config/routes.rb
# MyEngine::Engine.routes.draw do
@@ -174,6 +176,7 @@ module Rails
# == Engine name
#
# There are some places where an Engine's name is used:
+ #
# * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>,
# it's used as default :as option
# * some of the rake tasks are based on engine name, e.g. <tt>my_engine:install:migrations</tt>,
@@ -191,8 +194,8 @@ module Rails
# == Isolated Engine
#
# Normally when you create controllers, helpers and models inside an engine, they are treated
- # as they were created inside the application. This means all application helpers and named routes
- # will be available to your engine's controllers.
+ # as if they were created inside the application itself. This means that all helpers and
+ # named routes from the application will be available to your engine's controllers as well.
#
# However, sometimes you want to isolate your engine from the application, especially if your engine
# has its own router. To do that, you simply need to call +isolate_namespace+. This method requires
@@ -217,7 +220,7 @@ module Rails
# If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and
# <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
#
- # The next thing that changes in isolated engines is the behaviour of routes. Normally, when you namespace
+ # The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
# your controllers, you also need to do namespace all your routes. With an isolated engine,
# the namespace is applied by default, so you can ignore it in routes:
#
@@ -229,7 +232,7 @@ module Rails
# need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
# <tt>articles_path</tt> as you would do with your application.
#
- # To make that behaviour consistent with other parts of the framework, an isolated engine also has influence on
+ # To make that behavior consistent with other parts of the framework, an isolated engine also has influence on
# <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally
# use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
# form fields for convenience.
@@ -240,9 +243,9 @@ module Rails
# text_field :title # => <input type="text" name="article[title]" id="article_title" />
# end
#
- # Additionally an isolated engine will set its name according to namespace, so
+ # Additionally, an isolated engine will set its name according to namespace, so
# MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
- # to "my_engine_", changing MyEngine::Article model to use my_engine_article table.
+ # to "my_engine_", changing the MyEngine::Article model to use the my_engine_article table.
#
# == Using Engine's routes outside Engine
#
@@ -274,12 +277,13 @@ module Rails
# end
# end
#
- # Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tT> as default, so most of the time
+ # Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tt> as default, so most of the time
# you can simply omit it.
#
- # Finally, if you want to generate a url to an engine's route using <tt>polymorphic_url</tt>, you also need
- # to pass the engine helper. Let's say that you want to create a form pointing to one of the
- # engine's routes. All you need to do is pass the helper as the first element in array with
+ # Finally, if you want to generate a url to an engine's route using
+ # <tt>polymorphic_url</tt>, you also need to pass the engine helper. Let's
+ # say that you want to create a form pointing to one of the engine's routes.
+ # All you need to do is pass the helper as the first element in array with
# attributes for url:
#
# form_for([my_engine, @user])
@@ -319,7 +323,7 @@ module Rails
#
# Note that some of the migrations may be skipped if a migration with the same name already exists
# in application. In such a situation you must decide whether to leave that migration or rename the
- # migration in application and rerun copying migrations.
+ # migration in the application and rerun copying migrations.
#
# If your engine has migrations, you may also want to prepare data for the database in
# the <tt>seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
@@ -356,6 +360,7 @@ module Rails
end
def endpoint(endpoint = nil)
+ @endpoint ||= nil
@endpoint = endpoint if endpoint
@endpoint
end
@@ -400,15 +405,15 @@ module Rails
super
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
-
+
def load_console(app=self)
railties.all { |r| r.load_console(app) }
super
end
-
+
def eager_load!
railties.all(&:eager_load!)
-
+
config.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
@@ -538,9 +543,9 @@ module Rails
end
initializer :append_assets_path do |app|
- app.config.assets.paths.unshift(*paths["vendor/assets"].existent)
- app.config.assets.paths.unshift(*paths["lib/assets"].existent)
- app.config.assets.paths.unshift(*paths["app/assets"].existent)
+ app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
+ app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
+ app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
end
initializer :prepend_helpers_path do |app|
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index 3b0920e213..b71119af77 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -3,7 +3,8 @@ require 'active_support/core_ext/object/inclusion'
ARGV << '--help' if ARGV.empty?
aliases = {
- "g" => "generate"
+ "g" => "generate",
+ "d" => "destroy"
}
command = ARGV.shift
@@ -30,7 +31,7 @@ Usage: rails COMMAND [ARGS]
The common rails commands available for engines are:
generate Generate new code (short-cut alias: "g")
- destroy Undo code generated with "generate"
+ destroy Undo code generated with "generate" (short-cut alias: "d")
All commands can be run with -h for more information.
EOT
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index fdafc91fcb..27f8d13ce8 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -77,7 +77,7 @@ module Rails
fallbacks.merge! config.fallbacks
templates_path.concat config.templates
templates_path.uniq!
- hide_namespaces *config.hidden_namespaces
+ hide_namespaces(*config.hidden_namespaces)
end
def self.templates_path
@@ -313,7 +313,7 @@ module Rails
begin
path = path.sub("#{base}/", "")
require path
- rescue Exception => e
+ rescue Exception
# No problem
end
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 433a56dc57..b8541c236e 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -92,14 +92,15 @@ module Rails
#
def environment(data=nil, options={}, &block)
sentinel = /class [a-z_:]+ < Rails::Application/i
+ env_file_sentinel = /::Application\.configure do/
data = block.call if !data && block_given?
in_root do
if options[:env].nil?
inject_into_file 'config/application.rb', "\n #{data}", :after => sentinel, :verbose => false
else
- Array.wrap(options[:env]).each do|env|
- append_file "config/environments/#{env}.rb", "\n#{data}", :verbose => false
+ Array.wrap(options[:env]).each do |env|
+ inject_into_file "config/environments/#{env}.rb", "\n #{data}", :after => env_file_sentinel, :verbose => false
end
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index bbdd000ad9..21a2ae4e28 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -200,9 +200,11 @@ module Rails
def assets_gemfile_entry
<<-GEMFILE.strip_heredoc
+ # Gems used only for assets and not required
+ # in production environments by default.
group :assets do
- gem 'sass-rails', :git => 'git://github.com/rails/sass-rails'
- gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails'
+ gem 'sass-rails', :git => 'git://github.com/rails/sass-rails.git'
+ gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails.git'
gem 'uglifier'
end
GEMFILE
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index f9f89c9f1d..816d82cac3 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -32,7 +32,7 @@ module Rails
when :decimal then "9.99"
when :datetime, :timestamp, :time then Time.now.to_s(:db)
when :date then Date.today.to_s(:db)
- when :string then "MyString"
+ when :string then name == "type" ? "" : "MyString"
when :text then "MyText"
when :boolean then false
when :references, :belongs_to then nil
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 6af9d299aa..d9dd33518a 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -117,7 +117,8 @@ module Rails
end
def tmp
- empty_directory_with_gitkeep "tmp/cache"
+ empty_directory "tmp/cache"
+ empty_directory "tmp/cache/assets"
end
def vendor
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 88eea40b1b..c83e7ddf80 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -7,10 +7,7 @@ source 'http://rubygems.org'
<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%>
<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
-# Gems used only for assets and not required
-# in production environments by default.
<%= assets_gemfile_entry %>
-
<%= javascript_gemfile_entry %>
# Use unicorn as the web server
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 9e07c7d9a9..3b5cc6648e 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -4,10 +4,10 @@
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
- *
+ *
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
- *= require_tree .
-*/ \ No newline at end of file
+ *= require_tree .
+*/
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index eaa31e7386..3891829150 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -8,14 +8,16 @@ require 'rails/all'
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
-<%= comment_if :skip_sprockets %> require "sprockets/railtie"
-<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie"
+<%= comment_if :skip_sprockets %>require "sprockets/railtie"
+<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
-# If you have a Gemfile, require the default gems, the ones in the
-# current environment and also include :assets gems if in development
-# or test environments.
-Bundler.require *Rails.groups(:assets => %w(development test)) if defined?(Bundler)
+if defined?(Bundler)
+ # If you precompile assets before deploying to production, use this line
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
+ # If you want your assets lazily compiled in production, use this line
+ # Bundler.require(:default, :assets, Rails.env)
+end
module <%= app_const_base %>
class Application < Rails::Application
@@ -47,9 +49,9 @@ module <%= app_const_base %>
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
- <% unless options.skip_sprockets? %>
+<% unless options.skip_sprockets? -%>
# Enable the asset pipeline
config.assets.enabled = true
- <% end %>
+<% end -%>
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 3e0c29a587..33f9939ffe 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -29,4 +29,10 @@
# Do not compress assets
config.assets.compress = false
+
+ # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
+ config.assets.allow_debugging = true
+
+ # Expands the lines which load the assets
+ config.assets.debug = true
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 06ed890e05..de56d47688 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -15,8 +15,8 @@
config.assets.compress = true
# Specifies the header that your server uses for sending files
- # (comment out if your front-end server doesn't support this)
- config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index ee068b0202..8e33a65b2d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -34,6 +34,14 @@
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
+ <%- unless options.skip_active_record? -%>
+ # Raise exception on mass assignment protection for ActiveRecord models
+ config.active_record.mass_assignment_sanitizer = :strict
+ <%- end -%>
+
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
+
+ # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
+ config.assets.allow_debugging = true
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
index 9e8b0131f8..5d8d9be237 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
@@ -8,3 +8,8 @@
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
+#
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index fa1548db8b..d640f578da 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -4,9 +4,13 @@
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
-ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %>
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters <%= key_value :format, "[:json]" %>
+end
+<%- unless options.skip_active_record? -%>
# Disable root element in JSON by default.
-if defined?(ActiveRecord)
- ActiveRecord::Base.include_root_in_json = false
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
end
+<%- end -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 7c0a2b9cf4..4baa2110e7 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -11,14 +11,13 @@ module Rails
def app
if mountable?
directory "app"
- template "app/views/layouts/application.html.erb.tt",
- "app/views/layouts/#{name}/application.html.erb"
empty_directory_with_gitkeep "app/assets/images/#{name}"
elsif full?
empty_directory_with_gitkeep "app/models"
empty_directory_with_gitkeep "app/controllers"
empty_directory_with_gitkeep "app/views"
empty_directory_with_gitkeep "app/helpers"
+ empty_directory_with_gitkeep "app/mailers"
empty_directory_with_gitkeep "app/assets/images/#{name}"
end
end
@@ -46,6 +45,7 @@ module Rails
def lib
template "lib/%name%.rb"
template "lib/tasks/%name%_tasks.rake"
+ template "lib/%name%/version.rb"
if full?
template "lib/%name%/engine.rb"
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 56b06829d8..8588e88077 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -1,12 +1,27 @@
-# Provide a simple gemspec so you can easily use your
-# project in your rails apps through git.
+$:.push File.expand_path("../lib", __FILE__)
+
+# Maintain your gem's version:
+require "<%= name %>/version"
+
+# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
- s.name = "<%= name %>"
- s.summary = "Insert <%= camelized %> summary."
- s.description = "Insert <%= camelized %> description."
- s.files = Dir["{app,config,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
+ s.name = "<%= name %>"
+ s.version = <%= camelized %>::VERSION
+ s.authors = ["TODO: Your name"]
+ s.email = ["TODO: Your email"]
+ s.homepage = "TODO"
+ s.summary = "TODO: Summary of <%= camelized %>."
+ s.description = "TODO: Description of <%= camelized %>."
+
+ s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
<% unless options.skip_test_unit? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
- s.version = "0.0.1"
+
+ <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
+<% if full? && !options[:skip_javascript] -%>
+ # s.add_dependency "<%= "#{options[:javascript]}-rails" %>"
+<% end -%>
+
+ s.add_development_dependency "<%= gem_for_database %>"
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index 7e6eb18341..160baa6906 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,14 +1,20 @@
source "http://rubygems.org"
-<%= rails_gemfile_entry -%>
+# Declare your gem's dependencies in <%= name %>.gemspec.
+# Bundler will treat runtime dependencies like base dependencies, and
+# development dependencies will be added by default to the :development group.
+gemspec
-<% if full? -%>
-<%= database_gemfile_entry -%>
-<% end -%>
+# Declare any dependencies that are still in development here instead of in
+# your gemspec. These might include edge Rails or gems from your path or
+# Git. Remember to move these dependencies to your gemspec before releasing
+# your gem to rubygems.org.
-<% if mountable? -%>
-<%= javascript_gemfile_entry -%>
-<% end -%>
+<% if options.dev? || options.edge? -%>
+# Your gem is dependent on dev or edge Rails. Once you can lock this
+# dependency down to a specific version, move it to your gemspec.
+<%= rails_gemfile_entry -%>
+<% end -%>
# To use debugger
# <%= ruby_debugger_gemfile_entry %> \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index b28a842731..6ed6adcf1b 100755
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -15,7 +15,7 @@ end
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = '<%= camelized %>'
- rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end
@@ -24,3 +24,8 @@ end
APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
+
+<% unless options[:skip_gemspec] -%>
+
+Bundler::GemHelper.install_tasks
+<% end %>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory b/railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
index 01550dec2f..01550dec2f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb
new file mode 100644
index 0000000000..ef07ef2e19
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb
@@ -0,0 +1,3 @@
+module <%= camelized %>
+ VERSION = "0.0.1"
+end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
index 4864ead960..996ea79e67 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
@@ -8,8 +8,8 @@ require 'rails/all'
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
-<%= comment_if :skip_sprockets %> require "sprockets/railtie"
-<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie"
+<%= comment_if :skip_sprockets %>require "sprockets/railtie"
+<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
Bundler.require
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 09ff0ef378..b37421c09c 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -2,7 +2,7 @@ require 'set'
module Rails
module Paths
- # This object is an extended hash that behaves as root of the Rails::Paths system.
+ # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
# It allows you to collect information about how you want to structure your application
# paths by a Hash like API. It requires you to give a physical path on initialization.
#
@@ -10,13 +10,13 @@ module Rails
# root.add "app/controllers", :eager_load => true
#
# The command above creates a new root object and add "app/controllers" as a path.
- # This means we can get a Path object back like below:
+ # This means we can get a +Rails::Paths::Path+ object back like below:
#
# path = root["app/controllers"]
# path.eager_load? # => true
# path.is_a?(Rails::Paths::Path) # => true
#
- # The Path object is simply an array and allows you to easily add extra paths:
+ # The +Path+ object is simply an array and allows you to easily add extra paths:
#
# path.is_a?(Array) # => true
# path.inspect # => ["app/controllers"]
@@ -24,17 +24,17 @@ module Rails
# path << "lib/controllers"
# path.inspect # => ["app/controllers", "lib/controllers"]
#
- # Notice that when you add a path using #add, the path object created already
- # contains the path with the same path value given to #add. In some situations,
+ # Notice that when you add a path using +add+, the path object created already
+ # contains the path with the same path value given to +add+. In some situations,
# you may not want this behavior, so you can give :with as option.
#
# root.add "config/routes", :with => "config/routes.rb"
# root["config/routes"].inspect # => ["config/routes.rb"]
#
- # #add also accepts the following options as argument: eager_load, autoload,
- # autoload_once and glob.
+ # The +add+ method accepts the following options as arguments:
+ # eager_load, autoload, autoload_once and glob.
#
- # Finally, the Path object also provides a few helpers:
+ # Finally, the +Path+ object also provides a few helpers:
#
# root = Root.new "/rails"
# root.add "app/controllers"
@@ -42,7 +42,7 @@ module Rails
# root["app/controllers"].expanded # => ["/rails/app/controllers"]
# root["app/controllers"].existent # => ["/rails/app/controllers"]
#
- # Check the Path documentation for more information.
+ # Check the <tt>Rails::Paths::Path</tt> documentation for more information.
class Root < ::Hash
attr_accessor :path
@@ -157,7 +157,7 @@ module Rails
path = File.expand_path(p, @root.path)
if @glob
- result.concat Dir[File.join(path, @glob)]
+ result.concat Dir[File.join(path, @glob)].sort
else
result << path
end
@@ -172,6 +172,10 @@ module Rails
expanded.select { |f| File.exists?(f) }
end
+ def existent_directories
+ expanded.select { |d| File.directory?(d) }
+ end
+
alias to_a expanded
end
end
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index ceddd25eaa..3e27688bb9 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -74,7 +74,8 @@ module Rails
initializer :load_init_rb, :before => :load_config_initializers do |app|
init_rb = File.expand_path("init.rb", root)
if File.file?(init_rb)
- config = app.config
+ # This double assignment is to prevent an "unused variable" warning on Ruby 1.9.3.
+ config = config = app.config
# TODO: think about evaling initrb in context of Engine (currently it's
# always evaled in context of Rails::Application)
eval(File.read(init_rb), binding, init_rb)
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 8c88b25617..e8fb1f3d98 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -2,6 +2,7 @@ require 'rails/initializable'
require 'rails/configuration'
require 'active_support/inflector'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/delegation'
module Rails
# Railtie is the core of the Rails framework and provides several hooks to extend
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 79255d1f56..ca8875ad9b 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -8,6 +8,8 @@ end
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
+ include ::Rake::DSL
+
def define
task rdoc_task_name
@@ -47,7 +49,7 @@ namespace :doc do
rdoc.rdoc_dir = 'doc/app'
rdoc.template = ENV['template'] if ENV['template']
rdoc.title = ENV['title'] || "Rails Application Documentation"
- rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.options << '--line-numbers'
rdoc.options << '--charset' << 'utf-8'
rdoc.rdoc_files.include('doc/README_FOR_APP')
rdoc.rdoc_files.include('app/**/*.rb')
@@ -60,7 +62,7 @@ namespace :doc do
rdoc.rdoc_dir = 'doc/api'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.title = "Rails Framework Documentation"
- rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README')
gem_path('actionmailer') do |actionmailer|
@@ -133,7 +135,7 @@ namespace :doc do
files = Rake::FileList.new
options << "-o doc/plugins/#{plugin}"
options << "--title '#{plugin.titlecase} Plugin Documentation'"
- options << '--line-numbers' << '--inline-source'
+ options << '--line-numbers'
options << '--charset' << 'utf-8'
options << '-T html'
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 77a5c4dc6c..206ce39773 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -2,14 +2,14 @@ namespace :rails do
desc "Update configs and some other initially generated files (or use just update:configs, update:scripts, or update:application_controller)"
task :update => [ "update:configs", "update:scripts", "update:application_controller" ]
- desc "Applies the template supplied by LOCATION=/path/to/template"
+ desc "Applies the template supplied by LOCATION=(/path/to/template) or URL"
task :template do
template = ENV["LOCATION"]
+ raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank?
template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://}
-
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
- generator = Rails::Generators::AppGenerator.new [ Rails.root ], {}, :destination_root => Rails.root
+ generator = Rails::Generators::AppGenerator.new [Rails.root], {}, :destination_root => Rails.root
generator.apply template, :verbose => false
end
diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake
index 833fcb6f72..8b4775d1d3 100644
--- a/railties/lib/rails/tasks/misc.rake
+++ b/railties/lib/rails/tasks/misc.rake
@@ -14,7 +14,7 @@ task :secret do
end
desc 'List versions of all Rails frameworks and the environment'
-task :about do
+task :about => :environment do
puts Rails::Info
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index a0c953967c..0c26bcf790 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -10,8 +10,16 @@ task :routes => :environment do
routes = all_routes.collect do |route|
reqs = route.requirements.dup
- reqs[:to] = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/
- reqs = reqs.empty? ? "" : reqs.inspect
+ rack_app = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/
+
+ endpoint = rack_app ? rack_app.inspect : "#{reqs[:controller]}##{reqs[:action]}"
+ constraints = reqs.except(:controller, :action)
+
+ reqs = endpoint == '#' ? '' : endpoint
+
+ unless constraints.empty?
+ reqs = reqs.empty? ? constraints.inspect : "#{reqs} #{constraints.inspect}"
+ end
{:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
end
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 3ee5452475..0d6c10328f 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -4,7 +4,7 @@ namespace :tmp do
desc "Creates tmp directories for sessions, cache, sockets, and pids"
task :create do
- FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids ))
+ FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids tmp/cache/assets ))
end
namespace :sessions do
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index b9877a83b5..33da2c0f5a 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -108,9 +108,9 @@ namespace :test do
Rake::TestTask.new(:uncommitted => "test:prepare") do |t|
def t.file_list
if File.directory?(".svn")
- changed_since_checkin = silence_stderr { `svn status` }.map { |path| path.chomp[7 .. -1] }
+ changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] }
elsif File.directory?(".git")
- changed_since_checkin = silence_stderr { `git ls-files --modified --others` }.map { |path| path.chomp }
+ changed_since_checkin = silence_stderr { `git ls-files --modified --others` }.split.map { |path| path.chomp }
else
abort "Not a Subversion or Git checkout."
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
new file mode 100644
index 0000000000..38e1e21d17
--- /dev/null
+++ b/railties/test/application/asset_debugging_test.rb
@@ -0,0 +1,56 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class AssetDebuggingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app(:initializers => true)
+
+ app_file "app/assets/javascripts/application.js", "//= require_tree ."
+ app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }"
+ app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>"
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match '/posts', :to => "posts#index"
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ActionController::Base
+ end
+ RUBY
+
+ ENV["RAILS_ENV"] = "production"
+
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "assets are concatenated when debug is off and allow_debugging is off either if debug_assets param is provided" do
+ # config.assets.debug and config.assets.allow_debugging are false for production environment
+ require "#{app_path}/config/environment"
+
+ # the debug_assets params isn't used if allow_debugging is off
+ get '/posts?debug_assets=true'
+ assert_match %r{<script src="/assets/application-([0-z]+)\.js" type="text/javascript"></script>}, last_response.body
+ assert_no_match %r{<script src="/assets/xmlhr-([0-z]+)\.js" type="text/javascript"></script>}, last_response.body
+ end
+
+ test "assets aren't concatened when allow_debugging is on and debug_assets params is true" do
+ app_file "config/initializers/allow_debugging.rb", "Rails.application.config.assets.allow_debugging = true"
+
+ require "#{app_path}/config/environment"
+
+ get '/posts?debug_assets=true'
+ assert_match %r{<script src="/assets/application-([0-z]+)\.js\?body=1" type="text/javascript"></script>}, last_response.body
+ assert_match %r{<script src="/assets/xmlhr-([0-z]+)\.js\?body=1" type="text/javascript"></script>}, last_response.body
+ end
+ end
+end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index e1eee71a9e..a8d1382e94 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -8,7 +8,7 @@ module ApplicationTests
include Rack::Test::Methods
def setup
- build_app
+ build_app(:initializers => true)
boot_rails
end
@@ -46,10 +46,11 @@ module ApplicationTests
assert defined?(Uglifier)
end
- test "assets are compiled properly" do
+ test "precompile creates the file, gives it the original asset's content and run in production as default" do
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
+ ENV["RAILS_ENV"] = nil
capture(:stdout) do
Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
end
@@ -57,10 +58,31 @@ module ApplicationTests
files << Dir["#{app_path}/public/assets/foo/application-*.js"].first
files.each do |file|
assert_not_nil file, "Expected application.js asset to be generated, but none found"
- assert_equal "alert();\n", File.read(file)
+ assert_equal "alert()", File.read(file)
end
end
+ test "precompile appends the md5 hash to files referenced with asset_path and run in the provided RAILS_ENV" do
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+
+ # capture(:stdout) do
+ Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_ENV=test` }
+ # end
+ file = Dir["#{app_path}/public/assets/application-*.css"].first
+ assert_match /\/assets\/rails-([0-z]+)\.png/, File.read(file)
+ end
+
+ test "precompile appends the md5 hash to files referenced with asset_path and run in production as default even using RAILS_GROUPS=assets" do
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+
+ ENV["RAILS_ENV"] = nil
+ capture(:stdout) do
+ Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_GROUPS=assets` }
+ end
+ file = Dir["#{app_path}/public/assets/application-*.css"].first
+ assert_match /\/assets\/rails-([0-z]+)\.png/, File.read(file)
+ end
+
test "assets are cleaned up properly" do
app_file "public/assets/application.js", "alert();"
app_file "public/assets/application.css", "a { color: green; }"
@@ -70,7 +92,7 @@ module ApplicationTests
Dir.chdir(app_path){ `bundle exec rake assets:clean` }
end
- files = Dir["#{app_path}/public/assets/**/*"]
+ files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
@@ -99,5 +121,19 @@ module ApplicationTests
assert_match "alert()", last_response.body
assert_equal nil, last_response.headers["Set-Cookie"]
end
+
+ test "files in any assets/ directories are not added to Sprockets" do
+ %w[app lib vendor].each do |dir|
+ app_file "#{dir}/assets/#{dir}_test.erb", "testing"
+ end
+
+ app_file "app/assets/javascripts/demo.js", "alert();"
+
+ require "#{app_path}/config/environment"
+
+ get "/assets/demo.js"
+ assert_match "alert();", last_response.body
+ assert_equal 200, last_response.status
+ end
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 2547863d12..448982f9de 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -45,7 +45,7 @@ module ApplicationTests
Rails.env = "development"
assert_equal [:default, "development"], Rails.groups
assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development])
- assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development))
+ assert_equal [:default, "development", :another, :assets], Rails.groups(:another, :assets => %w(development))
Rails.env = "test"
assert_equal [:default, "test"], Rails.groups(:assets => [:development])
@@ -516,5 +516,14 @@ module ApplicationTests
get "/", { :format => :xml }, "HTTP_ACCEPT" => "application/xml"
assert_equal 'XML', last_response.body
end
+
+ test "Rails.application#env_config exists and include some existing parameters" do
+ make_basic_app
+
+ assert_respond_to app, :env_config
+ assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
+ assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
+ assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
+ end
end
end
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index c7a1c573f9..d2ad2668bb 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -27,11 +27,12 @@ module ApplicationTests
end
# x_sendfile_header middleware
- test "config.action_dispatch.x_sendfile_header defaults to ''" do
+ test "config.action_dispatch.x_sendfile_header defaults to nil" do
make_basic_app
simple_controller
get "/"
+ assert !last_response.headers["X-Sendfile"]
assert_equal File.read(__FILE__), last_response.body
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 6a0a272073..bed5ba503f 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -20,6 +20,8 @@ module ApplicationTests
end
test "default middleware stack" do
+ add_to_config "config.action_dispatch.x_sendfile_header = 'X-Sendfile'"
+
boot!
assert_equal [
@@ -47,6 +49,12 @@ module ApplicationTests
], middleware
end
+ test "Rack::Sendfile is not included by default" do
+ boot!
+
+ assert !middleware.include?("Rack::Sendfile"), "Rack::Sendfile is not included in the default stack unless you set config.action_dispatch.x_sendfile_header"
+ end
+
test "Rack::Cache is present when action_controller.perform_caching is set" do
add_to_config "config.action_controller.perform_caching = true"
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 0d22d8c19a..964cff48cd 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -48,7 +48,6 @@ module ApplicationTests
assert_path @paths["vendor"], "vendor"
assert_path @paths["vendor/plugins"], "vendor/plugins"
assert_path @paths["tmp"], "tmp"
- assert_path @paths["tmp/cache"], "tmp/cache"
assert_path @paths["config"], "config"
assert_path @paths["config/locales"], "config/locales/en.yml"
assert_path @paths["config/environment"], "config/environment.rb"
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 7671c129e9..cc65a674c9 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -91,7 +91,7 @@ module ApplicationTests
get '/cart', :to => 'cart#show'
end
RUBY
- assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` }
+ assert_equal "cart GET /cart(.:format) cart#show\n", Dir.chdir(app_path){ `rake routes` }
end
def test_rake_routes_shows_custom_assets
@@ -100,7 +100,91 @@ module ApplicationTests
get '/custom/assets', :to => 'custom_assets#show'
end
RUBY
- assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` }
+ assert_equal "custom_assets GET /custom/assets(.:format) custom_assets#show\n",
+ Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_resources_route
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ resources :articles
+ end
+ RUBY
+ expected =
+ " articles GET /articles(.:format) articles#index\n" <<
+ " POST /articles(.:format) articles#create\n" <<
+ " new_article GET /articles/new(.:format) articles#new\n" <<
+ "edit_article GET /articles/:id/edit(.:format) articles#edit\n" <<
+ " article GET /articles/:id(.:format) articles#show\n" <<
+ " PUT /articles/:id(.:format) articles#update\n" <<
+ " DELETE /articles/:id(.:format) articles#destroy\n"
+ assert_equal expected, Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_root_route
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ root :to => 'pages#main'
+ end
+ RUBY
+ assert_equal "root / pages#main\n", Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_controller_and_action_only_route
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match ':controller/:action'
+ end
+ RUBY
+ assert_equal " /:controller/:action(.:format) \n", Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_controller_and_action_route_with_constraints
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match ':controller(/:action(/:id))', :id => /\\d+/
+ end
+ RUBY
+ assert_equal " /:controller(/:action(/:id))(.:format) {:id=>/\\d+/}\n", Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_route_with_defaults
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
+ end
+ RUBY
+ assert_equal %Q[ /photos/:id(.:format) photos#show {:format=>"jpg"}\n], Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_route_with_constraints
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match 'photos/:id' => 'photos#show', :id => /[A-Z]\\d{5}/
+ end
+ RUBY
+ assert_equal " /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}\n", Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_rake_routes_shows_route_with_rack_app
+ app_file "lib/rack_app.rb", <<-RUBY
+ class RackApp
+ class << self
+ def call(env)
+ end
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ require 'rack_app'
+
+ AppTemplate::Application.routes.draw do
+ match 'foo/:id' => RackApp, :id => /[A-Z]\\d{5}/
+ end
+ RUBY
+
+ assert_equal " /foo/:id(.:format) RackApp {:id=>/[A-Z]\\d{5}/}\n", Dir.chdir(app_path){ `rake routes` }
end
def test_logger_is_flushed_when_exiting_production_rake_tasks
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 597746c4aa..e4a8000425 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -109,6 +109,13 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'config/application.rb', /#{Regexp.escape(autoload_paths)}/
end
+ def test_environment_should_include_data_in_environment_initializer_block_with_env_option
+ run_generator
+ autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]'
+ action :environment, autoload_paths, :env => 'development'
+ assert_file "config/environments/development.rb", /Application\.configure do\n #{Regexp.escape(autoload_paths)}/
+ end
+
def test_environment_with_block_should_include_block_contents_in_environment_initializer_block
run_generator
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 81f0bf5e82..1b48c80042 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -35,6 +35,7 @@ DEFAULT_APP_FILES = %w(
vendor/assets
vendor/plugins
tmp/cache
+ tmp/cache/assets
)
class AppGeneratorTest < Rails::Generators::TestCase
@@ -188,7 +189,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "test/performance/browsing_test.rb"
end
- def test_generator_if_skip_active_record_is_given
+ def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
@@ -313,6 +314,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generated_environments_file_for_sanitizer
+ run_generator [destination_root, "--skip-active-record"]
+ ["config/environments/development.rb", "config/environments/test.rb"].each do |env_file|
+ assert_file env_file do |file|
+ assert_no_match(/config.active_record.mass_assignment_sanitizer = :strict/, file)
+ end
+ end
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index c9f8ab0a7b..a85829085c 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -68,6 +68,11 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
assert_field_default_value :string, 'MyString'
end
+ def test_default_value_for_type
+ att = Rails::Generators::GeneratedAttribute.new("type", "string")
+ assert_equal("", att.default)
+ end
+
def test_default_value_is_text
assert_field_default_value :text, 'MyText'
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 4bd77ff7e3..b49945f153 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -7,11 +7,13 @@ DEFAULT_PLUGIN_FILES = %w(
.gitignore
Gemfile
Rakefile
+ README.rdoc
bukkits.gemspec
MIT-LICENSE
lib
lib/bukkits.rb
lib/tasks/bukkits_tasks.rake
+ lib/bukkits/version.rb
test/bukkits_test.rb
test/test_helper.rb
test/dummy
@@ -25,10 +27,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
# brings setup, teardown, and some tests
include SharedGeneratorTests
- def default_files
- ::DEFAULT_PLUGIN_FILES
- end
-
def test_invalid_plugin_name_raises_an_error
content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
assert_equal "Invalid plugin name 43-things. Please give a name which does not start with numbers.\n", content
@@ -69,13 +67,13 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode
run_generator([destination_root, "--full"])
assert_file "test/dummy/config/database.yml", /sqlite/
- assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
+ assert_file "bukkits.gemspec", /sqlite3/
end
def test_config_another_database
run_generator([destination_root, "-d", "mysql", "--full"])
assert_file "test/dummy/config/database.yml", /mysql/
- assert_file "Gemfile", /^gem\s+["']mysql2["']$/
+ assert_file "bukkits.gemspec", /mysql/
end
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
@@ -117,8 +115,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'jquery-rails'/, contents)
+ assert_file 'bukkits.gemspec' do |contents|
+ assert_match(/jquery-rails/, contents)
end
end
@@ -128,8 +126,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require prototype}, contents
assert_match %r{^//= require prototype_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'prototype-rails'/, contents)
+ assert_file 'bukkits.gemspec' do |contents|
+ assert_match(/prototype-rails/, contents)
end
end
@@ -176,6 +174,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "app/controllers"
assert_file "app/views"
assert_file "app/helpers"
+ assert_file "app/mailers"
assert_file "config/routes.rb", /Rails.application.routes.draw do/
assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
@@ -205,10 +204,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
- assert_file "bukkits.gemspec", /s.name = "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,lib\}\/\*\*\/\*"\]/
+ assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
+ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*"\]/
assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/
- assert_file "bukkits.gemspec", /s.version = "0.0.1"/
+ assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
end
def test_usage_of_engine_commands
@@ -257,6 +256,10 @@ protected
silence(:stdout){ generator.send(*args, &block) }
end
+protected
+ def default_files
+ ::DEFAULT_PLUGIN_FILES
+ end
end
class CustomPluginGeneratorTest < Rails::Generators::TestCase
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index b793a7401f..94dec405a7 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -15,10 +15,12 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
+ resources :posts
match "/engine_route" => "application_generating#engine_route"
match "/engine_route_in_view" => "application_generating#engine_route_in_view"
match "/url_for_engine_route" => "application_generating#url_for_engine_route"
match "/polymorphic_route" => "application_generating#polymorphic_route"
+ match "/application_polymorphic_path" => "application_generating#application_polymorphic_path"
scope "/:user", :user => "anonymous" do
mount Blog::Engine => "/blog"
end
@@ -59,6 +61,7 @@ module ApplicationTests
resources :posts
match '/generate_application_route', :to => 'posts#generate_application_route'
match '/application_route_in_view', :to => 'posts#application_route_in_view'
+ match '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path'
end
RUBY
@@ -79,6 +82,10 @@ module ApplicationTests
def application_route_in_view
render :inline => "<%= main_app.root_path %>"
end
+
+ def engine_polymorphic_path
+ render :text => polymorphic_path(Post.new)
+ end
end
end
RUBY
@@ -100,6 +107,10 @@ module ApplicationTests
def polymorphic_route
render :text => polymorphic_url([blog, Blog::Post.new])
end
+
+ def application_polymorphic_path
+ render :text => polymorphic_path(Blog::Post.new)
+ end
end
RUBY
@@ -172,6 +183,14 @@ module ApplicationTests
# test polymorphic routes
get "/polymorphic_route"
assert_equal "http://example.org/anonymous/blog/posts/44", last_response.body
+
+ # test that correct path is generated for the same polymorphic_path call in an engine
+ get "/somone/blog/engine_polymorphic_path"
+ assert_equal "/somone/blog/posts/44", last_response.body
+
+ # and in an application
+ get "/application_polymorphic_path"
+ assert_equal "/posts/44", last_response.body
end
end
end
diff --git a/tasks/release.rb b/tasks/release.rb
index 01950b227d..2422efa786 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -4,11 +4,11 @@ root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
tag = "v#{version}"
-directory "dist"
+directory "pkg"
(FRAMEWORKS + ['rails']).each do |framework|
namespace framework do
- gem = "dist/#{framework}-#{version}.gem"
+ gem = "pkg/#{framework}-#{version}.gem"
gemspec = "#{framework}.gemspec"
task :clean do
@@ -41,10 +41,10 @@ directory "dist"
File.open(file, 'w') { |f| f.write ruby }
end
- task gem => %w(update_version_rb dist) do
+ task gem => %w(update_version_rb pkg) do
cmd = ""
cmd << "cd #{framework} && " unless framework == "rails"
- cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/dist/"
+ cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/"
sh cmd
end
@@ -104,14 +104,14 @@ namespace :all do
end
task :commit do
- File.open('dist/commit_message.txt', 'w') do |f|
+ File.open('pkg/commit_message.txt', 'w') do |f|
f.puts "# Preparing for #{version} release\n"
f.puts
f.puts "# UNCOMMENT THE LINE ABOVE TO APPROVE THIS COMMIT"
end
- sh "git add . && git commit --verbose --template=dist/commit_message.txt"
- rm_f "dist/commit_message.txt"
+ sh "git add . && git commit --verbose --template=pkg/commit_message.txt"
+ rm_f "pkg/commit_message.txt"
end
task :tag do